<?php
/*
 * Copyright © 2021 ExacTI Technology Solutions. All rights reserved.
 * GPLv3 General License.
 * https://exacti.com.br
 * Phacil PHP Framework - https://github.com/exacti/phacil-framework
 */

namespace Phacil\Framework;

use Phacil\Framework\Config;
use Phacil\Framework\Registry;

/** 
 * Extend this class to create interation with your module controller to Phacil engine controller.
 * 
 * Use as:
 * <code>
 * <?php 
 * namespace YourPrefix\Path\Controller;
 * class YouClass extends \Phacil\Framework\Controller {
 *  public function index() {
 *      #Your code
 *  }
 * } 
 * </code>
 * 
 * You can use the __construct function on call the \Phacil\Framework\Register object inside parent.
 * 
 * <code>
 *  public funcion __construct(\Phacil\Framework\Registry $registry){ parent::__construct($registry); YOUR_CODE; }
 * </code>
 * 
 * @abstract
 * @package Phacil\Framework 
 * @since 0.1.0
 * @api
 */
abstract class Controller implements \Phacil\Framework\Interfaces\Controller {
    /**
     * 
     * @var Registry
     */
    protected $registry;

    /**
     * 
     * @var int
     */
    protected $id;

    /**
     * 
     * @var mixed
     */
    protected $layout;

    /**
     * The template path
     * 
     * @var string
     */
    protected $template;

    /**
     * The childrens parts of the controller
     * 
     * @var array
     */
    protected $children = array();

    /**
     * The variables of controller
     * 
     * @var array
     */
    protected $data = array();

    /**
     * The twig aditional funcions created by your!
     * 
     * @var array
     */
    protected $twig = array();

    /**
     * The errors array
     * 
     * @var array
     */
    protected $error = array();
    
    /**
     * The rendered output 
     * 
     * @var string
     */
    protected $output;

    /**
     * The original route of childrens
     * @var string
     */
    public $routeOrig;

    /**
     * The output content type
     * 
     * @var string
     */
    public $contentType = 'text/html; charset=utf-8';

    /**
     * Implements constructor.
     * 
     * If you use this, don't forget the parent::__construct($registry);
     * 
     * @param \Phacil\Framework\Registry $registry 
     * @return void 
     */
    public function __construct(\Phacil\Framework\Registry $registry = null) {
        if (!$registry) {

            /**
             * @var \Phacil\Framework\Registry
             */
            $registry = \Phacil\Framework\Registry::getInstance();
        }
        $this->registry = &$registry;
    }

    /** @return void  */
    private function __getRegistryClass(){
        $this->registry = \Phacil\Framework\startEngineExacTI::getRegistry();
    }

    /**
     * 
     * {@inheritdoc}
     */
    static public function getInstance() {
        $class = get_called_class();
        return \Phacil\Framework\Registry::getAutoInstance((new $class()));
    }

    /**
     * @param string $key 
     * @return object 
     * @final
     */
    final public function __get($key) {
        if (!$this->registry) {
            $this->__getRegistryClass();
        }

        return $this->registry->get($key);
    }

    /**
     * @param string $key 
     * @return object 
     * @final
     * @todo Not yet...
     */
    /* final public function __call($key, array $arguments) {
        if (!$this->registry) {
            $this->__getRegistryClass();
        }

        return $this->registry->get($key);
    } */

    /**
     * 
     * @param string $key 
     * @param object $value 
     * @return void 
     * @final
     */
    final public function __set($key, $value) {
        if(!$this->registry) {
            $this->__getRegistryClass();
        }

        $this->registry->set($key, $value);
    }

    /**
     * @param string $route 
     * @param array $args 
     * @return \Phacil\Framework\Interfaces\Action
     * @final
     */
    final protected function forward($route, array $args = array()) {
        return new Action($route, $args);
    }

    /**
     * Send redirect HTTP header to specified URL
     * 
     * Use the \Phacil\Framework\Response::redirect() registered object.
     * 
     * @param string $url 
     * @param int $status 
     * @return never 
     * @final
     */
    final protected function redirect($url, $status = 302) {
        $this->registry->response->redirect($url, $status);
    }

    /**
     * Get and load the childrens controller classes
     * 
     * @final
     * @param string $child 
     * @param array $args 
     * @return object 
     */
    final protected function getChild($child, array $args = array()) {
        $action = new Action($child, $args);
        $file = $action->getFile();
        $class = $action->getClass();
		$classAlt = $action->getClassAlt();
        $method = $action->getMethod();

        if ($file && file_exists($file)) {
            require_once($file);

            foreach($classAlt as $classController){
				try {
                    if(class_exists($classController)){
                        $this->registry->routeOrig = $child;
                        $controller = $this->registry->injectionClass($classController);
					    //$controller = new $classController($this->registry);
                        if (is_callable([$controller, $method])) {
                            call_user_func_array(array($controller, $method), $args);
                        } else {
                            throw new \Phacil\Framework\Exception("Error Processing Request", 1);
                        }
					
					    break;
                    }
				} catch (\Phacil\Framework\Exception\Throwable $th) {
					throw $th;
				}
			}

            $this->registry->routeOrig = null;

            return $controller->output;

        } elseif (!$file && isset($classAlt['class']) && !empty($classAlt['class']) && class_exists($classAlt['class'])) {
            try {
                $this->registry->routeOrig = $child;
                $controller = $this->registry->injectionClass($classAlt['class']);
                if (is_callable(array($controller, $method))) {
                    call_user_func_array(array($controller, $method), $args);
                } else {
                    throw new \Phacil\Framework\Exception("Error Processing Request", 1);
                }

                $this->registry->routeOrig = null;

                return $controller->output;
            } catch (\Phacil\Framework\Exception\Throwable $th) {
                throw ($th);
            }
        } else {
            throw new Exception("Could not load controller " . $child . '!', 1);
        }
    }

