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

/**
 * The LogHopper class. This is used to hop to all the nodes and grab their log files
 * at a certain interval at a time.
 * @author Matt Keoshkerian
 */
public class LogHopper extends Thread implements Runnable
{
	private int interval = 600;   // The interval in seconds. Default is 10 minutes (600 seconds)
	private LogHopper thisHop = this;
    //private String line;
    private HopperInterval ta;
    static int instance = 0;
    static Properties props = null;
    private String task_ip;
    private String task_port;
    private String result_ip;
    private String result_port;
    
    public LogHopper(int interval, String propsAddress, ThreadGroup group)
    {
    	super(group, "Hopper"+instance++);
    	this.interval = interval;
    	//this.line = line;
	
    	if (props == null)
    	{
    		System.out.println("Starting...");
    		try {
    			props = new Properties();
    			URL url = new URL(propsAddress);
    			System.setProperty("propsAddress", propsAddress);
    			BufferedInputStream inStream = new BufferedInputStream(url.openStream());
    			props.load(inStream);
    		}
    		catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    }
    
    public void run() 
    {
    	String spaces = props.getProperty("spaces");
    	StringTokenizer spacesSplit = new StringTokenizer(spaces, ",");
    	String taskspace = spacesSplit.nextToken();
    	String resultspace = spacesSplit.nextToken();
    	
    	// Task IP and Port
    	StringTokenizer tsSplit = new StringTokenizer(taskspace, ":");
    	task_ip = tsSplit.nextToken();
    	task_port = tsSplit.nextToken();
    	
    	// Result IP and Port
    	StringTokenizer rsSplit = new StringTokenizer(resultspace, ":");
    	result_ip = rsSplit.nextToken();
    	result_port = rsSplit.nextToken();
	
    	ta = new HopperInterval(this, interval);
    	ta.start();
    }
    
    /**
     * This method will hop to all the nodes and grab their log files.
     * @return void
     */
    public void hop()
    {
    	try {
    		System.out.println("Starting hop...");
    		// Create a socket connection to the task and result space to grab all the registered nodes.
    		int tPortNum = Integer.parseInt(task_port);
    		int rPortNum = Integer.parseInt(result_port);
    		Socket sT = new Socket(task_ip, tPortNum);
    		Socket sR = new Socket(result_ip, rPortNum);
	
    		// Set the Communication value (perhaps make it extend communicator?)
    		int GET_ALL_NODES = 11;

    		System.out.println("Retrieving node list...");
    		// Write out to the task space requestion the list of the nodes.
    		InputStream inT = sT.getInputStream();
    		InputStream inR = sR.getInputStream();
    		ObjectOutputStream outStreamT = new ObjectOutputStream(sT.getOutputStream());
    		ObjectOutputStream outStreamR = new ObjectOutputStream(sR.getOutputStream());
	    
    		outStreamT.writeInt(GET_ALL_NODES);
    		outStreamT.flush();
    		outStreamR.writeInt(GET_ALL_NODES);
    		outStreamR.flush();
    		ObjectInputStream inStreamT = new ObjectInputStream(inT);
    		ObjectInputStream inStreamR = new ObjectInputStream(inR);
	
    		// Read the list sent in and place it as an array.
    		//ObjectInputStream inStream = new ObjectInputStream(s.getInputStream());
    		Object [] nodesT = (Object [])inStreamT.readObject();
    		Object [] nodesR = (Object [])inStreamR.readObject();
    		
    		// Close the streams and the sockets. 
    		inStreamT.close();
    		inStreamR.close();
    		outStreamT.close();
    		outStreamR.close();
    		sT.close();
    		sR.close();
    		System.out.println("Node list recieved...");
	    
	    
    		// Hop to the TaskSpace first.
    		String tSpaceAddr = task_ip + ":" + tPortNum;
    		connectAndLog(tSpaceAddr, "Task Space", true);
    		
    		// Hop to the ResultSpace next
    		String rSpaceAddr = result_ip + ":" + rPortNum;
    		connectAndLog(rSpaceAddr, "Result Space", true);

    		// Connect to all the recievers first and recieve the logs.
    		for (int i = 0; i < nodesR.length; i++)
    		{
    			System.out.println("Connecting to Reciever node #" + i + ". Addr: " + (String)nodesR[i]);
    			connectAndLog((String)nodesR[i], ("Reciever #" + i), false);
    		}
    			
    		// Now hop to each node on the TaskSpace and send the LogAgent to be executed, then read the page and process from there.
    		for (int i = 0; i < nodesT.length; i++)
    		{
    			System.out.println("Connecting to Worker node #" + i + ". Addr: " + (String)nodesT[i]);
    			connectAndLog((String)nodesT[i], ("Node #" + i),false);
    			// One Node completed, hopping to the next.
    		}

    		// All nodes have been visited. Leaving the hop method.
    	}
    	catch (Exception e)
		{
    		e.printStackTrace();
		}
    }
    
    private void connectAndLog(String addr, String name, boolean space)
    {
    	try {
    		// Get the vital information
    		StringTokenizer ipPort = new StringTokenizer(addr, ":");
    		String nodeip = ipPort.nextToken();
    		String nodeport = ipPort.nextToken();
	    
    		System.out.println("Making the LogAgent...");
    		// Create an instance of the LogAgent
    		LogAgent agent = new LogAgent();

    		// Now create a socket to the Node and transmit the LogAgent.
    		int nodeportNo = Integer.parseInt(nodeport);
    		Socket conn = new Socket(nodeip, nodeportNo);
    		ObjectOutputStream out = new ObjectOutputStream(conn.getOutputStream());
    		InputStream rawInput = conn.getInputStream();
    		if (space)
    			out.writeInt(12);
    		out.writeObject(agent);
    		out.flush();
    		System.out.println("Agent has been sent... Awaiting execution...");
	    
    		// Now we should be recieving instructions. Create an input stream, and then process accordingly
    		//InputStream rawInput = conn.getInputStream();
    		ObjectInputStream input = new ObjectInputStream(rawInput);
	    
	    
    		int mode;
    		while((mode = input.readInt()) != 0)
    		{
    			String user;
    			String appName;
	
    			// Get the user and appName. If it is system, set it manually.
    			if (mode == 1)
    			{
    				user = "system";
    				appName = "system";
    			}
    			else
    			{
    				user = (String)input.readObject();
    				appName = (String)input.readObject();
    			}
	    
    			System.out.println("Agent has responded... Log for " + user + "'s application (" + appName + ")");
	    
	    
    			// Now we should be recieving the file bytes. Open up the file's output stream and append to it.
    			File f = new File(user + "/" + appName + "/log");
    			//BufferedInputStream data = new BufferedInputStream(rawInput);
    			
    			FileOutputStream fos = new FileOutputStream(f, true);
    			String header = "==" + name + ":\n";
    			fos.write(header.getBytes());
    			int b;
    			while((b=input.readInt())!= -1) { fos.write(b);}
    			fos.write("\n".getBytes());
    			fos.flush();
    			fos.close();
    			//data.close();
    			System.out.println("File has been sent and written... Iterating for next log/app");
    			//System.out.println(input.available());
    			// Iterate over again
    		}
    		System.out.println("All Done... closing");
    		input.close();
    		out.close();
    		rawInput.close();
    		conn.close();
    		System.out.println("Sockets closed...");
    	}
    	catch (IOException e) { }
    	catch (Exception f) {f.printStackTrace();};
    }
    
    public static void main (String [] args)
    {
    	ThreadGroup group = new ThreadGroup("hoppers");
    	String addr = "http://"+args[1];
    	try {
    		LogHopper lawl = new LogHopper(Integer.parseInt(args[0]),addr,group);
    		lawl.run();
    	}
    	catch (NumberFormatException e) {
    		System.out.println("Please use a number as the interval.");
    	}
    }
}

class HopperInterval extends Thread
{
    private LogHopper main;
    private int interval;
    
    public HopperInterval (LogHopper main, int interval)
    {
    	super("Interval");
    	this.main = main;
    	this.interval = interval;
    }
    
    public void run()
    {
	
    	do {
    		// Do the routine.
    		main.hop();

    		// Wait out the interval
    		try {
    			this.sleep((interval * 1000));
    		}
    		catch (InterruptedException e) {
    		}
	    
    		// Repeat now!
	
    	}while(true); // Yea, it pretty much never ends, unless killed.
    }
}
