/* DistributedWaveTCP.java */

package distributedwave;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.ServerSocket;
import java.io.IOException;

/** Concrete implementation of abstract class DistributedWave that communicates 
 *  with neighbors via TCP.
 *
 *  Copyright (c) 2010, 2011 - Russell C. Bjork
 */
public class DistributedWaveTCP extends DistributedWave
{
    /** Constructor
     *
     *  @param leftNeighbor the left neighbor specified by the user - null
     *         if no neighbor was specified at startup
     *  @param rightNeighbor the right neighbor specified by the user - null
     *         if no neighbor was specified at startup
     *  @exception IOException if there is a problem creating a socket
     */
    public DistributedWaveTCP(InetAddress leftNeighbor,
                           InetAddress rightNeighbor) throws IOException

    {
        super();
        
        // Connect to each neighbor if one is specified, or create a thread to
        // listen for a connection request from the neighbor if none specified.
        // When connecting to a neighbor, our left neighbor sees us as its right
        // neighbor and vice versa, which is reflected in the way the port is
        // specified in the connect request

        if (leftNeighbor != null)
        {
            communicateWithNeighborSocket[LEFT] = new Socket();
            communicateWithNeighborSocket[LEFT].connect(
                    new InetSocketAddress(leftNeighbor,
                                          COMMUNICATE_WITH_NEIGHBOR_PORT[RIGHT]));
        }
        else
            new Thread() {
                public void run()
                {
                    listenForConnectionRequest(LEFT);
                }
            }.start();

        if (rightNeighbor != null)
        {
            communicateWithNeighborSocket[RIGHT] = new Socket();
            communicateWithNeighborSocket[RIGHT].connect(
                    new InetSocketAddress(rightNeighbor,
                                          COMMUNICATE_WITH_NEIGHBOR_PORT[LEFT]));
        }
        else
            new Thread() {
                public void run()
                {
                    listenForConnectionRequest(RIGHT);
                }
            }.start();
    }

    /** Method executed by a thread that listens for incoming connection
     *  requests
     *
     *  @param side the side on which to listen - one of LEFT, RIGHT
     */
    private void listenForConnectionRequest(int side)
    {
        ServerSocket serverSocket = null;
        try
        {
            serverSocket = new ServerSocket(COMMUNICATE_WITH_NEIGHBOR_PORT[side]);
        }
        catch(IOException e)
        {
            System.err.println("Error creating server socket on " +
                               SIDE_NAME[side] + " " + e);
            System.exit(1);
        }

        while (true)
        {
            Socket connectionSocket = null;
            try
            {
                connectionSocket = serverSocket.accept();
            }
            catch(IOException e)
            {
                System.err.println("Error accepting connection on " + 
                                   SIDE_NAME[side] + " " + e);
                System.exit(1);
            }
            synchronized(this)
            {
                communicateWithNeighborSocket[side] = connectionSocket;
                notifyAll();
            }
        }
    }

    void sendToNeighbor(int side, byte [] waveMessage)
    {
        if (communicateWithNeighborSocket[side] != null)
            try
            {
                communicateWithNeighborSocket[side].getOutputStream().
                    write(waveMessage);
            }
            catch(IOException e)
            {
                System.err.println("Exception sending to neighbor on " +
                                   SIDE_NAME[1 - side] + ": " + e);
                System.exit(1);
            }
    }
    
    /** Receive a message from a neighbor.  This method blocks until a
     *  message is received
     *
     *  @param side the side to receive from
     *  @return the message received from this neighbor
     */
    byte [] receiveMessage(int side)
    {
        Socket socket;
        byte [] messageBuffer = new byte[MESSAGE_LENGTH];

        while (communicateWithNeighborSocket[side] == null)
        {
            // Wait until a connection has been opened
            synchronized(this)
            {
                try
                {
                    wait();
                }
                catch(InterruptedException e)
                { }
            }
        }
        try
        {
            communicateWithNeighborSocket[side].getInputStream().
                    read(messageBuffer);
        }
        catch(IOException e)
        {
            System.err.println("Exception on receiving from neighbor on " +
                                SIDE_NAME[side] + ": " + e);
            System.exit(1);
        }
        return messageBuffer;
    }
    
    private Socket [] communicateWithNeighborSocket = new Socket[2];
}
