package fireTester.tests.batch;


import java.io.File;
import java.io.IOException;
import java.io.Serializable;

import com.dragonsoft.tryapp.common.SubmissionObj;

import fireTester.interfaces.ClientController;
import fireTester.messages.MaxDirectorySizeExceededException;
import fireTester.messages.TestUnitResults;
import fireTester.messages.TimeoutExceededException;


/**
 * A BatchTestUnit is a collection of commands that together produce one result.
 */
public class BatchTestUnit implements Serializable {
	private BatchCommand[] _pre_commands, _commands;
	private BatchCommand[] _check_pass_commands; // pass when exit code = 0
	private BatchCommand[] _pass_post_commands, _fail_post_commands; 
	private boolean _continue_on_fail;
	private int _next_unit_id;

	/**
	 * Constructor.
	 * 
	 * @param pre_commands (optional) commands to execute before any others
	 * @param commands (required 1+) commands to execute
	 * @param check_pass_commands (required) command to check test outcome 
	 * 	(exitCode: 0 => pass, else => fail)
	 * @param pass_post_commands (optional) commands to execute after all others
	 * 	if the student passed the test
	 * @param fail_post_commands (optional) commands to execute after all others
	 * 	if the student failed the test
	 * @param continue_on_fail a failed test halts execution of following tests unless this is true
	 * @param next_unit_id the next test unit's ID number (-1 if last unit)
	 */
	public BatchTestUnit(
			BatchCommand[] pre_commands, 
			BatchCommand[] commands, 
			BatchCommand[] check_pass_commands, 
			BatchCommand[] pass_post_commands, 
			BatchCommand[] fail_post_commands,
			boolean continue_on_fail,
			int next_unit_id) {
		assert check_pass_commands != null;
		assert check_pass_commands.length > 0;
		
		if(pre_commands == null) 
			this._pre_commands = new BatchCommand[0];
		else
			this._pre_commands = pre_commands;
		
		if(commands == null)
			this._commands = new BatchCommand[0];
		else
			this._commands = commands;
		
		this._check_pass_commands = check_pass_commands;
		
		if(pass_post_commands == null)
			this._pass_post_commands = new BatchCommand[0];
		else
			this._pass_post_commands = pass_post_commands;
	
		if(fail_post_commands == null)
			this._fail_post_commands = new BatchCommand[0];
		else
			this._fail_post_commands = fail_post_commands;
		
		this._continue_on_fail = continue_on_fail;
		this._next_unit_id = next_unit_id;
	}
	
	/**
	 * This is a blocking call which will execute the BatchTestUnit.
	 * 
	 * @param results_controller the object needed to send results
	 * @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 submission the submission object.
	 * 
	 * @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
	 * 
	 * @return true if the test is complete, false if more units are to be processed.
	 */	
	public boolean execute(ClientController results_controller, String[] environment, 
			File working_directory, long max_directory_b, SubmissionObj submission) 
			throws TimeoutExceededException, MaxDirectorySizeExceededException, 
			IOException {
		assert results_controller != null;
		assert(working_directory.exists());
		assert(working_directory.isDirectory());
		assert(working_directory.canRead());
		assert(working_directory.canWrite());
		assert max_directory_b > 0;
		
		for(int i = 0; i < this._pre_commands.length; i++) {
			this._pre_commands[i].execute(environment, working_directory, 
					max_directory_b, BatchReturnType.exitCode);
		}
		
		for(int i = 0; i < this._commands.length; i++) {
			this._commands[i].execute(environment, working_directory, 
					max_directory_b, BatchReturnType.exitCode);
		}
		
		boolean passed = true;
		if(this._check_pass_commands != null) {
			for(int i = 0; i < this._check_pass_commands.length; i++) {
				passed = passed && ((Integer)this._check_pass_commands[i].execute(environment, 
						working_directory, max_directory_b, BatchReturnType.exitCode)).intValue() == 0;
			}
		}
		
		boolean complete = this._next_unit_id == -1 || (!passed && !this._continue_on_fail);
		
		TestUnitResults results = new TestUnitResults(
				submission,
				passed,
				complete, 
				this._next_unit_id
				);
		results_controller.post_unit_results(results);
		
		if(passed) {
			for(int i = 0; i < this._pass_post_commands.length; i++) {
				this._pass_post_commands[i].execute(environment, working_directory, max_directory_b, BatchReturnType.exitCode);
			}
		} else {
			for(int i = 0; i < this._fail_post_commands.length; i++) {
				this._fail_post_commands[i].execute(environment, working_directory, max_directory_b, BatchReturnType.exitCode);
			}
		}
		
		return complete;
	}
	
	/**
	 * @return total timeout for all commands in this unit (in ms)
	 */
	public long get_timeout_ms() {
		long timeout = 0;

		for(int i = 0; i < this._pre_commands.length; i++) {
			timeout += this._pre_commands[i].get_timeout_ms();
		}

		for(int i = 0; i < this._commands.length; i++) {
			timeout += this._commands[i].get_timeout_ms();
		}
		
		if(this._check_pass_commands != null) {
			for(int i = 0; i < this._check_pass_commands.length; i++) {
				timeout += this._check_pass_commands[i].get_timeout_ms();
			}
		}
		
		for(int i = 0; i < this._pass_post_commands.length; i++) {
			timeout += this._pass_post_commands[i].get_timeout_ms();
		}
		for(int i = 0; i < this._fail_post_commands.length; i++) {
			timeout += this._pass_post_commands[i].get_timeout_ms();
		}
		
		assert timeout > 0;
		return timeout;
	}
}