    /**
     * Render template
     * 
     * @return string 
     * @throws TypeError 
     * @throws Exception 
     * @final
     */
    protected function render() {

        foreach ($this->children as $child) {
            $this->data[basename($child)] = $this->getChild($child);
        }

        /**
         * @var \Phacil\Framework\Render
         */
        $tpl = $this->registry->getInstance(\Phacil\Framework\Render::class);

        $pegRout = explode("/", ($this->registry->routeOrig)?: \Phacil\Framework\startEngineExacTI::getRoute());

        if($this->template === NULL) {
            $thema = ($this->config->get("config_template") != NULL) ? $this->config->get("config_template") : "default";

            $noCaseFunction = function ($str) {
                return \Phacil\Framework\Registry::case_insensitive_pattern($str);
            };

            //Just template not set manual
            $routePatterned = array_map($noCaseFunction, $pegRout);
            $routeWithoutLastPatterned = $routePatterned;
            $lastRoutePatterned = array_pop($routeWithoutLastPatterned);
            //$routeWithoutFirstPatterned = $routePatterned;
            //$firstRoutePatterned = array_shift($routeWithoutFirstPatterned);

            $structure = [];

            if ($thema != "default")
                $structure[self::TEMPLATE_AREA_THEME][] =  $thema . '/' . implode("/", $routePatterned);

            $structure[self::TEMPLATE_AREA_THEME][] = 'default/' . implode("/", $routePatterned);

            $positions = 2;

            for ($i=1; $i <= $positions; $i++) {
                if(count($routePatterned) <= ($i)) break;

                $mount = $routePatterned;
                array_splice($mount, $i, 0, 'View');
                $structure[self::TEMPLATE_AREA_MODULAR][] =  implode("/", $mount);

                if(($i+1) < count($routePatterned))
                    $structure[self::TEMPLATE_AREA_MODULAR][] =  implode("/",  array_slice($mount, 0, -1)). "_" .end($mount);
            }

            if (count($routePatterned) > 2) {
                //Old compatibility
                if($thema != "default")
                    $structure[self::TEMPLATE_AREA_THEME][] =  $thema . '/' . implode("/", $routeWithoutLastPatterned) . '_' . $lastRoutePatterned ;

                $structure[self::TEMPLATE_AREA_THEME][] = 'default/' . implode("/", $routeWithoutLastPatterned) . '_' . $lastRoutePatterned;
            }

            //Check if theme exists
            $findThemeFile = (function(array $structure, $pathArea) use ($tpl) {
                foreach ($structure as $themefile) {
                    $types = [
                        'modular' => $pathArea . $themefile,
                    ];
                    $files['modular'] = null;
                    foreach ($types as $type => $globs) {
                        foreach ($tpl->getTemplateTypes() as $extensionTemplate) {
                            $files[$type] = glob($globs . "." . $extensionTemplate, GLOB_BRACE);
                            if (count($files[$type]) > 0) break;
                        }
                    }
                    if (empty($files['modular'])) continue;
                    if (!empty($files['modular'])) {
                        foreach ($files['modular'] as $modular) {
                            $this->template = str_replace($pathArea, "", $modular);
                            return $templatePath = $pathArea;
                            break;
                        }
                    }
                    if (!empty($this->template)) break;
                }
            });

            $templatePath = $findThemeFile($structure[self::TEMPLATE_AREA_THEME], Config::DIR_TEMPLATE()) ?: $findThemeFile($structure[self::TEMPLATE_AREA_MODULAR], Config::DIR_APP_MODULAR());
        } else {
            if(file_exists(Config::DIR_APP_MODULAR(). $pegRout[0] ."/View/" .$this->template)){
                $templatePath = Config::DIR_APP_MODULAR(). $pegRout[0] ."/View/";
            } else {
                $filesSeted = glob(
                    Config::DIR_APP_MODULAR() .
                    \Phacil\Framework\Registry::case_insensitive_pattern($pegRout[0])
                    . "/View/" . $this->template,
                    GLOB_BRACE
                );

                if (count($filesSeted) > 0) {
                    $templatePath = str_replace($this->template, "", $filesSeted[0]);
                }
            }

            if(file_exists(Config::DIR_TEMPLATE() .$this->template)){
                $templatePath = Config::DIR_TEMPLATE();
            }
        }

        if(empty($this->template)) {
            throw new Exception('Error: template not seted!');
        }

        if (is_readable($templatePath . $this->template)) {

            $templateFileInfo = pathinfo($templatePath .$this->template);
            $templateType = $templateFileInfo['extension'];

            $tpl->setTemplate($templateType, $templatePath, $this->template, $this->data, $this->twig);

            $this->registry->response->addHeader('Content-Type', $this->contentType);

            $this->output = $tpl->render();

            unset($tpl);

            return $this->output;

        } else {
            throw new Exception('Error: Could not load template ' . $templatePath . $this->template . '!');
        }
    }

    /**
     * @param bool $commonChildren (optional) Whether to include the common children
     * @return \Phacil\Framework\Response 
     * @throws Exception 
     * @since 1.1.0
     */
    protected function out ($commonChildren = true) {
        if($commonChildren === true){
            $this->children = array_merge(array(
                'common/footer',
                'common/header'), $this->children
            );
        }

        return $this->response->setOutput($this->render());
    }
}