<?php

// what controller/method is going to process this uri?

class Destination {
	
	private $uri;
	private $input;
	private $output;
	private $permissions;
	public  $segments;
	private $info;
	private $routes = [];
	private $level = 0;

	public function __construct() {
		$this->level = $GLOBALS['loader']->UserLevel();
		$this->permissions = $GLOBALS['loader']->permissions;
	}
	
	public function uri() {
		if (func_num_args()) {
			$this->uri = func_get_arg(0);
			return $this;
		}
		return $this->uri;
	}

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

	public function output() {
		if (func_num_args()) {
			$this->output = func_get_arg(0);
			return $this;
		}
		return $this->input;
	}

	public function permissions() {
		if (func_num_args()) {
			$this->permissions = func_get_arg(0);
            if (func_num_args() > 1) {
                $this->level = func_get_arg(1);
            }
			return $this;
		}
		return $this->permissions;
	}

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

	private function isStaticFile($segments) {
		array_shift($segments);
		$module = array_shift($segments);
		$path = join('/', $segments);
		if ($path == '') return '';
		$filename = HOME . '/modules/' . $module . '/static/' . $path;
		$filename = str_replace(['..', '&', '<', '>', '|'], '', $filename);
		return file_exists($filename) ? $filename : '';
	}
	
		// Does the user have permission to access the current item?
	private function _HasPermission() {
		if (!empty($this->info['doc_comment'])) {
			
			$grouplist = explode(' ', trim(preg_replace('/\s+/', ' ', $this->info['doc_comment'])));
			foreach($grouplist as $item) {
                if (is_numeric($item)) {
                    if ($this->level >= $item) return TRUE;
                } elseif (isset($this->permissions[$item])) {
                    return TRUE;
                }
			}
			$res = hook_execute('user.permission', FALSE, $this->info);	// custom permissions for individual items
			return $res;
		}
		return TRUE;		// not specifically listed, so yes
	}
	
	private function _AssembleInfo($route_info) {
		$callable = $route_info['callback'];
		$params = [];
		foreach($route_info['match'] as $index => $value) {
			if ($index) {
				$params[$route_info['mapping'][$index - 1]] = $value;
			}
		}
		$doc_comment = '';
		$info = [
			'file' => '',
			'name' => '',
			'doc_comment' => $doc_comment,
			'params' => [$params],
		];
		if (is_object($callable[0])) {
			$obj = $callable[0];
			$class_name = get_class($obj);
			$info['method'] = $callable[1];
			$info['main'] = $obj;
			$info['class_name'] = $class_name;
		}
		return $info;
	}
	
	private function _processRoute() {
		$uri = ($p = strpos($this->uri, '?')) !== FALSE ? substr($this->uri, 0, $p) : $this->uri;
		$uri = trim($uri, '/');
        $found = NULL;
        foreach($this->routes as $route) {      // should be sorted longest to shortest
			$pattern = $route['pattern'];
			$pattern = str_replace('~', "\\~", $pattern);
            if (preg_match("~^{$pattern}$~i", $uri, $matches)) {
                $found = $route;
                $found['match'] = $matches;
                break;
            }
        }
        if ($found !== NULL) {
			return $this->_AssembleInfo($found);
		}
		return FALSE;
	}

	public function Parse() {
		$this->info = FALSE;
		// check Routes first
		if (count($this->routes)) {
			// locate the doc-comment for this function
			// create info structure if found
			$this->info = $this->_processRoute();
		}
		
		if ($this->info === FALSE) {
			$uri = $this->uri;
			$uri = hook_execute('uri', $uri, $this->input, $this->output);
            if (is_numeric($uri)) {
                $this->info = $uri;
            } elseif ($uri !== FALSE) {
				$this->segments = $this->input->retrieveSegments($uri);
				// find a handler for this uri
				$this->info = hook_execute('execute', FALSE, $this->segments);		// return TRUE if handled, 404 if not recognized, or array if good.
			}
		}
		if (is_array($this->info)) {		// found a handler; do we have permission to execute it?
			if (!$this->_HasPermission()) {
				$this->info = 403;
			}
		} elseif ($this->info === FALSE) {
			$this->info = 404;
		}
	}
	
	public function GetError() {
		return is_numeric($this->info) ? $this->info : 0;
	}
	
	public function isHandled() {
		return $this->info === TRUE;		// already handled
	}
	
	private function loadControllers($info) {
		$file = $info['file'] ?? '';
		if ($file != '') {
			$res = $GLOBALS['loader']->getActiveControllers($file);
			if (count($res)) {
				foreach($res as $item) {
					$filename = $item['filename'];
					try {
						require_once $filename;
						// load class
						foreach($item['classes'] as $class_name) {
							if (stripos($class_name, 'controller') !== FALSE) {
								$class_name::GetHandle();
							}
						}
					} catch (Throwable $e) {			// php 7
						die('Error:' . $e->getMessage() . ' in ' . $e->getFile() . ' at line ' . $e->getLine() . ' [1]');
					}
				}
				return;
			}
		}
		$filename = HOME . '/modules/' . $info['name'] . '/controller/' . $info['file'] . '.php';
		try {
			require_once $filename;
		} catch (Throwable $e) {			// php 7
			die('Error:' . $e->getMessage() . ' in ' . $e->getFile() . ' at line ' . $e->getLine() . ' [1]');
		}
	}
	
	public function Execute() {
		$info = $this->info;
//			return hook_execute('output.error', FALSE, 404, $this->__OUTPUT);
		if (!empty($info['name'])) {
			$this->loadControllers($info);			// if any module has a file "/controller/{$info['file']}.php" then load it
		}

		$class_name = $info['class_name'];
		if (isset($info['main']) && is_object($info['main'])) {
			$object = $info['main'];
		} else {
			$object = $class_name::GetHandle();
		}
		$method = $info['method'];
		if ($method == 'list' || $method == 'new') $method = '_' . $method;
		if (method_exists($object, $method)) {
			return call_user_func_array([$object, $method], $info['params']);	// return NULL if not suitable
		} else {
			$this->info = 404;
			return FALSE;
		}
	}
}
