<?php

namespace modules\output;

trait traits {
	protected $__OUTPUT;
	
	public function __construct($input = NULL) {
		if (!isset($this->__OUTPUT)) {
			if (!isset($GLOBALS['output_trait_info'])) {
				$GLOBALS['output_trait_info'] = new trait_info();
			}
			$this->__OUTPUT = $GLOBALS['output_trait_info'];
		}
		if ($input !== NULL) $this->__OUTPUT->LoadFromInput($input);
//		if ($config !== NULL) $this->__OUTPUT->LoadFromConfig($config);
	}
	
	public function redirect($loc = NULL, $force302 = FALSE) {
		$this->__OUTPUT->redirect($loc, $force302);
	}

	public function IncludeFile($filename, $library_identifier = '') {		// javascript or css,  returns an identifier
		return $this->__OUTPUT->IncludeFile($filename, $library_identifier);
	}

	public function IncludeLibrary($library_identifier) {
		return $this->__OUTPUT->IncludeLibrary($library_identifier);
	}
	
	public function RemoveItem($identifier) {
		return $this->__OUTPUT->RemoveItem($identifier);
	}
	
	protected function registerView($method_name, $view_structure = NULL, $module_name = NULL) {
		return $this->__OUTPUT->registerView($method_name, $view_structure, $module_name);	// requires PHP 5.4
	}
	protected function DefaultResult($method_name) {
		return $this->__OUTPUT->DefaultResult($method_name);
	}

	public function GetInclude($text = TRUE) {
		return $this->__OUTPUT->GetInclude($text);
	}

	protected function IncludeCSS($css) {
		return $this->__OUTPUT->IncludeCSS($css);
	}

	// Get or Set Content-Disposition Filename
	protected function target() {
		return call_user_func_array([$this->__OUTPUT, 'Target'], func_get_args());
	}
	
	// Get or Set Title
	public function title() {
		return call_user_func_array([$this->__OUTPUT, 'Title'], func_get_args());
	}

	// Get or Set Body
	public function body() {
		return call_user_func_array([$this->__OUTPUT, 'body'], func_get_args());
	}

	// Get or Set ContentType
	protected function OutputFormat() {
		return call_user_func_array([$this->__OUTPUT, 'OutputFormat'], func_get_args());
	}

	public function HttpStatus() {
		return call_user_func_array([$this->__OUTPUT, 'HttpStatus'], func_get_args());
	}
	
	protected function getJsExtension() {
		return $this->__OUTPUT->getJsExtension();
	}

	public function OutputSet($variable, $value) {
		return $this->__OUTPUT->OutputSet($variable, $value);
	}
	
	public function OutputGet($variable, $default = FALSE) {	// has a default
		return $this->__OUTPUT->OutputGet($variable, $default);
	}
	
	// what would be the public uri of the specified static folder within the module
	// $dirname must point to the folder containing the modules' primary index.php file
	public function StaticUriModule($dirname) {
		return $this->__OUTPUT->StaticUriModule($dirname);
	}
	
	public function IncludeTemplate($id) {
		return $this->__OUTPUT->IncludeTemplate($id);
	}
	
	public function loadView($module_folder, $name) {
		return $this->__OUTPUT->loadView($module_folder, $name);
	}
	
	public function Register_FormatHandler($extension, $callback) {
		return $this->__OUTPUT->Register_FormatHandler($extension, $callback);
	}
	
	public function hook_apply_late($caption) {
		return $this->__OUTPUT->hook_apply_late($caption);
	}
	
	public function hasLate() {
		return $this->__OUTPUT->hasLate();
	}
	
	public function favIcon() {
		return $this->__OUTPUT->favIcon();
	}
	
}

require_once __DIR__ . '/model/fileobject.php';

class trait_info {

// This has all the components needed for a html page,
//  it is then used by a template module to be formatted & displayed
	private $include;
	private $title;
	private $output_format;
	private $body = NULL;
	private $css = [];
	private $initialized = FALSE;
	private $storage = [];
	private $PHASE;
	public  $base_uri;
	private $formathandlers = [];
	private $target_filename = NULL;		// no special filename
	private $latehook = [];
	private $http_status;
	private $active_controller = FALSE;
	private $macros = [];

	public function __construct() {
		$this->include = array();
		$this->output_format = 'html';
		$this->PHASE = 0;
		$this->http_status = new \http_status(200);
		$this->settings = NULL;

	}
	
	public function hasLate() {
		return count($this->latehook) > 0;
	}
	
	public function LoadFromInput($input) {
		$this->hostname = $input->hostname();
		$this->accept_type = $input->accept_type;	// text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
		$this->accept_lang = $input->accept_lang;	// en-US,en;q=0.5
		$this->requested = $input->requested;		// X-Requested-With: XMLHttpRequest
		$this->base_uri = $input->BaseUri();
		
		if (strpos($input->requested, 'xml') !== FALSE) {		// X-Requested-With: XMLHttpRequest
			$this->output_format = 'xml';
		};
	}
	
