package com.limegroup.gnutella.simpp;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.Socket;

import org.limewire.service.ErrorService;
import org.limewire.util.CommonUtils;

import com.limegroup.gnutella.Assert;
import com.limegroup.gnutella.ByteReader;
import com.limegroup.gnutella.messages.BadPacketException;
import com.limegroup.gnutella.messages.Message;
import com.limegroup.gnutella.messages.vendor.CapabilitiesVM;
import com.limegroup.gnutella.messages.vendor.CapabilitiesVMStubHelper;
import com.limegroup.gnutella.messages.vendor.SimppRequestVM;
import com.limegroup.gnutella.messages.vendor.SimppVM;
import com.limegroup.gnutella.util.LimeTestCase;


public class TestConnection {

    private Socket _socket;
    
    private byte[] _simppData; 
    
    
    // used to determine if the file should actually be sent.
    private boolean _sendSimppData;

    private volatile boolean _expectSimppRequest;

    private int _capabilitySimppNo;

    private boolean _causeError;

    /**
     * When creating a TestConnection you want to specify 4 things
     * @param port The port on which the TestConnection should connect to LW
     * @param simppNumber The simppNumber the TestConnection should pretend to
     * have, this affects the simpp-data that this TestConnection eventually
     * sends.
     * @param expectSimppReq whether or not the TestConnection should expect to
     * receive a SimppRequestVM
     * @param sendSimppData whether or not the TestConnection should send the
     * simpp-data when it receives a SimppRequestVM
     */
    public TestConnection(int simppNumber, boolean expectSimppReq,
                                     boolean sendSimppData) throws IOException {
        this(simppNumber, expectSimppReq, sendSimppData, simppNumber);
    }
    
    public TestConnection(int simppNumber, boolean expectSimppReq, 
               boolean sendSimppData, int capabilitySimpp) throws IOException {
        _simppData = readCorrectFile(simppNumber);
        _expectSimppRequest = expectSimppReq;
        _sendSimppData = sendSimppData;
        _capabilitySimppNo = capabilitySimpp;
        _causeError = false;
    }

    public void start() {
        Thread t = new Thread() {
            public void run() {
                //Make an out going connection to the machine
                try {
                    _socket = new Socket("localhost", SimppManagerTest.PORT);
                    establishConnection();
                } catch (IOException iox) {
                    if(!_causeError) //if not expected, show errorservice
                        ErrorService.error(iox);
                }        
            }//end of run
        };
        t.setDaemon(true);
        t.start();
    }

    public void killConnection() {
        try {
            if(_socket != null)
                _socket.close();
        } catch(IOException iox) {}
    }

    private void establishConnection() throws IOException {
        InputStream is = _socket.getInputStream();
        OutputStream os = _socket.getOutputStream();
        ByteReader reader = new ByteReader(is);
        //write out the headers this code needs to change if the definition of
        //what's considered a good connection
        //Phase 1 of the handshake
        os.write("GNUTELLA CONNECT/0.6\r\n".getBytes());
        os.write("User-Agent: LimeWire/3.4.4\r\n".getBytes());
        os.write("Listen-IP:127.0.0.1:6346\r\n".getBytes());
        os.write("X-Query-Routing: 0.1\r\n".getBytes());
        os.write("X-Max-TTL: 3\r\n".getBytes());
        os.write("X-Dynamic-Querying: 0.1\r\n".getBytes());
        os.write(("X-Version: @version@\r\n").getBytes());
        os.write("X-Ultrapeer: False\r\n".getBytes());
        os.write("X-Degree: 15\r\n".getBytes());
        os.write("X-Ultrapeer-Query-Routing: 0.1\r\n".getBytes());
        os.write("\r\n".getBytes());
        //Phase 2 of handshake -- read
        String line = null;
        while((line = reader.readLine()) != null && !line.equals("")) {
            //System.out.println(line);
        }

        //Phase 3 of handshake -- write 200 OK
        os.write("GNUTELLA/0.6 200 OK \r\n".getBytes());
        os.write("\r\n".getBytes());
        //Handshake complete
        //Make sure Simpp code is behaving correctly.
        checkSimppBehaviour(is, os);
    }
    
