package taskspaces.system;

import java.io.*;
import java.net.*;

/**
 * Represents the point of interaction between Nodes and other
 * components. 
 * @author Rob Markel, Matthew Keoshkerian
 * @version 2.0
 */
public class Server extends Communicator implements Runnable 
{
	/** Reference to local TaskStore. */
	private TaskStore taskStore;

	/** Reference to local MessageStore. */
	private MessageStore messageStore;

	/** ID for this Server. */
	private int id;

	/** ServerSocket for this Server. */
	private ServerSocket s;

	/** Reference to the host Node. */
	private Node node;
    
	/** Timeout count */
	private int timeout = 10;

	/** Constructor for the Server class. */
	public Server(TaskStore taskStore,MessageStore messageStore,int id,Node node) 
	{
		this.taskStore=taskStore;
		this.messageStore=messageStore;
		this.id=id;
		this.node=node;
	}

	/**
	 * Starts a Server thread on an open port, and sets the port in the parent Node. 
	 * The thread remains running waiting for arrival of Tasks or Agents.
	 * @return void
	 */
	public void run() 
	{
		try
		{
			// Get the port number of the node to which this class belongs to.
			int port=node.getDefaultPort();
         
			// Try and open a ServerSocket on the default port, but if unable to, try on the next port until a ServerSocket can be established.
			while(s==null)
			{
				try {s=new ServerSocket(port);} 
				catch(BindException e) {port++;}
			}
	 
			// Get the Port value to which thise ServerSocket is listening and tell set the node's value to correspond.
			port=s.getLocalPort();
			node.setPort(port);
			node.print("Server: Running on port "+port);
	 
			// Get the space address from the node.
			String spaceAddress=node.getSpaceAddress();
         
			// Register the node with the Space. Try (every minute) until the connection is established.
			int count = 0;
			while(true && count < timeout)
			{
				try {registerNode(spaceAddress,port); break;}
				catch(ConnectException e) {Thread.sleep(60000); count++;}
			}
			connLoop:while(true)
			{
				try
				{
					// Wait for a connection and accept it. Get it's input stream and read the object sent. 
					Socket socket = null;
					socket=s.accept();
					ObjectInputStream in=new ObjectInputStream(socket.getInputStream());
					Object o=in.readObject();
	       
					// Check to see what object was sent and process it accordingly.
					if(o instanceof Task) 
					{  
						// Store the task sent and have it wait to be processed.
						node.print("Server: Received Task");
						Task t=(Task)o;
						t.setNode(node);
						node.taskStore.put(t);
					}
					if(o instanceof Agent)
					{
						// Setup the agent and run it.
						node.print("Server: Received Agent");
						Agent a=(Agent)o;
						a.setNode(node);

						// This will happen if it is the LogAgent. That way the socket isn't wasted.
						if (o instanceof LogAgent)
						{
							((LogAgent)a).setVars(socket, node);
							((Runnable)a).run();
							continue connLoop;
						}
		  
						((Runnable)a).run();
					}
					if(o instanceof URL)
					{
						addURL((URL)o, this);
					}
					if(!(o instanceof Task) && !(o instanceof Agent) && !(o instanceof URL)) 
					{     
						// This is a message sent, store it.
						Object message=in.readObject();
						node.messageStore.put(o,message); 
					}
	       
					// Close the stream and the socket.
					in.close();
					socket.close();
				} 
				catch(StreamCorruptedException e)
				{
					node.print("Server: Received connection without serialized object.");
				}
			}
		} catch(Exception e) {e.printStackTrace();}
	}
}
