package com.limegroup.gnutella.handshaking;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Properties;

import com.limegroup.gnutella.Constants;
import com.limegroup.gnutella.statistics.HandshakingStat;

/**
 * An outgoing handshaker that blocks while handshaking.
 */
public class BlockingOutgoingHandshaker implements Handshaker {
    
    private BlockingHandshakeSupport support;
    private Properties ourRequestHeaders;
    private HandshakeResponder ourResponder;

    /**
     * Constructs a new BlockingOutgoingHandshaker using the given
     * requestHeaders for the initial request, responsder to handle our response,
     * and Socket/InputStream/OutputStream for I/O.
     */ 
    public BlockingOutgoingHandshaker(Properties requestHeaders, HandshakeResponder responder,
                                      Socket socket, InputStream in, OutputStream out) {
        this.support = new BlockingHandshakeSupport(socket, in, out);
        this.ourRequestHeaders = requestHeaders;
        this.ourResponder = responder;
    }
    
    /** Returns a HandshakeResponse containing all the headers we read while handshaking. */
    public HandshakeResponse getReadHeaders() {
        return support.getReadHandshakeResponse();
    }

    /** Returns a HandshakeResponse containing all the headers we wrote while handshaking. */
    public HandshakeResponse getWrittenHeaders() {
        return support.getWrittenHandshakeResponse();
    }

    /** Performs the handshake.*/
    public void shake() throws IOException, BadHandshakeException, NoGnutellaOkException {
        initializeOutgoing();
        concludeOutgoingHandshake();
    }

    /** 
     * Sends and receives handshake strings for outgoing connections,
     * throwing exception if any problems. 
     * 
     * @exception NoGnutellaOkException one of the participants responded
     *  with an error code other than 200 OK (possibly after several rounds
     *  of 401's)
     * @exception IOException any other error.  
     */
    private void initializeOutgoing() throws IOException {
        support.writeConnectLine();
        support.sendHeaders(ourRequestHeaders);
    }
    
    /**
     * Responds to the responses/challenges from the host on the other
     * end of the connection, till a conclusion reaches. Handshaking may
     * involve multiple steps. 
     *
     * @exception NoGnutellaOkException one of the participants responded
     *              with an error code other than 200 OK
     * @exception IOException any other error.  
     */
    private void concludeOutgoingHandshake() throws IOException {
        String connectLine = support.readLine();
        if (!support.isConnectLineValid(connectLine)) {
            HandshakingStat.OUTGOING_BAD_CONNECT.incrementStat();
            throw new IOException("Bad connect string");
        }
        
        support.readHeaders(Constants.TIMEOUT);

        //Terminate abnormally if we read something other than 200 or 401.
        HandshakeResponse theirResponse = support.createRemoteResponse(connectLine);

        switch(theirResponse.getStatusCode()) {
        case HandshakeResponse.OK:
            break;
        case HandshakeResponse.SLOTS_FULL:
            handleSlotsFullStats(theirResponse);
            throw NoGnutellaOkException.SERVER_REJECT;
        default: 
            HandshakingStat.OUTGOING_SERVER_UNKNOWN.incrementStat();
            throw NoGnutellaOkException.createServerUnknown(theirResponse.getStatusCode());
        }

        HandshakeResponse ourResponse = ourResponder.respond(theirResponse, true);
        support.writeResponse(ourResponse);

        switch(ourResponse.getStatusCode()) {
        case HandshakeResponse.OK:
            HandshakingStat.SUCCESSFUL_OUTGOING.incrementStat();
            break;
        case HandshakeResponse.SLOTS_FULL:
            HandshakingStat.OUTGOING_CLIENT_REJECT.incrementStat();
            throw NoGnutellaOkException.CLIENT_REJECT;
        case HandshakeResponse.LOCALE_NO_MATCH:
            //if responder's locale preferencing was set 
            //and didn't match the locale this code is used.
            //(currently in use by the dedicated connectionfetcher)
            throw NoGnutellaOkException.CLIENT_REJECT_LOCALE;
        default: 
            HandshakingStat.OUTGOING_CLIENT_UNKNOWN.incrementStat();
            throw NoGnutellaOkException.createClientUnknown(ourResponse.getStatusCode());
        }
    }
    
    /** Increments various HandshakingStats because of a slots full response. */
    private void handleSlotsFullStats(HandshakeResponse theirResponse) {
        if (theirResponse.isLimeWire()) {
            if (theirResponse.isUltrapeer())
                HandshakingStat.OUTGOING_LIMEWIRE_ULTRAPEER_REJECT.incrementStat();
            else
                HandshakingStat.OUTGOING_LIMEWIRE_LEAF_REJECT.incrementStat();
        } else {
            if (theirResponse.isUltrapeer())
                HandshakingStat.OUTGOING_OTHER_ULTRAPEER_REJECT.incrementStat();
            else
                HandshakingStat.OUTGOING_OTHER_LEAF_REJECT.incrementStat();
        }
    }
}