    private void checkSimppBehaviour(InputStream is, OutputStream os) 
                                                          throws IOException {
        //This method will create a CapabilitiesVM and send it to the LimeWire
        //it is connected to, it will then test that LimeWire is responding
        //correctly, and if necessary upload simpp-bytes to the limewire so the
        //testing can be done.
        
        CapabilitiesVM capVM = makeCapabilitiesVM();
        capVM.write(os);
        os.flush();
        //Read the first message of type SimppRequest
        Message message = null;
        try {
            message = LimeTestCase.getFirstInstanceOfMessage(
                                           _socket, SimppRequestVM.class, 2000);
        } catch (BadPacketException bpx) {
            Assert.that(false, "limewire sent message with BPX");
        }
        if(_expectSimppRequest)
            Assert.that(message != null, "should have gotten simpp message");
        else
            Assert.that(message == null, "shouldn't have gotten simpp message");

        //send back the simppdata if reqd. 
        if( _sendSimppData) {//we dont want 
            if(_causeError)
                throw new IOException();
            Message simppVM = new SimppVM(_simppData);
            simppVM.write(os);
            os.flush();
        }
        
        //Read messages, if (_expectSimppRequest) make sure we get it, otherwise
        //make sure we do not get it. Then based on _sendSimppData send the data
        //back once if we got the SimppRequestVM        
    }

    private CapabilitiesVM makeCapabilitiesVM() {
        try {
            return  CapabilitiesVMStubHelper.makeCapVM(_capabilitySimppNo);
        } catch (Exception e) {
            Assert.that(false, 
                "couldn't set up test -- failed to manipulate CapabilitiesVM");
        }
        return null;
    }

    ////////////////////////////setter methods////////////////////////

    public void setSendSimppData(boolean sendData) {
        _sendSimppData = sendData;
    }
    
    public void setCauseError(boolean err) {
        _causeError = err;
    }

    public void setExpectSimppRequest(boolean expected) {
        _expectSimppRequest = expected;
    }
    
    public void setSimppMessageNumber(int number) {
        try {
            _simppData = readCorrectFile(number);        
        } catch(IOException iox) {
            throw new RuntimeException("unable to read correct simpp test data", iox);
        }
    }

    ////////////////////////utilities//////////////////////

    private byte[] readCorrectFile(int fileVersion) throws IOException {
        File file = null;
        String simppDir = "com/limegroup/gnutella/simpp/";
        if(fileVersion == SimppManagerTest.OLD)
            file = CommonUtils.getResourceFile(simppDir+"oldFile.xml");
        else if(fileVersion == SimppManagerTest.MIDDLE)
            file = CommonUtils.getResourceFile(simppDir+"middleFile.xml");
        else if(fileVersion == SimppManagerTest.NEW) 
            file = CommonUtils.getResourceFile(simppDir+"newFile.xml");
        else if(fileVersion == SimppManagerTest.DEF_MESSAGE)
            file = CommonUtils.getResourceFile(simppDir+"defMessageFile.xml");
        else if(fileVersion == SimppManagerTest.DEF_SIGNATURE)
            file = CommonUtils.getResourceFile(simppDir+"defSigFile.xml");
        else if(fileVersion == SimppManagerTest.BAD_XML)
            file = CommonUtils.getResourceFile(simppDir+"badXmlFile.xml");
        else if(fileVersion == SimppManagerTest.RANDOM_BYTES)
            file = CommonUtils.getResourceFile(simppDir+"randFile.xml");
        else if(fileVersion == SimppManagerTest.ABOVE_MAX)
            file = CommonUtils.getResourceFile(simppDir+"aboveMaxFile.xml");
        else if(fileVersion == SimppManagerTest.BELOW_MIN)
            file = CommonUtils.getResourceFile(simppDir+"belowMinFile.xml");
        else
            Assert.that(false,"simpp version set to illegal value");
        
        //read the bytes of the file and close it. 
        RandomAccessFile raf = new RandomAccessFile(file,"r");
        byte[] ret = new byte[(int)raf.length()];
        raf.readFully(ret);
        raf.close();
        return ret;
    }

}
