package taskspaces.system;
 
import java.io.*;
import java.util.*;
import java.net.*;

/**
 * Gathers result String objects from a Space
 * and prints them to a new file with the given path, name
 * and header, footer, and date timestamp if specified.
 * @author Rob Markel, Matthew Keoshkerian
 * @version 2.0 
 */
public class Receiver extends Communicator implements Runnable
{
	/** IP address and port number of the Space storing results. */
	public String spaceAddress;

	/** Number of results to be received. */
	public int tasks;

	/** Desired full output file path. */
	public String path;

	/** Desired full status file path. */
	public String statusPath;

	/** Print date if <code>true</code>. */
	public boolean date=false;
 
	/** Include output file header. */
	public String header=null; 

	/** Include output file footer. */
	public String footer=null; 

	/** Overwrite <code>false</code> or append <code>true</code> output. */
	public boolean append; 

	/** Hold 1 for tasks received, 0 for tasks not accounted for. */
	public int[] receivedTasks;
	
	/** The username of the person who started this job. */
	protected String user;
	
	/** The application name of this job */
	protected String app;
	
	/** 
	 * Constructor takes an IP address and port number of the result space, the number of expected
	 * result Objects, and an output file name to create and a header to write to the created file.
	 * @param spaceAddress the address to the space which this quasi-Node will register with. 
	 * @param tasks the amount of tasks the Reciever will recieve.
	 * @param path the file path where the results will be printed out to
	 * @param statusPath the file path where the status results will be preinted to (i.e. which results come in)
	 * @param user the username of the person who started the application
	 * @param app the application name of this job
	 * @param append <code>true</code> if you wish to append to an existing file, <code>false</code> if you wish to create a new one.
	 */
	public Receiver(String spaceAddress, int tasks, String path, String statusPath, String user, String app, boolean append)
	{
		this.spaceAddress=spaceAddress;
		this.tasks=tasks;
		this.path=path;
		this.statusPath=statusPath;
		this.append=append;
		receivedTasks = new int[tasks+1]; 
		this.user = user;
		this.app = app;
	}

	/** 
	 * If <code>true</code>, includes a date timestamp at the top of the output file. 
	 * @param b true to include date.
	 * @return void
	 */
	public void includeDate(boolean b)
	{
		this.date=b; 
	}

	/** 
	 * Includes a header String at the top of the output file. 
	 * @param s the header text.
	 * @return void
	 */
	public void printHeader(String s)
	{
		this.header=header; 
	}

	/** 
	 * Includes a footer String at the bottom of the output file. 
	 * @param s the footer text.
	 * @return void
	 */
	public void printFooter(String s)
	{
		this.footer=footer; 
	}

	/**
	 * Starts a server on an open port. The server registers with a Space as a Node, and as objects
	 * arrive the Space will send them to the server's address. The objects are cast to Strings
	 * and the Strings are written to an output file.
	 * @return void
	 */
	public void run()
	{
		try
		{
			// Setup the communicator's logWrite for system logging.
			//logWriteName = "./logs/" + user + "." + app;
			//logPrefix = "Reciever";
			//logWrite = new PrintWriter(new FileWriter(logWriteName, true));
			
			// Setup the printwriter for writing results.
			(new File("./results/")).mkdir();
			PrintWriter pw;
			path=path.replace('/',File.separatorChar);
			statusPath=statusPath.replace('/',File.separatorChar);
			PrintWriter statusWriter = new PrintWriter(new FileWriter(statusPath,true));

			BufferedReader br = new BufferedReader(new FileReader(statusPath));
			String line;
			int received = 0;
			while((line = br.readLine()) != null)
			{
				int receivedId = Integer.parseInt(line); 
				receivedTasks[receivedId] = 1;
				received++;
			}
			int numberLeft = tasks-received;

			if(append)
			{
				pw=new PrintWriter(new BufferedWriter(new FileWriter(path,true)));
			}
			else
			{
				pw=new PrintWriter(new BufferedWriter(new FileWriter(path,false)));
			}
			if(date)
			{
				pw.println(new Date()+"\n");
			}
			if(header!=null)
			{
				pw.println(header);
			}
			int counter=0;
			ServerSocket server=new ServerSocket(0);
			int localPort=server.getLocalPort();
			print("Receiver: Waiting for "+numberLeft+" tasks.");
			
			//resultLoop:while(true)
			resultLoop:while(counter<numberLeft)
			{
				registerNode(spaceAddress,localPort);
				Socket sock=server.accept();
				ObjectInputStream is=new ObjectInputStream(sock.getInputStream());
				Object o=is.readObject();
				
				if(o instanceof LogAgent)
				{
					// Do the stuff here. Send back data and such.
					Agent a = (Agent)o;
					((LogAgent)a).setVars(sock, this);
					((Runnable)a).run();
					is.close();
					sock.close();
					
					//if(counter >= numberLeft)
						//break resultLoop;
					
					continue resultLoop;
				}
				
				if(o!=null)
				{
					Result result = (Result)o;
					int id = result.getId();
					print("Receiver: "+id+" received");
					int status = receivedTasks[id];
					if(!(status == 1))
					{
						counter++;
						receivedTasks[id] = 1;
						statusWriter.println(id);
						statusWriter.flush();
						Object data = result.getResultObject();
						if(data instanceof String)
						{
							pw.println(data.toString());
							pw.flush();
						}
						if(data instanceof String[])
						{
							String[] strings = (String[])data;
							for(int i = 0; i < strings.length; i++)
							{
								pw.println(strings[i]);
								pw.flush();
							}
						}
					}
					else
					{
						print("Receiver: Duplicate task id received!");
					}
				}
				is.close();
				sock.close();
			}
			if(footer!=null)
			{
				pw.println(footer);
			}
			
			// Delete node from the list.
			StringTokenizer resSpace = new StringTokenizer(spaceAddress, ":");
			Socket s = new Socket(resSpace.nextToken(), Integer.parseInt(resSpace.nextToken()));
			ObjectOutputStream oStream = new ObjectOutputStream(s.getOutputStream());
			oStream.writeInt(DELETE_NODE);
			oStream.writeInt(localPort);
			oStream.close();
			s.close();
			
			// Flush and close streams.
			pw.flush();
			pw.close();
			statusWriter.flush();
			statusWriter.close();
		} catch(Exception e) {e.printStackTrace();}
	}
}
