<?php

	use Dotink\Inkwell;
	use Dotink\Flourish;

	//
	// We want to ensure that if we run as root that we take on the groupid
	// and userid of our files.  This allows scaffolded files and written files
	// to maintain proper user and group permissions.
	//

	if (function_exists('posix_setgid')) {
		@posix_setgid(filegroup(__FILE__));
	}

	if (function_exists('posix_setuid')) {
		@posix_setuid(fileowner(__FILE__));
	}

	//
	// Set up our APPLICATION_ROOT, the console is always assumed to run in directly in it.
	//

	define('APPLICATION_ROOT', realpath(dirname(__FILE__)));

	//
	// Set up our default pseudo $_SERVER parameters
	//

	$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1';
	$_SERVER['SERVER_NAME']     = 'localhost';
	$_SERVER['SERVER_PORT']     = 80;

	$_SERVER['REQUEST_METHOD']  = 'GET';
	$_SERVER['REQUEST_URI']     = '/';

	$_SERVER['REMOTE_ADDR']     = '127.0.0.1';
	$_SERVER['DOCUMENT_ROOT']   = !isset($_ENV['DOCUMENT_ROOT'])
		? dirname(__FILE__) . DIRECTORY_SEPARATOR . 'public'
		: $_ENV['DOCUMENT_ROOT'];

	//
	// And these guys.
	//

	$_GET  = array();
	$_POST = array();

	//
	// Bootstrap inKWell.
	//

	$app = require implode(DIRECTORY_SEPARATOR, [
		APPLICATION_ROOT,
		'includes',
		'init.php'
	]);

	//
	// Set some basics regardless of configuration
	//

	App\Core::enableErrorHandling('html');
	App\Core::enableExceptionHandling('html');

	$___php = isset($_ENV['IW_PHP_CMD'])
		? $_ENV['IW_PHP_CMD']
		: 'php';

	@exec($___php . ' -v', $___garbage, $___error);

	if ($___error) {
		echo PHP_EOL;
		echo 'Please add the PHP executable to your path.';
		echo PHP_EOL;
		exit();
	}

	$___shell_args = array();
	$___settings   = array(
		'short_open_tag'  => '0',
		'asp_tags'        => '1',
		'error_reporting' => 'E_PARSE'
	);

	foreach ($___settings as $___setting => $___value) {
		$___shell_args[] = '-d ' . escapeshellarg($___setting . '=' . $___value);
	}

	$___php .= implode(' ', $___shell_args);

	if (isset($argv[1]) && trim($argv[1]) && $argv[1] != '!') {

		//
		// If the first argument looks like a file, just include it
		//

		if (is_file($argv[1]) && is_readable($argv[1])) {
			try {
				include $argv[1];
				exit(0);
			} catch (Exception $e) {
				echo 'Exception: ' . $e->getMessage();
				exit(1);
			}

		//
		// Otherwise, it is assumed to be a request URI with an optional request method
		// preceding it and separate by '::'
		//

		} else {
			$___request = explode('::', $argv[1], 2);

			//
			// First argument is request method and URI
			//

			if (count($___request) == 2) {
				$_SERVER['REQUEST_METHOD'] = strtoupper($___request[0]);
				$_SERVER['REQUEST_URI']    = $___request[1];

			} else {
				$_SERVER['REQUEST_URI'] = $___request[0];
			}


			//
			// Second argument is Accept header
			//

			if (isset($argv[2])) {
				$_SERVER['HTTP_ACCEPT'] = $argv[2];

			} else {
				$_SERVER['HTTP_ACCEPT'] = '*/*';
			}

			//
			// Third argument is a JSON string containing parameter/value information
			// for the request in JSON format.

			if (isset($argv[3])) {
				if ($_SERVER['REQUEST_METHOD'] == 'GET') {
					$_GET  = (new Flourish\JSON($argv[3]))->asArray();

				} else {
					$_POST = (new Flourish\JSON($argv[3]))->asArray();
				}
			}

			$status = require implode(DIRECTORY_SEPARATOR, ['.', 'includes', 'run.php']);

			exit($status);
		}
	}

	register_shutdown_function('___respawn');

	$___stdin     = fopen('php://stdin', 'r');
	$___stat      = fstat($___stdin);
	$___depth     = 0;
	$___code      = '\?';
	$___line      = 1;
	$___command   = '';
	$___silent    = FALSE;
	$___no_banner = FALSE;

	if (isset($app['databases'])) {
		$___database = ___use_db('default');
	}

	if (($___stat['mode'] & 0170000) !== 0020000) {
		$___silent      = TRUE;
		$___called_quit = TRUE;
		$___code        = NULL;
	}

	if (isset($argv[1]) && $argv[1] == '!') {
		$___no_banner = TRUE;
		$___code      = NULL;
	}

	if (!$___silent && !$___no_banner) {
		echo PHP_EOL;
		echo '       .__        ____  __.__      __       .__  .__   ' . PHP_EOL;
		echo '       |__| ____ |    |/ _/  \    /  \ ____ |  | |  |  ' . PHP_EOL;
		echo '       |  |/    \|      < \   \/\/   // __ \|  | |  |  ' . PHP_EOL;
		echo '       |  |   |  \    |  \ \        /\  ___/|  |_|  |__' . PHP_EOL;
		echo '       |__|___|  /____|__ \ \__/\  /  \___  >____/____/' . PHP_EOL;
		echo '               \/        \/      \/       \/           ' . PHP_EOL;
		echo PHP_EOL;
		echo 'Welcome to the inKWell Console, you can begin typing PHP or try:';
		echo PHP_EOL;
	}

	do {
		if (($___code = trim($___code)) && $___code[0] == '\\') {

			//
			// Handle escaped console commands
			//

			$___command = $___code;
			$___code    = NULL;

			switch (substr($___command, 0, 2)) {
				case '\q':
					$___called_quit = TRUE;

				case '\r':
					exit();

				case '\m':
					echo PHP_EOL;

					if (!App\Core::checkOS('windows')) {
						echo 'Entering non-interactive mode, press <ctrl>+D when done.' . PHP_EOL;
					} else {
						echo 'Entering non-interactive mode, press <ctrl>+Z when done.' . PHP_EOL;
					}

					echo PHP_EOL;

					$___multi_mode = TRUE;

					while ($___mline = fgets($___stdin)) {
						$___code = $___code . $___mline;
					}

					echo PHP_EOL;
					echo 'Output:' . PHP_EOL;
					echo PHP_EOL;
					break;

				case '\e':
					if (!($___exec = trim(substr($___command, 2)))) {
						echo 'Please enter a Command: ';
						$___exec = trim(fgets($___stdin));
					}
					passthru($___exec);
					break;

				case '\c':
					___clear_screen();
					break;

				case '\s':

					if ($___command[2] == 'm') {
						echo PHP_EOL;

						if (!App\Core::checkOS('windows')) {
							echo 'Entering non-interactive SQL mode, press <ctrl>+D when done.' . PHP_EOL;
						} else {
							echo 'Entering non-interactive SQL mode, press <ctrl>+Z when done.' . PHP_EOL;
						}

						echo PHP_EOL;

						$___multi_mode = TRUE;
						$___multi_sql  = TRUE;

						while ($___mline = fgets($___stdin)) {
							$___code = $___code . $___mline;
						}

						echo PHP_EOL;
						echo 'Output:' . PHP_EOL;
						echo PHP_EOL;
						break;

					}

					if (!($___sql = trim(substr($___command, 2)))) {
						echo 'Please enter your SQL: ';
						$___sql = trim(fgets($___stdin));
					}

					___run_query($___database, $___sql);
					break;

				case '\u':
					if (!($___db_name = trim(substr($___command, 2)))) {
						echo 'Enter the database name: ';
						$___db_name = trim(fgets($___stdin));

					}

					$___database = ___use_db($___db_name);
					break;

				case '\l':
					foreach ($app['databases'] as $___db_name => $___garbage) {
						echo PHP_EOL;
						echo '- ' . $___db_name . PHP_EOL;
						echo PHP_EOL;
					}
					break;

				case '\>':
					if (!($___directory = trim(substr($___command, 2)))) {
						echo 'Please enter a Directory: ';
						$___directory = trim(fgets($___stdin));
					}
					chdir(trim($___directory, '\'"'));
					break;

				case '\?':
					echo PHP_EOL;
					echo '\c - Clear the Screen'             . PHP_EOL;
					echo '\e - Execute a System Command'     . PHP_EOL;
					echo '\m - Enter Non-Interactive mode'   . PHP_EOL;
					echo '\q - Quit the Program'             . PHP_EOL;
					echo '\r - Reset the Program'            . PHP_EOL;

					if (isset($___database)) {
						echo '\s - Run SQL Statement'        . PHP_EOL;
						echo '\u - Select Database'          . PHP_EOL;
						echo '\l - List Databases'           . PHP_EOL;
					}

					echo '\> - Change Directory'             . PHP_EOL;
					echo '\? - Display this Message'         . PHP_EOL;
					echo PHP_EOL;
					break;
			}

			$___command = NULL;
		}


		if ($___code) {

			if (isset($___multi_sql)) {
				___run_query($___database, $___code);
				exit(0);
			}


			$___prev_depth = $___depth;

			foreach (token_get_all('<?php ' . $___code) as $___token) {
				if (!is_array($___token)) {
					switch ($___token) {
						case '{':
						case '(':
							$___depth++;
							break;
						case '}':
						case ')':
							$___depth--;
							break;
					}
				}
			}

			$___command  .= $___code . PHP_EOL;

			if ($___depth <= 0) {

				if ($___command && ___check($___command)) {

					ob_start();
					App\Core::startErrorCapture();

					try {
						eval($___command);
					} catch (Exception $___e) {
						echo 'Exception:  ' . $___e->getMessage();
					}

					$___output  = ob_get_clean();

					if(is_array($errors = App\Core::stopErrorCapture())) {
						foreach ($errors as $error) {
							echo $error['type'] . ':  ' . $error['string'] . PHP_EOL;
						}
					}

					if($___output) {
						echo $___output;
						if (!$___silent) {
							echo PHP_EOL;
						}
					}
				}

				$___depth   = 0;
				$___line    = 0;
				$___command = NULL;
			}

			$___line++;
		}

		if (!$___silent && !isset($___multi_mode)) {
			echo '[' . getcwd() . '][' . sprintf('%02s', $___line) . ']# ';
			flush();
		}

	} while(($___code = ___readline($___stdin)));


	/**
	 * A simple readline wrapper that allows us to get the user input via
	 * various methods.
	 *
	 * @param resource $handler A file pointer to the input
	 * @return string The user entered string
	 */
	function ___readline($handler)
	{
		return fgets($handler);
	}


	/**
	 * Register as a shutdown function to ensure that any fatal errors or
	 * uncaught exceptions do not terminate the shell.  This function
	 * will cause it to respawn itself on shutdown.  The only downside is
	 * that all previous variables and information are lost.
	 *
	 * @param void
	 * @return void
	 */
	function ___respawn()
	{
		if (!isset($GLOBALS['___called_quit'])) {
			echo ob_get_clean() . PHP_EOL;

			if (!isset($GLOBALS['___multi_mode'])) {
				echo 'Resetting...' . PHP_EOL;

				passthru(implode(' ', array(
					'php -q',
						'-d register_globals=0',
						'-d magic_quotes_gpc=0',
						'-d short_open_tag=0',
						'-d asp_tags=1',
						'-d display_errors=1',
						escapeshellarg(__FILE__)
				)));

			} else {
				passthru(implode(' ', array(
					'php -q',
						'-d register_globals=0',
						'-d magic_quotes_gpc=0',
						'-d short_open_tag=0',
						'-d asp_tags=1',
						'-d display_errors=1',
						escapeshellarg(__FILE__),
						'!'
				)));
			}


			exit(1);

		} else {
			echo PHP_EOL;

			exit(0);
		}
	}


	/**
	 * Checks syntax of a file and outputs where the errors are.
	 *
	 * @param string $file The file to check
	 * @return boolean TRUE if no errors exist, FALSE otherwise
	 */
	function ___verify($file)
	{
		if (App\Core::checkOS('windows')) {
			$syntax_check = trim(shell_exec(sprintf(
				$GLOBALS['___php'] . ' -l %s',
				escapeshellarg($file)
			)));
		} else {
			ob_start();

			passthru(sprintf(
				$GLOBALS['___php'] . ' -l %s 2>&1',
				escapeshellarg($file)
			), $return);

			$syntax_check = trim(ob_get_clean());
		}

		list($syntax_check) = preg_split('/[\r\n]+/', $syntax_check);

		if (stripos($syntax_check, 'Parse error') !== FALSE) {
			echo str_replace($file, 'parsed input', $syntax_check);
			echo PHP_EOL;
			return FALSE;
		}

		return TRUE;
	}


	/**
	 * Check validity of chunks of PHP source code based on advanced alien
	 * token logic stolen from the mothership by uploading a virus to their
	 * puter with a piece of fruit.
	 *
	 * @param string $string A string of PHP source code without an open tag
	 * @return boolean TRUE if code appears valid, FALSE otherwise
	 */
	function ___check($source)
	{
		$source        = '<?php ' . trim($source);
		$file          = APPLICATION_ROOT . '/.console.command';
		$current_class = NULL;

		file_put_contents($file, $source);

		if (!___verify($file)) {
			return FALSE;
		}

		// TODO: The logic for this should be completely written in a cleaner fashion.  Things
		// we would like to check for:
		//
		// - Non Existent Functions (must support namespaces)
		// - Non Existent Classes (must support namespaces)

		return TRUE;
	}


	/**
	 * Runs an SQL query on a provided database.  The function will dump
	 * the rows returned in the result using App\Core::expose();
	 *
	 * @param fDatabase $db The database object to run the query on
	 * @param string $sql The query to run
	 * @return void
	 */
	function ___run_query($db, $sql)
	{
		if (!$db) {
			echo PHP_EOL;
			echo 'Cannot run SQL query, database inaccessible'      . PHP_EOL;
			echo '  - Check your current configuration '            . PHP_EOL;
			echo '  - Select a different database using:'           . PHP_EOL;
			echo                                                      PHP_EOL;
			echo '   \u <database_name> [<database_role> = either]' . PHP_EOL;
			echo PHP_EOL;

		} else {
			try {
				$result = $db->query($sql);

				if ($result) {
					while ($record = $result->fetch()) {
						App\Core::expose($record);
					}
				}

			} catch (Exception $e) {
				echo 'Exception: ' . $e->getMessage();
				echo PHP_EOL;
			}
		}
	}


	/**
	 * Gracefully attempts to get and return a selected database
	 *
	 * @param string $database The name of the database
	 * @param string $role The role of the database, default 'either'
	 * @return fDatabase The fDatabase object if available, NULL otherwise
	 */
	function ___use_db($database)
	{
		global $app;

		if (is_array($database)) {
			if (isset($database[0])) {
				$database = $database[0];
			}

		} else {
			$database = (string) $database;
		}

		return isset($app['databases'][$database])
			? $app['databases'][$database]->getConnection()
			: NULL;
	}


	/**
	 * Clear the screen
	 *
	 * @param void
	 * @return void
	 */
	function ___clear_screen()
	{
		if (App\Core::checkOS('windows')) {

			//
			// TODO: Figure out a way to clear the screen on windows that actually works
			//

			system('cls');

		} else {
			passthru('clear');
		}
	}