	// is this production server? (use minimized js files if so)
	public function getJsExtension() {
		if (is_null($this->settings)) {
			$this->settings = $GLOBALS['loader']->LoadModuleSettings(__DIR__);
		}
		$servertype = $this->settings['mode'] ?? 'dev';
		return $servertype == 'prod' ? '.min.js' : '.js';
	}

	public function redirect($loc = NULL, $force302 = FALSE) {
		// if json then return json redirect request
		if ($loc === NULL && $force302 === FALSE) {
			hook_execute('error', 1, 'Null Redirect', __FILE__, __LINE__);
		} elseif ($this->output_format == 'json' && $force302 === FALSE) {
			$data = ['redirect' => $loc];
			$data = json_encode($data);
			header('Content-type: application/json; charset=utf-8');
			echo $data;
		} elseif (strpos($loc, '://') !== FALSE) {
			header('Location: '. $loc);
		} elseif (substr($loc, 0, 1) == '/') {
			header('Location: //'. $this->hostname . $loc);
		} else {
			header('Location: '. $loc);
		}
        if ($force302 !== FALSE) {
            echo $force302;
        }
		exit;
	}

	public function StaticUriModule($dirname) {
		$uri = $GLOBALS['loader']->GetStaticUri($dirname);
		return $uri;
	}

	public function EnumHandleBars() {
		$controller = $this->active_controller;
		$GLOBALS['loader']->LoadAllViews($controller, NULL);		// second parameter is unused 
	}

	public function registerView($method_name, $view_structure = NULL, $view_module = NULL) {
		$GLOBALS['loader']->LoadCurrentView($method_name, $view_structure, $view_module);
	}
	
	public function DefaultResult($method_name) {
		$this->active_controller = $GLOBALS['loader']->getControllerForMethod($method_name);

		// return the info we need for this action
		return ['error' => array(),
			'template' => '', 
			'page_title' => '',													// filled in by Model
		];
	}

	private function initializeTemplate($view) {
		if (method_exists($view, 'Init')) {
			$count = 100000;
			foreach($this->include as $hash => $item) {
				$item->SetTag($count); $count++;
			}
			$view->Init($this);		// this should add to the includes
			$count = 0;
			foreach($this->include as $hash => $item) {
				if (!$item->GetTag()) {
					$item->SetTag($count); $count++;
				}
			}
			// sort by tag order
			uasort($this->include, function($a, $b) {
				return $a->GetTag() - $b->GetTag();
			});
		}
	}

	private function _extractVersion($info) {
		if (preg_match('~(\d+)\.(\d+)\.(\d+)\.(\d+)~', $info, $match)) return $match[0];
		if (preg_match('~(\d+)\.(\d+)\.(\d+)~', $info, $match)) return $match[0];
		return '';
	}
	/*
		Different versions of the same library may be 'accidentally' requested. In such cases,
		we only want the latest version of the library. Version is extracted from the filename
	*/
	public function IncludeFile($filename, $library_identifier = '') {		// javascript or css,  returns an identifier
		$x = parse_url($filename, PHP_URL_PATH);
		$x = basename($x);
		if (strlen($x) < 5) $x = parse_url($filename, PHP_URL_PATH);		// basename('js') as an example
		$hash = md5($x);
		if ($library_identifier != '') {
			$version_current = $this->_extractVersion($filename);
			foreach($this->include as $_hash => $n) {
				if ($n->Library() == $library_identifier) {
					$hash = $_hash;
					$version_old = $this->_extractVersion($n->Filename());
					if (version_compare($version_current, $version_old) > 0) {
						$n->Filename($filename);		// update the filename to the new version
					}
					return $this->include[$hash];
				}
			}
		}
		
		if (!isset($this->include[$hash])) {
			$n = new \FileObject();
			$n->filename($filename)->Library($library_identifier);
			$this->include[$hash] = $n;
		}
		return $this->include[$hash];
	}
	
	public function IncludeLibrary($library_identifier) {
		foreach($this->include as $_hash => $n) {
			if ($n->Library() == $library_identifier) {
				return $this->include[$_hash];
			}
		}
		return NULL;
	}

	public function IncludeTemplate($identifier) {		// $identifier will be ID="..."
		if (!isset($this->include[$identifier])) {
			$n = new \FileObject('handlebars');
			$this->include[$identifier] = $n;
		}
		return $this->include[$identifier];
	}
	
	public function IncludeCSS($css) {
		$key = md5($css);
		$this->css[$key] = $css;
	}

	public function Title() {
		if (func_num_args()) {
			$this->title = func_get_arg(0);
			return $this;
		} else {
			return $this->title;
		}
	}

	public function Target() {
		if (func_num_args()) {
			$this->target_filename = func_get_arg(0);
			return $this;
		} else {
			return $this->target_filename;
		}
	}
	
	public function Body() {
		if (func_num_args()) {
			$this->body = func_get_arg(0);
			return $this;
		} else {
			return $this->body;
		}
	}
	
	public function OutputFormat() {
		if (func_num_args()) {
			$this->output_format = func_get_arg(0);
			return $this;
		} else {
			return $this->output_format;
		}
	}

