package util.net;

import java.io.*;
import java.net.*;
import java.util.*;
import util.net.Debug;


/**
 * A reasonably vanilla network client that works with Server.java.
 * To send objects to server, using method send. 
 * To customize how objects are received from server, provide it with 
 * an InputHandler (the handleInput method is called).
 */
public class Client implements Runnable
{
    //////////////////////////////////////////////////////////////
    // state
    private SocketConnection myConnection;
    private ArrayList myHandlers; 
    private boolean myStopped;
    private Thread mySpirit;


    //////////////////////////////////////////////////////////////
    // constructor
    /**
     * Creates an autonomous client connected to a server running at hostname
     * and listening on port. 
     */
    public Client (String hostName, int port)
    {
	try
	{
	    myConnection = new SocketConnection(new Socket(hostName, port));
            myHandlers = new ArrayList();
	    mySpirit = new Thread(this);
	    mySpirit.start();
	}
	catch (Exception e)
	{
	    throw new RuntimeException("Client:  could not establish connection with " + 
				       hostName + ":" + port);
	}
    }


    //////////////////////////////////////////////////////////////
    // public methods
    /**
     * Reads objects from the server.  Called by this object's Thread.
     * Should not be called otherwise.
     */
    public void run ()
    {
        try
        {
	    Debug.println("Client:  starting read loop." );
            while (!myStopped)
            {
                Object input = myConnection.readObject();
		Debug.println("Client: read from server " + input);
                notifyHandlers(input);
            }
        }
        catch (Exception e)
        {
            throw new RuntimeException("Client:  failed to read");
        }
    }

    /**
     * Use this method to send an object to the server.
     */
    public void send (Object o)
    {
        try
        {
	    Debug.println("Client:  sending " + o + " to server.");
            myConnection.writeObject(o);
        }
        catch (Exception e)
        {
            throw new RuntimeException("Client:  failed to send " + o);
        }
    }

    /**
     * Add specialized handler to customize how server input is received.
     */
    public void addInputHandler (InputHandler handler)
    {
        myHandlers.add(handler);
    }

    /**
     * Remove server input handler.
     */
    public void removeInputHandler (InputHandler handler)
    {
        myHandlers.remove(handler);
    }

    /**
     *  Closes the socket, stops the thread.
     */
    public void stop ()
    {
        try
        {
            myStopped = true;
	    myConnection = null;
        }
        catch (Exception e)
        {}
    }
    
    private void notifyHandlers (Object o)
    {
	Iterator iter = myHandlers.iterator();
	while (iter.hasNext())
	{
            ((InputHandler)(iter.next())).handleInput(o);
	}
    }
}
