<?php
/**
 * coolTool Class
 *
 * @category  Web Applications
 * @package   coolTool
 * @author    Clint Zeringue <clint.zeringue@zmoddynamics.com>
 * @author    ZModDynamics LLC <info@zmoddynamics.com>
 * @copyright Copyright (c) 2014 ZModDynamics LLC, http://zmoddynamics.com
 * @license   http://zmoddynamics.com/pbDocs/ZModDynamicsLLC-End-User-Software-License-Agreement.doc
 * @version   0.2
 **/
	/*
		Copyright (c) 2014 ZModDynamics LLC, http://zmoddynamics.com
		Developer shall retain all copyright, patent, trade secret and other intellectual property rights Developer may have in anything 	
		created or developed by Developer for Customer under this Agreement ("Work Product"). Developer grants Customer a nontransferable 
		license to use the Work Product. The license is conditioned upon full payment of the compensation due Developer under this 
		Agreement.The license shall authorize Customer to:
			A.	Install the Software on computer systems owned, leased or otherwise controlled by Customer;
			B.	Utilize the Software for its internal data-processing purposes (but not for time-sharing or service bureau purposes) and 
			    copy the Software only as necessary to exercise the rights granted in this Agreement

		THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
		EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
		MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
		NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
		LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
		OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
		WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
	*/

	

	class coolTool{
		/**
		 * Class Version
		 * @var int
		 */
		const version = 0.02;
		

		/**
		 * @var array Array of errors text. Could use overwritten for multilingual support
		 */
		var $errorList = array(
			//Database Error while calling register functions
			1   => "New User Registration Failed",
			
		);
		
		/**
		 * @var Array of ini settings 
		 */
		 private $settings = false;
		
		
		
		/**
		 * @var array An array of Ec2 instance variables
		 */
		var $ctool_aws = array(
			"admin_email" => "",
			"admin_name" => "",
			"site_url" => "",
			"site_name" =>""
		);
	  
	  	/**
		 * @var string Current user id
		 */
		var $user_id;
		
		/**
		 * @var string Current User PHP Session ID
		 */
		var $sid;
		
		/**
		 * @var array Container to hold errors and reports
		 */
		var $console;

		 /**
		 * @var string Pointer for tracking errors and reports in the console
		 */
		protected $log;
		
		 /**
		 * @var object $sftp for constructing sftp calls
		 */
		 var $sftp;
		 /**
		 * @var object $ec2Client for constructing AWS EC2 calls
		 */
		 var $ec2Client;
		 /**
		 * @var int $aws_app_id the appid of this application
		 */
		 var $ec2State;
		 /**
		 * @var string $startTime holds the start time of the user's ec2Instance
		 */
		 var $ec2StartTime;
		 /**
		 * @var string $ec2State holds the current state of the user's ec2Instance
		 */
		 var $aws_app_id = 1;
		 /**
		 * @var object $db for constructing database calls
		 */
		 private $db;	 
		 
		 /**
		 * @var string $currentEc2InstanceId for the locked user instance
		 */
		 var $currentEc2InstanceId;
		 /**
		 * @var string $aws_instance_tbl name of aws instance table
		 */
		private $aws_instance_tbl = 'aws_instance';	
		/**
		 * @var string $aws_instance_tbl name of aws instance table
		 */
		private $uam_activity_tbl = 'usr_activity';				 
		
		/**
		 * @var string $userDirectory for storing user files.
		 */
		
		private $userDirectory = "usr";
		 
		 /**
		  * @var string $simInputFile current simulation input file 
		  */
		  var $simInputFile;
		  
		 /**
		  * @var string $simOutputFile current simulation output file 
		  */
		  var $simOutputFile;
		  
		  /**
		   * @var string $full_web_root dynamic webroot path to microcooltool web root
		   */ 
		  var $full_web_root; 
		  
		
		/*////////////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*\
		////////Protected and Secondary Methods below this line\\\\\\\\\\\\\
		 \*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\/////////////////////////////////////////////*/
		/**
		 * Object constructor
		 *
		 * @ignore
		 */
		function __construct($user_id='',$registry = null){
			
			set_error_handler(array($this, 'errorHandler'),E_USER_NOTICE);
			
			$this->logger("cooltool"); //Index for Reports and Errors;

			if(!isset($_SESSION) and !headers_sent()){
				session_start();
				$this->report("Session is being started...");
			}elseif(isset($_SESSION)){
				//$this->report("Session has already been started");
			}else{
				$this->error("Session could not be started");
				return false; //Finish Execution
			}
			
			//load other classes
			
			$web_root = get_include_path() . PATH_SEPARATOR . dirname(dirname(dirname(__FILE__)));
			//$this->full_web_root = substr(__DIR__, 0, strpos(__DIR__, 'html/')).'/html/microcooltool';
			//$this->full_web_root = '/home/wlv18548/public_html/microcooling.com/microcooltool';

//			$this->full_web_root = substr($_SERVER["DOCUMENT_ROOT"].'/microcooltool', strpos($_SERVER["DOCUMENT_ROOT"].'/microcooltool',"/home3"));
		$this->full_web_root = '/home3/microcoo/public_html/microcooltool';
			
			
			//echo $_SERVER["DOCUMENT_ROOT"].'/microcooltool';
			//echo '<br />';
			//echo $this->full_web_root;

			
			set_include_path(get_include_path() . PATH_SEPARATOR . $web_root .'/libs');
			include('PHP-MySQLi/MysqliDb.php'); 
			include('aws/aws-autoloader.php'); 
			$this->db = new Mysqlidb($this->getSetting('db','dns'), $this->getSetting('db','user'), $this->getSetting('db','pass'), $this->getSetting('db','name'));
			
			$aws = Aws\Common\Aws::factory($this->full_web_root.'/settings/aws-config.php');
		
			
			$this->ec2Client = $aws->get('ec2');
			
			
			
			$this->user_id = $user_id;
			$this->sid = session_id();


			return true;
		}
		
		
		
		
		public function getSetting( $section, $key, $default = null ) {
			
			if ( $this->settings === false ) {
				
            	$this->settings = parse_ini_file($this->full_web_root.'/settings/microcooltool.ini', true );
        	}
			foreach ( $this->settings[$section] as $_key => $_value ) {
				if ( $_key == $key ) return $_value;
			}
        	return $default;
   		 }
	
		public function typecastfromstr($arr) {
			foreach($arr as &$value) {
				if( strpos($value,'(bool)') !== false ) $value = (bool)strstr($value,'(bool)',true);
				if( strpos($value,'(int)') !== false ) $value = (int)strstr($value,'(int)',true);			
			}
		
			return $arr;
		}
		
		
		public function setSFTP($dnsName) {
			
				$success = false;
				set_include_path(get_include_path() . PATH_SEPARATOR . dirname(dirname(dirname(__FILE__))) .'/libs/phpseclib');
				include('Net/SFTP.php');
				include('Crypt/RSA.php');

				//define('NET_SFTP_LOGGING', NET_SFTP_LOG_COMPLEX);
				//define('NET_SSH2_LOGGING', NET_SSH2_LOG_COMPLEX)
				
				//create a new sftp instance
				
				$this->sftp = new Net_SFTP($dnsName,443,50);
				
				
				$ec2Key = new Crypt_RSA();
				if(!file_exists($this->getSetting('aws','pemPath'))) {
					$this->error("A security parameter is missing");
					return false;
				}
				
				$ec2Key->loadKey(file_get_contents($this->getSetting('aws','pemPath'),true));

				try{

					$success = $this->sftp->login($this->getSetting('aws','ec2User'), $ec2Key);

				} catch(Exception $e) {
					$msg = 'Exception Occured'. "\n";
					$msg.= json_encode($e->getMessage()) . "\n";
					$msg.= json_encode($this->sftp->getErrors()) . "\n";
					$msg.= json_encode($this->sftp->getSFTPErrors()) . "\n";

					$data = sprintf("%s\n", $msg);


					$this->error($msg);

					return false;

				}	

				if(!$success) {
					$msg = 'Could not login via sftp'. "\n";
					$msg.= json_encode($this->sftp->getErrors()) . "\n";
					$msg.= json_encode($this->sftp->getSFTPErrors()) . "\n";

					$data = sprintf("%s\n", $msg);


					$this->error($msg);
				}

				return $success;
				return $this->sftp->login($this->getSetting('aws','ec2User'), $ec2Key);

			
		}
		
		
		public function errorHandler($errno, $errstr, $errfile, $errline) {
    		//$this->error($errstr);
			$this->ec2State = "pendingssh";
			return false;
    		// Whatever you want to do.
		}
		
		
		/**
		 * builds and saves a .txt input file for use in Jackson's code
		 *
		 * @param array $data An associative array, containing array['sim_data'] ; holds simulation parameters
		 *
		 * @return bool for success or failure
		 */
		 
		public function bldInputFile($data = array()) {
			
			$this->report("Building input file.");
			
			if( !$this->validateInputFileData($data) ) return false;
			
			$filename = $this->getSetting('site','webroot') . $this->getSetting('site','sub_folder_real') . "/". $this->userDirectory ."/" . $this->user_id . "/";
		
			$dir =  $this->getSetting('site','webroot')  . $this->getSetting('site','sub_folder_real') . "/". $this->userDirectory ."/" . $this->user_id ;
			if (!file_exists($filename)) {
				//directory does not exist so create the directory
				$this->report("Creating user directory: " . $this->user_id);
				
    			if(!mkdir($dir , 0770,true)) {
					
					$lasterr = error_get_last();
					$this->error("Could not create user directory: $dir". "\n" . $lasterr['message']);
				
					return false;
				}
				
			}
		
	
			//directory exists so create the file
			$filename = $filename . uniqid('input_') . ".txt";
    		$this->report("Creating input file: " . $filename);
			
			
			//extra steps to convert units from mm to microns
            $data['sim_data']['fin_width'] = $data['sim_data']['fin_width']*1000;
			$data['sim_data']['fin_height'] = $data['sim_data']['fin_height']*1000;
			$data['sim_data']['channel_width'] = $data['sim_data']['channel_width']*1000;
			
			//extra steps to convert percentage to fraction
			$data['sim_data']['mixture_ratio'] = 1-$data['sim_data']['mixture_ratio']/100;
			
			//construct the data file
			$defaultSimFields = $this->getSetting('cooltool','defaultSimFields');
			
			//(1,1)
			$file_data .= $defaultSimFields['codeOwner'] . " ";
			//(1,2)
			$file_data .= $data['sim_data']['inlet_vol_flow'] . " ";
			//(1,3)
			$file_data .= $data['sim_data']['inlet_temperature'] . " ";
			//(1,4)
			$file_data .= $data['sim_data']['inlet_pressure'] . "\n";
			
			//(2,1)
			$file_data .= $data['sim_data']['material_cold_plate'] . " ";
			//(2,2)
			$file_data .= $data['sim_data']['fin_height'] . " ";
			//(2,3)
			$file_data .= $data['sim_data']['fin_width'] . " ";
			//(2,4)
			$file_data .= $data['sim_data']['channel_width'] . "\n";
			
			//(3,1)
			$file_data .= $data['sim_data']['material_heater'] . " ";
			//(3,2)
			$file_data .= $data['sim_data']['heated_length'] . " ";
			//(3,3)
			$file_data .= $data['sim_data']['heated_width'] . " ";
			//(3,4)
			$file_data .= $data['sim_data']['heated_thickness'] . "\n";
			
			//(4,1)
			$file_data .= $data['sim_data']['material_tim'] . " ";
			//(4,2)
			$file_data .= $data['sim_data']['base_thickness'] . " ";
			//(4,3)
			$file_data .= $data['sim_data']['tim_thickness'] . " ";
			//(4,4)
			$file_data .= $data['sim_data']['channel_roughness'] . "\n";
			
			//(5,1)
			$file_data .= $data['sim_data']['material_fluid'] . " ";
			//(5,2)
			$file_data .= $data['sim_data']['material_tim_conductivity'] . " ";
			//(5,3)
			$file_data .= $data['sim_data']['inlet_drop_coeff'] . " ";
			//(5,4)
			$file_data .= $data['sim_data']['outlet_drop_coeff'] . "\n";
			
			//(6,1)
			$file_data .= $data['sim_data']['bc_type_heat_footprint'] . " ";
			//(6,2)
			$file_data .= $data['sim_data']['mixture_ratio'] . " ";
			//(6,3)
			$file_data .= $data['sim_data']['bc_val_heat_footprint'] . " ";
			//(6,4)
			$file_data .= $defaultSimFields['simType'] . "\n";
			
			//(7,1)
			$file_data .= $defaultSimFields['parametricParameter']. " ";
			//(7,2)
			$file_data .= $defaultSimFields['minVolFlow'] . " ";
			//(7,3)
			$file_data .= $defaultSimFields['maxVolFlow'] . " ";
			//(7,4)
			$file_data .= $defaultSimFields['VolFlowIncrement'] . "\n";
			
			//(8,1)
			$file_data .= $defaultSimFields['defaultOutputFileName']. " ";
			//(8,2)
			$file_data .= $defaultSimFields['outputFileType']. " ";
			//(8,3)
			$file_data .= $data['sim_data']['fin_type']. " ";
			//(8,4)
			$file_data .= $data['sim_data']['fin_options'];
			
			
			
			
			// Write the contents back to the file
			if(file_put_contents($filename, $file_data, FILE_APPEND | LOCK_EX) === false) {
				
				$this->error("Could not create/write to file input file: $filename");
				return false;
				
			} else {
				$this->simInputFile = $filename;
			}
				
			return $this->has_error() ? false : true;	
			
			
		}
		
		
		
		/**
 		* Get all values from specific key in a multidimensional array
 		*
 		* @param $key string
 		* @param $arr array
 		* @return null|string|array
 		*/
		private function array_value_recursive($key, array $arr){
    		$val = array();
    		array_walk_recursive($arr, function($v, $k) use($key, &$val){
        		if($k == $key) array_push($val, $v);
   		 	});
   		 return $val;
		}
		
		public function unlockInactiveUserEc2Instances() {


			if(!$this->user_id == 'cron') 
				return;
			
			$this->report("Checking for Inactive Users.");
			
			//get all users which have not been pinged in more than 5 minutes and are not logged out
			$nowMinus5min = date('Y-m-d H:i:s',strtotime("-5 minute"));
			
			
			//$params = array($this->sid,$nowMinus5min);

			//$results = $this->db->rawQuery("SELECT user_id FROM ".$this->uam_activity_tbl." WHERE logout_time IS NULL AND last_ping_time <= ?", $params);	
			//$results = $this->db->rawQuery("SELECT user_id FROM ".$this->uam_activity_tbl." WHERE session_id = ? AND last_ping_time <= ?", $params);

			$params = array($nowMinus5min);

			$results = $this->db->rawQuery("SELECT user_id FROM ".$this->uam_activity_tbl." WHERE last_ping_time <= ?", $params);

			
			if (empty($results) || !$results) {
				//nothing to do
				$this->report("No inactive users.");
				return true;
			}
		
			//implode for use in multiple update SQL statement
			$userIds = implode(',',$this->array_value_recursive('user_id',$results));
			$this->report("Unlocking all locked instances associated with inactive users.");
			//unlock all unterminated instances associated with these users
			$this->db->rawQuery("UPDATE ".$this->aws_instance_tbl." SET is_locked = 0 WHERE is_terminated = 0 AND is_locked = 1 AND owner_id IN ({$userIds})");	
			return true;
			
		}
		
		
		
		public function unlockUserEc2Instance() {
			
			//get all locked instances associated with this user
			$results = $this->db
    			->where('is_terminated', 0)
				->where('is_locked', 1)
    			->where('owner_id', $this->user_id)
    			->get($this->aws_instance_tbl);
			
			
			if (empty($results)) {
				//nothing to do
				return true;
			}
			//mark these instances as unlocked
			foreach($results  as $result) {
				
				$updateData = array(
    				'is_locked' => 0,
				);
				$this->db->where('id', $result['id']);
				$this->db->update($this->aws_instance_tbl, $updateData);
			}
			return true;	
			
			
			
		}
		public function unlockUserEc2InstanceById($instanceId='') {
			
			//get all locked instances associated with this user
			$results = $this->db
    			->where('is_terminated', 0)
				->where('is_locked', 1)
    			->where('owner_id', $this->user_id)
				->where('instance_id',$instanceId)
    			->get($this->aws_instance_tbl);
			
			
			if (empty($results)) {
				//nothing to do
				return true;
			}
			//mark these instances as unlocked
			foreach($results  as $result) {
				
				$updateData = array(
    				'is_locked' => 0,
				);
				$this->db->where('id', $result['id']);
				$this->db->update($this->aws_instance_tbl, $updateData);
			}
			return true;	
			
			
			
		}
		
		
	
		public function checkEc2Ready() {
			
			$this->report("Checking for availible Ec2 Instances");

			//ensure the Ec2Instance is running or can be launched
			if(!$this->runEc2Instance()) {
					$this->ec2State ='unknown';
					$this->error('An unknown error occured while searching for an HPC Resource. Please try again');
					return false;
			}
			
			//user has a locked instance so lets get the instanceId
			$results = $this->db
    			->where('is_terminated', 0)
				->where('is_locked', 1)
    			->where('owner_id', $this->user_id)
    			->get($this->aws_instance_tbl);
			
			if (empty($results)) {
				$this->ec2State ='unknown';
				$this->error('An unknown error occured while searching for an HPC Resource. Please try again');
					return false;
			}
			
			
			$this->ec2StartTime = $results[0]['launch_time'];
			$instanceId = $results[0]['instance_id'];
			

			//return the Ec2Instance if it is running/pending
			$instances = $this->returnRunningEc2Instances(array($instanceId));
			
			if( $instances === false) {
				//could not describe this instance so return an error & unlock it from the user
				$this->ec2State ='unknown';
				$this->error("An unknown error occured while describing the HPC Resource. Please try again.");
				$this->unlockUserEc2InstanceById($instanceId);
				//initialize a new instance if needed
				$this->runEc2Instance();
				return false;
			}
			
			if ( $instances[0]['State']['Name'] == 'pending')  {
				
				$this->ec2State ='pending';			
				$this->error('The system resource is initializing. Please try again in a few minutes.');
				return false;
				
			}
			
			
			//check that this property is set before trying to make calls
			if (!isset($instances[0]['PublicDnsName']) || empty($instances[0]['PublicDnsName'])) {
				
				//could not describe this instance so return an error & unlock it from the user
				$this->unlockUserEc2InstanceById($instanceId);
				//initialize a new instance if needed
				$this->runEc2Instance();
				$this->ec2State ='unavailable';
				$this->error('No DNS found. Please try again.');
				return false;
			}
			
			
			//Check the health status of the instance. When Ec2 Launches an instance it runs a health check status and we won't be able to connect to it until the health status is complete. This process can take up to 5 minutes. Before going further, lets return an error message to the user to let them know that the instance is currently perfrom in health check and for them to try again in a few minutes. 
			
			$status = $this->returnEc2Status($instanceId);
			
			
			if($status === false) {
				$this->ec2State ='starting-up';
				$this->error('The system resource is loading. Please try again in a few minutes.');
				return false;
				
			}
			
			if( $status[0]['SystemStatus']['Status'] == 'initializing') {
				
				$this->ec2State ='initializing';
				$this->error('The system resource is performing a status check. Please try again in a few minutes.');
				return false;
			}
			if( $status[0]['SystemStatus']['Status'] !== 'ok') {
				
				//could not describe this instance so return an error & unlock it from the user
				$this->unlockUserEc2InstanceById($instanceId);
				//initialize a new instance if needed
				$this->runEc2Instance();
				$this->ec2State ='impaired';
				$this->error('The system resource is impaired. If this problem persists please contact the system administer.');
				//initialize a new instance if needed
				return false;
			}
			
		
			
			$start_time = time();
			
			//update the last_used time in the aws_instance table
			$updateData = array(
    						'is_locked' => 1,
    						'owner_id' => $this->user_id,
							'last_used' => date('Y-m-d H:i:s')
						);
			$this->db->where('id', $results[0]['id']);
			$this->db->update($this->aws_instance_tbl, $updateData);
			
			
			//check that the pem exists
			if(!file_exists($this->getSetting('aws','pemPath'))) {
					$this->error("A security parameter is missing");
					return false;
			}
		
			
			//Above check is deprecated. We use this instead
			if (!$this->setSFTP($instances[0]['PublicDnsName'])) {
				$this->ec2State ='pendingssh';
				//$this->error('Could not connect to HPC DNS. Please try again.');
					return false;
			}
			$this->ec2State =$status[0]['SystemStatus']['Status'];
			return true;
			
			
		}
		
		
		public function returnEc2Status($instanceIds='') {
			
			try {
    			$results = $this->ec2Client->DescribeInstanceStatus(
									array('InstanceId' => $instanceIds)
								);
			} catch (\Aws\Ec2\Exception\Ec2Exception $e) {
   			    // The instance could not be described
			 	$this->error($e->getMessage());
				return false;
			}
		
			if( !isset($results['InstanceStatuses']) || empty($results['InstanceStatuses']) ) {
				$this->error('The instance: '.$instanceIds.' status could not be described');
				return false;
			}
		
			return $results['InstanceStatuses'];	

		}
		
		
		public function updateLastPingTime() {
			
			$updateData = array(
    						'last_ping_time' => date("Y-m-d H:i:s")
						);
			$this->db->where('session_id', $this->sid);
			return $this->db->update($this->uam_activity_tbl, $updateData);
				
		}
		public function updateLogoutTime() {
			
			$updateData = array(
    						'logout_time' => date("Y-m-d H:i:s")
						);
			$this->db->where('session_id', $this->sid);
			return $this->db->update($this->uam_activity_tbl, $updateData);
				
		}
		
		public function terminateUnusedEc2Instances() {


			if(!$this->user_id == 'cron') 
				return;

			
			$this->report("Terminating all unused Ec2Instances.");
			//get all instances which have not been used in more than 54 minutes
			$nowMinus54min = date('Y-m-d H:i:s',strtotime("-54 minute"));
			
			$params = array($nowMinus54min,0);
	
			$results = $this->db->rawQuery("SELECT * FROM ".$this->aws_instance_tbl." WHERE last_used <= ? AND is_terminated = ?", $params);


			if (empty($results) || !$results) {
				//nothing to do
				$this->report("No Instances to terminate.");
				return true;
			}
			//return an array of instance id's from the $results array
			$instanceIds = $this->array_value_recursive('instance_id',$results);	
			//in order to terminate a list of instances, each instance must exist. Remove those instances which do not exist according to AWS.
			//update the status of the others which are obviously terminated
		
			$awsRunningInstances = $this->returnRunningEc2Instances($instanceIds);

			if (empty($awsRunningInstances) || !$awsRunningInstances) {
                //nothing to do
                $this->report("No running instances to terminate.");
                return true;
            }



			$awsInstanceIds = $this->array_value_recursive('InstanceId',$awsRunningInstances);


			//retrieve those in our database which are no longer running on AWS but don't have their status set to terminated.
			$otherInstanceIds = array_diff($instanceIds,$awsInstanceIds);

			//Based on these, update the status to terminated since these are no longer running but we missed them during the cron job
			foreach($otherInstanceIds as $instanceId) {
				$updateData = array(
    					'is_terminated' => 1,
						'termination_time' => date('Y-m-d H:i:s'),
						'is_locked' => 0
					);
				$this->db->where('instance_id', $instanceId);
				$this->db->update($this->aws_instance_tbl, $updateData);
					
				$this->report("Ec2 Instance: ".$instanceId." is no longer running according to AWS so the status in db was updated to terminated during this cron run.");

			}

			
			//retrieve only those values of $instanceIds that are in the running state according to AWS
			$instanceIds = array_intersect($instanceIds,$awsInstanceIds);

		
			
			try {
    			 $res = $this->ec2Client->terminateInstances(array(
    					// InstanceIds is required
    					'InstanceIds' => $instanceIds,
					));
				} catch (\Aws\Ec2\Exception\Ec2Exception $e) {
   			    	// The instance could not be described
			 		$this->error($e->getMessage());
					return false;
				
			}
			
			
			//Based on the results receieved from AWS mark these instances as terminated 
			foreach($res['TerminatingInstances'] as $result) {

				if($result['CurrentState']['Name'] == 'shutting-down' || $result['CurrentState']['Name'] == 'terminated' ) {
					
					$updateData = array(
    					'is_terminated' => 1,
						'termination_time' => date('Y-m-d H:i:s')
					);
					$this->db->where('instance_id', $result['InstanceId']);
					$this->db->update($this->aws_instance_tbl, $updateData);
					
					$this->report("Ec2 Instance: ".$result['InstanceId']." was terminated.");
					
				}
				
			}
			
			return true;	
			
			
		}
		
		
		public function runEc2Instance() {
			
			$this->report("Checking for unallocated Ec2Instances.");
			
			//we will also limit the number of Ec2Instances a user can have to be one
			$results = $this->db
    			->where('is_terminated', 0)
				->where('is_locked', 1)
    			->where('owner_id', $this->user_id)
    			->get($this->aws_instance_tbl);
				
				
			if (!empty($results)) {
				//this user has a locked unterminated instance so lets check if AWS recognizes it
				foreach($results as $result) {
	
					if ( $this->checkRunningEc2Instance($result['instance_id']) === true ) {
						//this instance is running or pending according to AWS
						$this->report('Instance: '.$result['instance_id'].' is running or pending');
						
						
						
						return true;
					}
					
				}
				//this means AWS reports that none of the above are running so we will need to create a new instance for this user
				return $this->createNewEc2Instance();
				
			}
			
			//before running an instance we should check if there are any unlocked,unterminated instances
			$results = $this->db
    			->where('is_terminated', 0)
    			->where('is_locked', 0)
    			->get($this->aws_instance_tbl);
				
			if (!empty($results)) {
				//there are unterminated, unlocked instances availible so check if aws still has this instance running/pending and lock to user
				foreach($results as $result) {
	
					if ( $this->checkRunningEc2Instance($result['instance_id']) === true ) {
						//this instance is running/pending according to AWS
						$this->report('Instance: '.$result['instance_id'].' is running/pending');
						//the instance is availible and running so lock it to the current user
						if(!$this->lockEc2Instance($result['id'])) {
							$this->error("Unknown Error attempting to lock the HPC instance.");	
							return false;
						} 
						//otherwise we were able to lock the instance so just return true
						
						return true;
					}
					
				}
				//this AWS reports that none of the above are running so we will need to create a new instance for this user
				return $this->createNewEc2Instance();
				
			} else {
				//this means nothing was availible so lets create a new EC2 Instance
				return $this->createNewEc2Instance();
				
			}
				
		}
		
		private function createNewEc2Instance() {
			
			$ec2InstanceDefaults = $this->typecastfromstr($this->getSetting('aws','ec2InstanceDefaults')) + 
								   array('SecurityGroups' => $this->typecastfromstr($this->getSetting('aws','SecurityGroups'))) + 
								   array('Placement' => $this->typecastfromstr($this->getSetting('aws','Placement'))) + 
								   array('Monitoring' => $this->typecastfromstr($this->getSetting('aws','Monitoring')));
			
			
			//no availible unallocated resources.. create a new EC2 instance and lock to the user
				try {
    				$result = $this->ec2Client->runInstances($ec2InstanceDefaults);
				} catch (\Aws\Ec2\Exception\Ec2Exception $e) {
   			    	// The instance could not be described
			 		$this->error($e->getMessage());
					return false;
				
				}
				
				
				if( empty($result['Instances']) ) {
					$this->error('Unknown Error creating HPC Instance. Please try again');
					return false;
				}
				
				// (by default only 1 instance is launched when this command is called
				//Insert Ec2Instance into db
				$running_instance = $result['Instances'][0];
				if( !$this->insertEc2Instance($running_instance) ) {
					$this->error('Unkown Error saving HPC Instance to db. Please try again.');
					return false;
				}
				if( !$this->lockEc2Instance($this->db->getInsertId()) ) {
					
					$this->error("Unknown Error attempting to lock the HPC instance.");	
					return false;
					
				}
				
				if(!$this->has_error) {
					$this->report('Success, instance started.');
				}
				//sucessfull so save the instance to the session
				return $this->has_error() ? false : true;		
			
			
		}
		
		
		public function insertEc2Instance($instance = null) {
			
			$launchTime = date("Y-m-d H:i:s");
			$insertData = array(
    			 'instance_id' => $instance['InstanceId'],
   				 'instance_type' => $instance['InstanceType'],
				 'app_id' => $this->getSetting('aws','app_id'),
				 'launch_time' => $launchTime,
				 'last_used' => $launchTime,
				 'owner_id' => $this->user_id
			);
			
			$this->ec2StartTime = $launchTime;
			
			return $this->db->insert($this->aws_instance_tbl, $insertData);
			
		}
		

		public function lockEc2Instance($db_record_id = null) {
	
			
			$updateData = array(
    						'is_locked' => 1,
    						'owner_id' => $this->user_id
						);
			$this->db->where('id', $db_record_id);
			
			return $this->db->update($this->aws_instance_tbl, $updateData);
			
		}
		
		public function returnRunningEc2Instances($instanceIds=array()) {
			
			$runningIds = array();
			
			try {
    			$results = $this->ec2Client->DescribeInstances(
									array('Filters' => array(
										array('Name' => 'instance-id', 'Values' => $instanceIds))
									)
								);
			} catch (\Aws\Ec2\Exception\Ec2Exception $e) {
   			    // The instance could not be described
			 	$this->error($e->getMessage());
				return false;
			}
			
			
			// if no matches then return false
			$reservations = $results['Reservations'];
			if(empty($reservations)) return false;
		
		
		
			//if matches check to make sure the instance is in a running/pending state
			foreach ($reservations as $reservation) {
    			$instances = $reservation['Instances'];
				foreach ($instances as $instance) {
					if ($instance['State']['Name'] === 'running' || $instance['State']['Name'] === 'pending') {
						//instance is in running state 
						$runningIds[] = $instance;
						
					}
				}
					
			}
			if (empty($runningIds)) return false;
			
			return $runningIds;
			
		}	
		
		
		
		public function checkRunningEc2Instance($instanceId='') {
			
			try {
    			$results = $this->ec2Client->DescribeInstances(
									array('Filters' => array(
										array('Name' => 'instance-id', 'Values' => array($instanceId)))
									)
								);
			} catch (\Aws\Ec2\Exception\Ec2Exception $e) {
   			    // The instance could not be described
			 	$this->error($e->getMessage());
				return false;
			}
			
			// if no matches then return false
			$reservations = $results['Reservations'];
			
			if(empty($reservations)) return false;
		
		
			//if matches check to make sure the instance is in a running state
			foreach ($reservations as $reservation) {
    			$instances = $reservation['Instances'];
				foreach ($instances as $instance) {
					if ($instance['State']['Name'] === 'running' || $instance['State']['Name'] === 'pending') {
						//instance is in running/pending state 
						return true;
					}
					
					if ($instance['State']['Name'] === 'terminated') {
						//this instance has been terminated but maybe not according to the instance db. So lets up-date the db to reflect the termination status.
						$this->report("Updating record with instance_id: ". $instanceId. " in ". $this->aws_instance_tbl . " to termination status." );
						$updateData = array(
    						'is_terminated' => 1,
							'termination_time' => date('Y-m-d H:i:s')
						);
						$this->db->where('instance_id', $instanceId);
						$this->db->update($this->aws_instance_tbl, $updateData);
					}
				}
			}
			
		return false;
			
			
		}
		
		
		public function runCoolTool() {
			
			$this->report("Checking if input file exists in session.");
			
			// check for input file in the session data
			if( empty($_SESSION['sim_data']['simInputFile']) || !isset($_SESSION['sim_data']['simInputFile']) ) {
			
					$this->error("No sim input file exists for this session." );
					return false;	
			}
			
			// check if the input file exists on local drive
			if (!file_exists($_SESSION['sim_data']['simInputFile'])) {
				
					$this->error("The sim input file was not found on disk." );
					return false;	
			}
			
			$this->report("Logging into remote server.");
			
			
			//determine if the Ec2 server is ready and login
			if( !$this->checkEc2Ready() ) {
				
				
				return false;
				
			}
				
		
			$file_structure = explode('/',$_SESSION['sim_data']['simInputFile']);
			$file_name = $file_structure[sizeof($file_structure)-1];
			
			$this->report("Transferring simulation input file.");
			
			// transfer the file to cooltool server
			if( !$this->sftp->put($file_name, $_SESSION['sim_data']['simInputFile'], NET_SFTP_LOCAL_FILE) ) {
			
				$this->error("Sim input file could not be transferred." );
				return false;	
			}
			
			// make sure the file exists after transferring
			if( !$this->sftp->touch($file_name) ) {
			
				$this->error("Sim input file was transferred but could not be found." );
				return false;	
			}
					
			// run the cooltool solver
			$this->report("Logging into remote server.");
			
			//define output file with same name as input file but with _out.txt appended
			$file_name_out = str_replace('.txt','_out.txt',$file_name);
			
			
			$cmd = './'.$this->getSetting('cooltool','mainShellScript').' '.$this->getSetting('cooltool','mcr_path_v80').' '.$file_name.' '.$file_name_out;
			stream_set_timeout($this->sftp->fsock, 99999);
			$this->sftp->exec($cmd);

			$this->report('Executing command: '.$cmd);

			#stream_set_timeout($this->sftp->fsock, 99999);
			#$this->sftp->exec($cmd);
			
			// make sure the output file exists after running the cooltool algorithm
			if( !$this->sftp->touch($file_name_out) ) {
				
				$this->error("No output file created by CoolTool Solver." );
				return false;
					
			} else {
				$this->report('Output File Location '.$file_name_out);
			}
			
			// save the output file name to session for future use
			$_SESSION['sim_data']['simOutputFile'] = $file_name_out;
			
			return true;
			

	
		}
		public function processOutputFile() {
			
		
			// check for output file in the session data
			$this->report("Checking if output file exists.");
			
			if( empty($_SESSION['sim_data']['simOutputFile'] ) || !isset($_SESSION['sim_data']['simOutputFile'] ) ) {
			
					$this->error("No sim output file exists for this session." );
					return false;	
			}
			

			// make sure the output file exists after running the cooltool algorithm
			$file_name_out = $_SESSION['sim_data']['simOutputFile'];
			
			$this->report("Logging into remote server.");
			//determine if the Ec2 server is ready and login
			if( !$this->checkEc2Ready() ) return false;
			
			if( !$this->sftp->touch($file_name_out) ) {
				
				$this->error("No output file found." );
				return false;
					
			}
			// process the output file to return json encoded string of needed output and store in session
			$cmd = './'.$this->getSetting('cooltool','outputShellScript').' '.$this->getSetting('cooltool','mcr_path_v91').' '.
			'filepath'.' '.$file_name_out.' '.'Xc'.' '.$_SESSION['sim_data']['geom']['heated_width'];
			

			$this->report('Executing command: '.$cmd);


			stream_set_timeout($this->sftp->fsock, 99999);
			
			$out = $this->sftp->exec($cmd);


			
			
			if(!$out) {
				
				$this->error("An error occured parsing the output file." );
				return false;
			}

			
			
			// store the output json string into array inside php session
			$_SESSION['sim_data']['simOutput'] = json_decode(substr($out, strpos($out, "{")),true);
			$this->report('Output: '.$out);
	
			//$this->report('SimOutputJsonString: '.substr($out, strpos($out, "{")));
			//$debug = var_export(json_decode(substr($out, strpos($out, "{")),true), true);
			//$this->report('SimDataJsonDecoded: '.$debug);
			//$debug2 = var_export($_SESSION['sim_data']['simOutput'], true);
			//$this->report('SessionData: '.$debug2);
				


			return true;
			
			
		}
		public function getSimResults() {
			
				$this->report("Retrieving output results");
				
				if( empty( $_SESSION['sim_data']['simOutput']) || !isset($_SESSION['sim_data']['simOutput']) ) {
			
					$this->error("No Data to Display. " );
					return false;	
				}
				
				return  $_SESSION['sim_data']['simOutput'];
			
		}
		
		
		public function validateInputFileData($data = array()) {
			
			$this->report("Validating input file data.");
			
			foreach($this->getSetting('cooltool','requiredSimFields')  as $key){
				
				if ( !array_key_exists($key, $data['sim_data']) ) {
					
					$this->error("Field:  $key could not be found in input array.");
					break;
				}
				
			}
			
			return $this->has_error() ? false : true;

			
		}
		
		/**
		 * Check if there is an error in the current logger
		 *
		 * @param bool|string $index Stack name to look for errors
		 *
		 * @return bool
		 */
		function has_error($index = false){
			//Check for errors
			$index = $index?$index:$this->log;
			$count = @count($this->console['errors'][$index]);
			if($count){
				$this->report("$count Error(s) Found!");
				return true;
			}else{
				$this->report("No Error Found!");
				return false;
			}
		}

		
		


		/**
		 * Add a report log entry
		 *
		 * @param string|bool $str Text information, default is false
		 *
		 * @return bool|mixed[] If $str is false returns the array of reports in the current logger
		 */
		function report($str = false){
			$index = $this->log;
			if($str){
				if(is_string($str))
					$str = ucfirst($str);

				$this->console['reports'][$index][] = $str; //Store Report
				$this->writeToActivityLog($str);
				return true;
			}else{
				if($index){
					return $this->console['reports'][$index]; //Return the $index Reports Array
				}else{
					return $this->console['reports']; //Return the Full Reports Array
				}
			}

		}
		
		/**
		 * Log the type of request being initialized and log with the error amd report methods
		 *
		 * @param string $log Request name
		 * @return object Self $this object
		 */
		function logger($log){
			$this->log = $log;
			unset($this->console['errors'][$log]);
			unset($this->console['form'][$log]);
			$this->report("Starting new $log request");
			return $this;
		}
		
		/**
		 * Add an error log entry
		 *
		 * @param string|bool $str Text information, default is false
		 * @return bool|array If $str is false returns the array of errors in the current logger
		 */
		function error($str = false){
			$index = $this->log;
			if($str){
				$err = is_int($str) ? $this->errorList[$str] : $str;
				$this->console['errors'][$index][] = $err; //Store Error

				if(is_int($str)){
					$this->report("Error[{$str}]: {$err}"); //Report The error
				}else{
					$this->report("Error: {$str}"); //Report The error
				}
			}else{
				if($index){
					if(!isset($this->console['errors'][$index]))
						return false;
					return $this->console['errors'][$index]; //Return the $index Errors Array
				}else{
					return $this->console['errors']; //Return the Full Error Array
				}
			}
			return false;
		}

		/**
		 * Write to activity log file
		 *
		 * @param null
		 * @return void
		 */
		function writeToActivityLog($str='') {

			return;

			$this->full_web_root = substr($_SERVER["DOCUMENT_ROOT"].'/microcooltool', strpos($_SERVER["DOCUMENT_ROOT"].'/microcooltool',"/home"));


			if( !file_exists ( $this->full_web_root.'/cooltool_activity.log') )
				file_put_contents($this->full_web_root.'/cooltool_activity.log','');

			//$logs = $this->array_flatten($this->console['reports']);

			file_put_contents($this->full_web_root.'/cooltool_activity.log', $str." ".date('Y-m-d H:i:s')."\n", FILE_APPEND);
			
		}

		function array_flatten($array) {
    		$return = array();
    		foreach ($array as $key => $value) {
        		if (is_array($value)){
            		$return = array_merge($return, $this->array_flatten($value));
        		} else {
            		$return[$key] = $value;
        		}
    		}

    		return $return;
		}


		
		
		

		
	}
?>