	public function HttpStatus() {
		if (func_num_args()) {
			$x = func_get_arg(0);
			if (is_numeric($x)) $x = new \http_status($x);
			$this->http_status = $x;
			return $this;
		} else {
			return $this->http_status;
		}
	}
	
	public function OutputSet($variable, $value) {
		$this->storage[$variable] = $value;
	}
	public function __set($variable, $value) {
		$this->storage[$variable] = $value;
	}
	public function OutputGet($variable, $default = FALSE) {	// has a default
		if (array_key_exists($variable, $this->storage)) {
			return $this->storage[$variable];
		} else {
			return $default;
		}
	}

	public function __get($variable) {
		if (array_key_exists($variable, $this->storage)) {
			return $this->storage[$variable];
		} else {
			return FALSE;
		}
	}
	
	public function RemoveItem($identifier) {
		unset($this->include[$identifier]);
	}

	public function GetInclude($text = TRUE) {
		if (!$text) {
			$result = [];
			foreach($this->include as $hash => $item) {
				$node = $item->AsArray();
				$node['name'] = $hash;
				$result[] = $node;
			}
			return $result;
		}
		$dependency = new \DependencyManager();
		$css = $js = $handlebars = $autorun = '';
		$registered = [];
		$macros = '';
		foreach($this->include as $hash => $item) {
			if ($item->ext() == 'handlebars') {
				$macroname = $item->macro();
				if ($macroname && empty($registered[$macroname])) {
					$registered[$macroname] = 1;
					if ($macros != '') $macros .= "\n";
					$macros .= "\t\tregisterMacro('{$macroname}', '$hash');";
				}
				if ($this->hasLate()) {
					$item->ApplyLate([$this, 'hook_apply_late']);
				}
			}
		}
		$id = $this->includeFile('');
		$id->JavascriptCommand($macros);
		$includes = hook_execute('output.includes', $this->include);
		foreach($includes as $hash => $item) {
			switch($item->ext()) {
				case '':
					$autorun .= $item;
					break;
				case 'js':
					$dependency->Add($item);
					break;
				case 'css':
					$css .= $item;
					break;
				case 'less':
					$css .= $item;
					break;
				case 'ico':
				case 'png':
				case 'svg':
					$js .= $item;
					break;
				case 'handlebars':
					//to do:   add late hook execution to update embedded html
					$handlebars .= "<script id=\"$hash\" type=\"text/x-handlebars-template\">{$item}</script>\n";
					break;
			}
		}
		$list = $dependency->Resolve();
		foreach($list as $load_item) {
			$js .= $load_item;
		}
		if (count($this->css)) {
			$j = join('', $this->css);
			$css .= <<<BLOCK
<style type="text/css">
{$j}
</style>
BLOCK;
		} 
		return $css . $js . $handlebars . $autorun;
	}
	
	public function GetTitleFromTemplate($template) {
		$node = $this->GetTemplate($template);
		if ($node !== FALSE) {
			$title = $node->Title();
			if ($title != '') {
				$title = hook_execute('macro.expand', $title, FALSE);
				return $title;
			}
		}
		return "($template)";
	}
	
	public function GetTemplate($template) {
		if (isset($this->include[$template])) {
			return $this->include[$template];
		} else {
			return FALSE;
		}
	}

	public function Register_FormatHandler($extension, $callback) {
		$this->formathandlers[$extension] = $callback;
	}
	
	public function getRecognizedExtension($basename) {
		if (($p = strrpos($basename, '.')) === FALSE) {
			return [$basename, FALSE];
		}
		$ext = strtolower(substr($basename, $p + 1));
		if (!isset($this->formathandlers[$ext])) {
			return [$basename, FALSE];
		}
		return [substr($basename, 0, $p), $ext];
	}
	
	public function ExportHandler($data, $ext = '') {
		if ($ext == '') $ext = $this->output_format;
		$callback = $this->formathandlers[$ext];
		echo call_user_func($callback, $data);
	}
	
	public function loadView($module_folder, $name) {
		if (empty($module_folder)) $module_folder = HOME;
		$file = $module_folder .'/view/'. strtolower($name) .'.php';
		require_once $file;
		$view = new $name;
		return $view;
	}
	
	public function hook_execute_late($args) {
		$c = count($this->latehook);
		$this->latehook[$c] = $args;
		return "\x1f\x1f" . sprintf('%04x', $c);
	}
	
	private function _apply_macro($index) {
		$c = intval($index, 16);
		$args = $this->latehook[$c];
		return call_user_func_array('hook_execute', $args);
	}
	
	// apply late hooks
	public function hook_apply_late($caption) {
		$caption = preg_replace_callback('~\x1f\\x1f([0-9a-f]{4})~', function($matches) {
			$text = $matches[1];
			return $this->_apply_macro($text);
				}, $caption);
		return $caption;
		
	}

	// return html for the favicon, if present
	//  	<link type="image/x-icon" rel="icon" href="/favicon.ico">
	public function favIcon() {
		return hook_execute('html.favicon', '');
	}
}
