package fireTester.tests.batch;

import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Serializable;

import fireTester.messages.MaxDirectorySizeExceededException;
import fireTester.messages.TimeoutExceededException;



/**
 * A BatchCommand is a series of strings to be sent to a terminial.
 *
 */
public class BatchCommand implements Serializable {
	private String _command;
	private String _stdOutputFilename, _errOutputFilename;
	private long _timeout_ms;
	
	/**
	 * Constructor.
	 * 
	 * @param command a command send to a terminial (sequentially)
	 * @param timeout_ms the maximum number of milliseconds to allow commands to execute
	 * @param stdOutFilename if null, stdOut is not saved
	 * @param errOutFilename if null, errOut is not saved
	 */
	public BatchCommand(String command, long timeout_ms, String stdOutFilename, 
			String errOutFilename) {
		assert command != null;
		assert command.trim().length() > 0;
		assert timeout_ms > 0;
		
		this._command = command;
		this._timeout_ms = timeout_ms;
		
		this._stdOutputFilename = stdOutFilename;
		this._errOutputFilename = errOutFilename;
	}
	
	/**
	 * This is a blocking call which will execute the BatchCommand.
	 * 
	 * @param environment array of strings, each element of which has environment
	 * 	variable settings in format name=value.
	 * @param working_directory the working directory for shell commands.
	 * @param max_directory_b the maximum size of the directory in bytes
	 * @param return_type what to return (see BatchReturnType for options)
	 * 
	 * @return the Object selected by return_type
	 * 
	 * @throws TimeoutExceededException the executing command was halted because 
	 * 	the timeout had been violated.
	 * @throws MaxDirectorySizeExceededException the executing command was halted
	 * 	because the max directory size had been violated.
	 * @throws IOException
	 */	
	public Object execute(String[] environment, File working_directory, 
			long max_directory_b, BatchReturnType return_type) 
			throws TimeoutExceededException, MaxDirectorySizeExceededException, IOException {
		assert(working_directory.exists());
		assert(working_directory.isDirectory());
		assert(working_directory.canRead());
		assert(working_directory.canWrite());
		assert max_directory_b > 0;
		
		long start_time = System.currentTimeMillis();
		
		if(working_directory.length() > max_directory_b) {
			throw new MaxDirectorySizeExceededException();
		}

		createCommandFile(working_directory, this._command);
		
		Process chmod = Runtime.getRuntime().exec("chmod 700 command.sh.fire" , environment, working_directory);
	
		try {
			chmod.waitFor();
		} catch(InterruptedException e) {
			// ignore
		}
		
		Process p = Runtime.getRuntime().exec("/usr/bin/sh -c command.sh.fire" , environment, working_directory);
		
		BufferedInputStream stdOutStream = new BufferedInputStream(p.getInputStream());
		BufferedInputStream errOutStream = new BufferedInputStream(p.getErrorStream());
		
		String stdOutString = "", errOutString = "";
		
		
		FileOutputStream stdOutputStream = null, errOutputStream = null;
		if(this._stdOutputFilename != null)
			stdOutputStream = new FileOutputStream(working_directory.getAbsolutePath() + "/" + this._stdOutputFilename);
		
		if(this._errOutputFilename != null)
			errOutputStream = new FileOutputStream(working_directory.getAbsolutePath() + "/" + this._errOutputFilename);
		
		
		do {
			Thread.yield();
			
			if(findDirectorySize(working_directory) > max_directory_b) {
				p.destroy();
				throw new MaxDirectorySizeExceededException();
			}
			if(System.currentTimeMillis() - start_time > this._timeout_ms) {
				p.destroy();
				throw new TimeoutExceededException();
			}
			
			while(stdOutStream.available() > 0) {
				char c = (char)stdOutStream.read();
				
				if(stdOutputStream != null)
					stdOutputStream.write(c);
				
				if(return_type == BatchReturnType.stdOut)
					stdOutString += c;
			}
			while(errOutStream.available() > 0) {
				char c = (char)errOutStream.read();
				
				if(errOutputStream != null)
					errOutputStream.write(c);
				
				if(return_type == BatchReturnType.errOut)
					errOutString += c;
			}
			
			try {
				int exitCode = p.exitValue();
				if(return_type == BatchReturnType.exitCode) 
					return new Integer(exitCode);
				else if(return_type == BatchReturnType.stdOut)
					return stdOutString;
				else if(return_type == BatchReturnType.errOut)
					return errOutString;
			} catch(IllegalThreadStateException e) {
				// execution not yet complete, keep checking
			}
		} while(true);
		
	}
	
	private long findDirectorySize(File dir) {
		long size = 0;
		
		if(dir.isDirectory()) {
			File[] files = dir.listFiles();
			for(int i = 0; i < files.length; i++) {
				File f = files[i];
				
				size += findDirectorySize(f);
			}
		} else {
			size = dir.length();
		}
		
		return size;
	}
	
	private void createCommandFile(File working_dir, String command) {
		BufferedWriter writer = null; 
		
		try {
			File out = new File(working_dir.getAbsolutePath() + "/command.sh.fire");
			writer = new BufferedWriter(new FileWriter(out));
			writer.write(command);
		} catch(IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if(writer != null)
					writer.close();
			} catch(IOException e) {
				// ignore
			}
		}
	}
	
	/**
	 * @return the timeout for this command in ms
	 */
	public long get_timeout_ms() {
		return this._timeout_ms;
	}
	
	/**
	 * Unit test.
	 * 
	 * @param args ingored
	 */
	public static void main(String[] args) {
		BatchCommand a = new BatchCommand("javac *.java\"", 10000L, "stdOut.txt", "errOut.txt");
		String[] environment = new String[1];
		environment[0] = "PATH=.:/usr/java/bin";
		try {
			File working_dir = new File("/home/stu3/s12/tryagain/FireTester/Homes/ChokeTestClient/working_dir/");
			Object result = a.execute(environment, working_dir, 10000L, BatchReturnType.exitCode);
			System.out.println(result.toString());
		} catch(Exception e) {
			e.printStackTrace();
		}
		
		System.out.println("exiting...");
	}
}
