/*
 * 	
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

import java.io.*;
import java.util.*;
import java.net.*;
import javax.comm.*;

public class SoSserver {

    static final int FUNC_OPEN_HOST = 100;
    static final int FUNC_OPEN_IPN = 101;
    static final int FUNC_READ = 102;
    static final int FUNC_AVAILABLE = 103;
    static final int FUNC_WRITE = 104;
    static final int FUNC_CLOSE = 105;
    static final int FUNC_SHUTDOWN = 106;
    static final int FUNC_GET_IPNUMBER = 107;

    static final int SUCCESS = 1;
    static final int READ_BUFFER_SIZE = 256;
    static final int MAX_NO_OF_SOCKETS = 5;

    static final int UNKNOWNHOST_EXCEPTION_ERROR = -1;
    static final int PCSL_NET_IOERROR  = -2;
    static final int PCSL_NET_INTERRUPTED = -3;

    static final int SOCKET_ID_NOT_AVAILABLE = -2;
    static final int SOCKET_ID_AVAILABLE = -1;
    static final int SOCKET_ID_IN_USE = 0;

    Socket[] sockets;
    int[] socketid;

    CommPort serialPort;

    public SoSserver(int portno) {
        sockets = new Socket[MAX_NO_OF_SOCKETS];
        
        try {
            serialPort = new CommPort(portno, 9600, SerialPort.DATABITS_8,
                                      SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
        } catch (IOException e) {
            System.out.println("Open comm port error!");
        }

        socketid = new int[MAX_NO_OF_SOCKETS];

        for (int i=0; i < MAX_NO_OF_SOCKETS; i++) {
            socketid[i] = SOCKET_ID_AVAILABLE;
        }

        processRequestsFromClient();
    }

    public void processRequestsFromClient() {
        int func_type = -1;
        String dataString;

        while (true) {
            try {
                byte[] readin = serialPort.serialReceiveInt();
                func_type = getIntFromByteArray(readin);
                //System.out.println("func_type : " + func_type);
            } catch (IOException ioe) {
                System.out.println("IOException in serial port operations");
                return;
            }

            switch (func_type) {

                case FUNC_OPEN_HOST :  performOpenConnectionByHost();
                                  break;

                case FUNC_OPEN_IPN :  performOpenConnectionByIpn();
                                  break;

                case FUNC_READ :  performReadOperation();
                                  break;

                case FUNC_AVAILABLE :  performAvailableOperation();
                                  break;

                case FUNC_WRITE : performWriteOperation();
                                  break;
     
                case FUNC_GET_IPNUMBER :  performGetIpNumber();
                                  break;

                case FUNC_CLOSE : 
                                  boolean serialPortClosed = performCloseConnection();
                                  if (serialPortClosed) {
                                      System.out.println("End of the story");
                                      return;
                                  } 
                                  
                                  break;

                case FUNC_SHUTDOWN : performShutdownConnection();
                                  break;
     

                default         : break;
            
            }
        }
	}

    private void performOpenConnectionByHost() {
        try {
            performOpenConnectionByHostImpl();
        } catch (IOException ioe) {
            System.out.println("performOpenConnectionByHost : IOException" +
                               " for serial port operations ");
        }
    }
    
    private void performOpenConnectionByHostImpl() throws IOException {
        boolean exceptionThrown = false;
        System.out.println("performOpenConnectionByHostImpl");
        String strHost = null;
        int port = -1;
        int handle;

        //Get the length of host string from client
        byte[] dataFromProxy1 = serialPort.serialReceiveInt();
        int hostLength = getIntFromByteArray(dataFromProxy1);

        byte[] hostBytes = serialPort.serialReceive(hostLength);
        strHost = new String(hostBytes);

        //Get the port id from client
        byte[] dataFromProxy2 = serialPort.serialReceiveInt();
        port = getIntFromByteArray(dataFromProxy2);

        //Get the available socket-id from socketid array
        handle = getFirstAvailableSocketID();
        if ( handle == SOCKET_ID_NOT_AVAILABLE) {
            System.out.println("Limit of no of open connections exceeded");
            serialPort.serialSend(PCSL_NET_IOERROR);
        }

        try {
            //Open the proxy socket
            sockets[handle] = io_openSocketConnectionByHost(strHost, port);
        } catch (UnknownHostException uhe) {
            System.out.println("performOpenConnectionByHostImpl : " +
                               " UnknownHostException ");
            serialPort.serialSend(UNKNOWNHOST_EXCEPTION_ERROR);
            return;
        } catch (IOException ioe) {
            System.out.println("performOpenConnectionByHostImpl : IOException in" +
                                " opening a proxy socket");
            serialPort.serialSend(PCSL_NET_IOERROR);
            return;
        } catch (SecurityException se) {
            System.out.println("SecurityException : IOException in" +
                                " opening a proxy socket");
        } 

        //send the socket-id to the client
        System.out.println("socket= " + sockets[handle]);
        serialPort.serialSend(handle);

        // Mark the socketid status in socketid array
        socketid[handle] = SOCKET_ID_IN_USE;
    }

    private void performOpenConnectionByIpn() {
        try {
            performOpenConnectionByIpnImpl();
        } catch (IOException ioe) {
            System.out.println("performOpenConnectionByIpn : IOException" +
                               " for serial port operations ");
        }
    }
    
    private void performOpenConnectionByIpnImpl() throws IOException {
        boolean exceptionThrown = false;
        System.out.println("performOpenConnectionByIpnImpl");
        String strHost = null;
        int port = -1;
        int handle;

        //Get the length of host string from client
        byte[] ipn = serialPort.serialReceiveInt();

        //Get the port id from client
        byte[] dataFromProxy2 = serialPort.serialReceiveInt();
        port = getIntFromByteArray(dataFromProxy2);

        //Get the available socket-id from socketid array
        handle = getFirstAvailableSocketID();
        if ( handle == SOCKET_ID_NOT_AVAILABLE) {
            System.out.println("Limit of no of open connections exceeded");
            serialPort.serialSend(PCSL_NET_IOERROR);
        }

        try {
            //Open the proxy socket
            sockets[handle] = io_openSocketConnectionByIpn(ipn, port);
        } catch (UnknownHostException uhe) {
            System.out.println("performOpenConnectionByIpnImpl : " +
                               " UnknownHostException ");
            serialPort.serialSend(UNKNOWNHOST_EXCEPTION_ERROR);
            return;
        } catch (IOException ioe) {
            System.out.println("performOpenConnectionByIpnImpl : IOException in" +
                                " opening a proxy socket");
            serialPort.serialSend(PCSL_NET_IOERROR);
            return;
        } catch (SecurityException se) {
            System.out.println("SecurityException : IOException in" +
                                " opening a proxy socket");
        } 

        //send the socket-id to the client
        System.out.println("socket= " + sockets[handle]);
        serialPort.serialSend(handle);

        // Mark the socketid status in socketid array
        socketid[handle] = SOCKET_ID_IN_USE;
    }

    private void performGetIpNumber() {
        try {
            performGetIpNumberImpl();
        } catch (IOException ioe) {
            System.out.println("performGetIpNumber : IOException for serial" +
                                " port operations ");
        }
    }
    
    private void performGetIpNumberImpl() throws IOException {
        System.out.println("performGetIpNumberImpl");
        String strHost = null;
        byte[] ipn;

        //Get the length of host string from client
        byte[] dataFromProxy1 = serialPort.serialReceiveInt();
        int hostLength = getIntFromByteArray(dataFromProxy1);

        byte[] hostBytes = serialPort.serialReceive(hostLength);
        strHost = new String(hostBytes);

        try {
            // Get the IP address for the host
            ipn = io_getIpNumber(strHost);
        } catch (UnknownHostException ioe) {
            System.out.println("performGetIpNumberImpl : UnknownHostException in" +
                                " io_getIpNumber()");
            serialPort.serialSend(UNKNOWNHOST_EXCEPTION_ERROR);
            return;
        } 

        /* 
        Send the IP-address array to the client. ipn represents the 
        result in network byte-order: the highest order byte of the address
        is in ipn[0]
        */
        //System.out.println("performGetIpNumberImpl : ipn " + ipn);

        serialPort.serialSend(ipn, 4);
    }

    private void performWriteOperation() {
        try {
            performWriteOperationImpl();
        } catch (IOException ioe) {
            System.out.println("performWriteOperation : IOException for serial"  +
                                " port operations ");
        } 
    }

    private void performWriteOperationImpl() throws IOException {
        String dataString;
        int handle = -1;
        byte[] buf = null;

        System.out.println("performWriteOperation ");

        //Get the socketid from client
        byte[] dataFromProxy1 = serialPort.serialReceiveInt();
        handle = getIntFromByteArray(dataFromProxy1); 
        //System.out.println("handle : " + handle);

        //IMPL_NOTE: check if socket-id is invalid

        //Get the length of data to be read
        byte[] dataFromProxy2 = serialPort.serialReceiveInt();
        int numbytes = getIntFromByteArray(dataFromProxy2);
        //System.out.println("numbytes : " + numbytes);

        //Get the data to be written at the socket
        buf = serialPort.serialReceive(numbytes);

        //Perform the write operation at proxy socket
        System.out.println("Data from client : " + new String(buf));

        try {
            io_writeDataToSocket(sockets[handle], buf, numbytes);
        } catch (InterruptedIOException ioe) {
            System.out.println("performWriteOperation : InterruptedIOException"
                                + " in writing to a proxy socket");
            serialPort.serialSend(PCSL_NET_INTERRUPTED);
            return;
        } catch (IOException ioe) {
            System.out.println("performWriteOperation : IOException in"  +
                                " writing to a proxy socket");
            serialPort.serialSend(PCSL_NET_IOERROR);
            return;
        } 

        /* 
         * io_writeDataToSocket() does not return how many bytes it has 
         * actually written to the socket. According to the Javadoc for 
         * write() of an outputstream, it writes all the bytes one by one 
         */
        // Return the no of bytes written
        serialPort.serialSend(numbytes);
    }

    private void performReadOperation() {
        try {
            performReadOperationImpl();
        } catch (IOException ioe) {
            System.out.println("performReadOperation : IOException for serial"  +
                                " port operations ");
        }
    }

    private void performReadOperationImpl() throws IOException {
        String dataString;
        int handle = -1;
        int numbytes;
        int bytesRead;

        System.out.println("performReadOperation");

        //Get the socketid from client
        byte[] dataFromProxy1 = serialPort.serialReceiveInt();
        handle = getIntFromByteArray(dataFromProxy1);
        //System.out.println("handle : " + handle);
        //IMPL_NOTE: check if socket-id is invalid
       
        //Get the number of bytes to be read 
        byte[] dataFromProxy2 = serialPort.serialReceiveInt();
        numbytes = getIntFromByteArray(dataFromProxy2);
		byte[] buf = new byte[numbytes];

        try { 
            //Perform the read operation
            bytesRead = io_readDataFromSocket(sockets[handle], buf, numbytes);
        } catch (InterruptedIOException ioe) {
            System.out.println("performReadOperation : InterruptedIOException"
                                + " in reading from a proxy socket");
            serialPort.serialSend(PCSL_NET_INTERRUPTED);
            return;
        } catch (IOException ioe) {
            System.out.println("performReadOperation : IOException in" + 
                                " reading from a proxy socket");
            serialPort.serialSend(PCSL_NET_IOERROR);
            return;
        }
        
        //send the number of bytes read from socket
        serialPort.serialSend(bytesRead);
        //System.out.println("bytesRead : " + bytesRead);
        //write the actual data
        serialPort.serialSend(buf, bytesRead);

        //System.out.println("End of performReadOperation");
    }

    private void performAvailableOperation() {
        try {
            performAvailableOperationImpl();
        } catch (IOException ioe) {
            System.out.println("performAvailableOperation : IOException for serial"  +
                                " port operations ");
        }
    }

    private void performAvailableOperationImpl() throws IOException {
        String dataString;
        int handle = -1;
        int numbytes;
        int availableBytes;

        System.out.println("performAvailableOperation");

        //Get the socketid from client
        byte[] dataFromProxy1 = serialPort.serialReceiveInt();
        handle = getIntFromByteArray(dataFromProxy1);
        System.out.println("handle : " + handle);
        //IMPL_NOTE: check if socket-id is invalid
       
        try { 
            //Perform the read operation
            availableBytes = io_availableBytesFromSocket(sockets[handle]);
        } catch (IOException ioe) {
            System.out.println("performAvailableOperation : IOException in" + 
                                " available operation in a proxy socket");
            serialPort.serialSend(PCSL_NET_IOERROR);
            return;
        }

        //System.out.println("availableBytes : " + availableBytes);
        
        //send the number of bytes available to read from socket
        serialPort.serialSend(availableBytes);
    }

    private boolean performCloseConnection() {
        boolean serialPortClosed= false;
        try {
            serialPortClosed = performCloseConnectionImpl();
        } catch (IOException ioe) {
            System.out.println("performCloseConnection : IOException for serial"  +
                                " port operations ");
        }

        return serialPortClosed;
    }

    private boolean performCloseConnectionImpl() throws IOException {
        String dataString;
        int handle;

        System.out.println("performCloseOperation");

        //Get the socketid from client
        byte[] dataFromProxy1 = serialPort.serialReceiveInt();
        handle = getIntFromByteArray(dataFromProxy1);
        //IMPL_NOTE: check if socket-id is invalid

        try {
            //Perform the close operation
            io_closeSocketConnection(sockets[handle]);
        } catch (IOException ioe) {
            System.out.println("performCloseConnectionImpl : IOException in" + 
                                " closing the proxy socket");
            serialPort.serialSend(PCSL_NET_IOERROR);
            return false;  //serial port is still open
        }

        // Mark the socketid status in socketid array
        socketid[handle] = SOCKET_ID_AVAILABLE;

        serialPort.serialSend(SUCCESS);
      
        return false;
    }

    private void performShutdownConnection() {
        try {
            performShutdownImpl();
        } catch (IOException ioe) {
            System.out.println("performShutdownConnection : IOException for serial"  +
                                " port operations ");
        }
    }

    private void performShutdownImpl() throws IOException {
        String dataString;
        int handle;

        System.out.println("performShutdownImpl");

        //Get the socketid from client
        byte[] dataFromProxy1 = serialPort.serialReceiveInt();
        handle = getIntFromByteArray(dataFromProxy1);
        //IMPL_NOTE: check if socket-id is invalid

        try {
            //Perform the shutdown operation
            io_shutdownSocketConnection(sockets[handle]);
        } catch (IOException ioe) {
            System.out.println("performShutdownImpl : IOException in" + 
                                " closing the proxy socket");
            serialPort.serialSend(PCSL_NET_IOERROR);
            return;
        }

        // Mark the socketid status in socketid array
        socketid[handle] = SOCKET_ID_AVAILABLE;

        serialPort.serialSend(SUCCESS);
    }

	private Socket io_openSocketConnectionByHost(String host, int port) 
            throws IOException, UnknownHostException, SecurityException {
		Socket socket = null;
        //System.out.println("Inside io_openSocketConnection(): host: " + host +
         //                   "port : " + port);

        socket = new Socket(host, port);

        //System.out.println("socket opened successfully");

		return socket;
	}

	private Socket io_openSocketConnectionByIpn(byte[] ipn, int port) 
            throws IOException, UnknownHostException, SecurityException {
		Socket socket = null;
        InetAddress ipAddress = InetAddress.getByAddress(ipn);

        //System.out.println("Inside io_openSocketConnection(): host: " + host +
         //                   "port : " + port);

        socket = new Socket(ipAddress, port);

        //System.out.println("socket opened successfully");

		return socket;
	}

    private byte[] io_getIpNumber(String host) throws UnknownHostException {
        byte[] ip = InetAddress.getByName(host).getAddress();    
        return ip;
    }

    /** 
     * This function returns number of bytes read at proxy socket
     * Returns -1 in case of an error in read operation
     */
	private int io_readDataFromSocket(Socket socket, byte[] buf, 
                            int numbytes) throws IOException {
		int num = 0;

        InputStream in = socket.getInputStream();
        if (in.available() != 0) {
            num = in.read(buf, 0, numbytes);
        }

        return num;
	} 

	private int io_availableBytesFromSocket(Socket socket) 
                throws IOException {
		int num = 0;

        InputStream in = socket.getInputStream();
        num = in.available();

        return num;
	}
 
    /** 
     * This function does NOT return number of bytes written at proxy 
     * socket. It always writes all the bytes per the request. Write()
     * operation for an Outputstream is a blocking operation
     */
	private void io_writeDataToSocket(Socket socket, byte[] buf, 
                                   int numbytes) throws IOException {
        OutputStream out = socket.getOutputStream();
        out.write(buf, 0, numbytes);
	}

	private void io_closeSocketConnection(Socket socket) throws IOException {
        socket.close();
	}

	private void io_shutdownSocketConnection(Socket socket) throws IOException {
        socket.shutdownInput();
        socket.shutdownOutput();
	}

    public int getIntFromByteArray(byte[] buffer) {
        int n = 0;
        for(int i = 0; i < 4; i++){
            n += ((0xFF & buffer[i]) << (8*(3 - i)));
        }
        return n;
    }

    public int getFirstAvailableSocketID() {
        for (int index = 0; index < MAX_NO_OF_SOCKETS; index++) {
            if (socketid[index] == SOCKET_ID_AVAILABLE) {
                return index;
            }
        }

        return SOCKET_ID_NOT_AVAILABLE;
    }

    public static void main(String[] args) {
        SoSserver sosserver = new SoSserver(1);
    }

}


