<?php
	namespace Juju {
		require_once('base.abstract.class.php');
		require_once('App/router.class.php');
		require_once('App/exception.class.php');
		use \Juju\Http\{Response, Request};
		use \Juju\App\Router;

		class App extends Base {
			use Events;
			private static $apps = [];
			private $domains;
			private $routers;
			private $router;
			public $_onerror;
			public $request = null;
			public $response;
			public function __construct(string $name, callable $fn = null, array $events = null){
				$this->name = $name;
				$this->router = new Router();
				$this->response = new Response();
				$this->routers = [];
				$this->domains = [];
				$this->_onerror = function($req, $res, $error){
					$o = json_encode([
						'message'=>$error->getMessage(),
						'code'=>$error->getCode(),
						'file'=>$error->getFile(),
						'line'=>$error->getLine(),
						'traceback'=>$error->getTrace()
					]);
					$res->code(404);
					$res->header('Content-Type', 'application/json');
					$res->end($o);
					$res->shutdown();
				};
				if(!is_null($events)){
					foreach($events as $name => $handler){
						$this->on($name, $handler);
					}
				}
				static::$apps[] = $this;
				if(is_callable($fn)){
					$fn($this);
				}
			}
			public function __destruct(){
				$this->routers = [];
				$index = array_search($this, static::$apps);
				if($index !== false){
					array_splice(static::$apps, $index, 1);
				}
				if(is_callable('parent::__destruct')){
					parent::__destruct();
				}
			}
			public static function import_all(string $dirpath){
				foreach(scandir($dirpath) as $file){
					$path = "{$dirpath}{$file}";
					if(is_file($path) && pathinfo($file, PATHINFO_EXTENSION) == "php"){
						require_once($path);
					}
				}
			}
			public static function shutdown(){
				$verb = $_SERVER['REQUEST_METHOD'];
				$url = App::get_url();
				$data = file_get_contents( 'php://input','r');
				foreach(static::$apps as $k => $app){
					if($app instanceof App){
						$app->handle($verb, $url, $data)->shutdown();
					}
				}
				if(is_callable('parent::__destruct')){
					parent::__destruct();
				}
			}
			public static function shutdown_error($error){
				foreach(static::$apps as $k => $app){
					if(is_null($app->request)){
						$app->request = new Request(static::get_url(), getallheaders(), file_get_contents( 'php://input','r'));
					}
					$app->onerror($app->request, $app->response, $error);
					$app->response->shutdown();
				}
			}
			public static function get_url(){
				if(isset($_SERVER['REDIRECT_URL'])){
					$url = 'REDIRECT_URL';
				}else{
					$url = 'REQUEST_URI';
				}
				if(!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])){
					$root = $_SERVER['HTTP_X_FORWARDED_PROTO'].'://';
				}else{
					$root = !empty($_SERVER['HTTPS']) ? "https://" : "http://";
				}
				// @todo - determine if http or http and also check cloudflare
				return parse_url($root.$_SERVER['HTTP_HOST'].$_SERVER[$url]);
			}
			public function handle(string $verb, array $url, string $data, array $headers = null){
				if(is_null($headers)){
					$headers = getallheaders();
				}
				$res = $this->response;
				$this->request = $req = new Request($url, $headers, $data);
				if($this->fire('handle', $req, $res)){
					$self = $this;
					$onerror = function($res, $error) use($self){
						$self->onerror($req, $res, $error);
					};
					$handled = false;
					// Domain routers
					foreach($this->domains as $host => $router){
						if($url['host'] == $host){
							while(is_string($router)){
								$router = $this->domains[$router];
							}
							$router->handle($url["path"], $req, $res, null, $onerror);
							$handled = $handled || $router->handled;
						}
					}
					// Prefixed path routers
					foreach($this->routers as $prefix => $router){
						$router->handle($url["path"], $req, $res, null, $onerror);
						$handled = $handled || $router->handled;
					}
					// Base router for non-prefixed paths
					$this->router->handle($url["path"], $req, $res, function($req, $res) use($handled, $self){
						if(!$handled){
							$self->onerror($req, $res,new App\Exception("Not Found", 404));
						}
					}, $onerror);
					$this->fire('afterhandle', $req, $res);
				}
				return $res;
			}
			public function error(callable $fn){
				$this->_onerror = $fn;
				return $this;
			}
			public function route(string $path, callable $fn){
				$this->router->path($path, $fn);
				return $this;
			}
			public function prefix(string $prefix, callable $fn){
				if(!$this->routers[$prefix]){
					$this->routers[$prefix] = new App\Router($prefix);
				}
				$fn($this->routers[$prefix]);
				return $this;
			}
			public function domain(string $host, callable $fn){
				if(!isset($this->domains[$host])){
					$this->domains[$host] = new App\Router();
				}
				$fn($this->domains[$host]);
				return $this;
			}
			public function map_domain(string $host, $handle){
				if(!is_array($handle)){
					$handle = [$handle];
				}
				foreach($handle as $host2){
					$this->domains[$host2] = $host;
				}
				return $this;
			}
			public function bind(string $hosts, callable $fn){
				$hosts = array_map(function($item){
					return trim($item);
				}, explode(',', $hosts));
				$host = array_pop($hosts);
				return $this->map_domain($host, $hosts)->domain($host, $fn);
			}
			public function onerror(Request $req, Response $res, $error){
				$this->fire('error', $error);
				$fn = $this->_onerror;
				if(is_callable($fn)){
					$fn($req, $res, $error);
				}
			}
		}
		error_reporting(E_ALL);
		ini_set('display_errors', 'On');
		register_shutdown_function(function(){
			App::shutdown();
		});
		set_error_handler(function($errno, $errstr, $errfile, $errline){
			App::shutdown_error(new App\Exception($errstr, $errno, null, $errfile, $errline, debug_backtrace(), get_included_files()));
		},E_ALL);
	}
?>