<?php

namespace IZON\Admin\MVC\Routers;

use Exception;
use PDO;

use IZON\Utils\Locale;

use IZON\DB\DBConnection;

use IZON\Logs\Logger;

use IZON\MVC\HttpRequest;
use IZON\MVC\Routers\RouteDefinition;
use IZON\MVC\Routers\RouteInfo;

use IZON\Admin\Config;

use IZON\Admin\MVC\ModuleInfo;
use IZON\Admin\MVC\Context\AdminContext;

use IZON\Admin\MVC\Exceptions\AdminPageNotFoundException;

/**
 * Definice routovani, ktera provadi routovani pro jednotlive moduly admina
 */
class AdminRouteDefinition implements RouteDefinition {
    
    /**
     * prefix pro routovani admina
     * @var string 
     */
    protected $adminRoutePattern = "^/admin";
    
    /**
     * specificke routovani primo pro prihlaseni a odhlaseni z adminu
     * neaplikuji se na ne $adminInterceptorIdentifiers
     * @var array
     */
    protected $adminLoginLogouRoutes =  [
        "^/admin/login" => [Config::LOGIN_CONTROLLER_IDENTIFIER, "login"],
        "^/admin/logout" => [Config::LOGOUT_CONTROLLER_IDENTIFIER, "logout"],
    ];
    
    /**
     * routovani specificke v ramci admina
     */
    protected $adminSpecificRoutes = [
        "^/admin/dashboard" => [Config::DASHBOARD_CONTROLLER_IDENTIFIER],
        "^/admin/switch-country" => [Config::SWITCH_COUNTRY_CONTROLLER_IDENTIFIER],
        "^/admin/switch-interface-locale" => [Config::SWITCH_INTERFACE_LOCALE_CONTROLLER_IDENTIFIER],
        "^/admin/notifications" => [Config::NOTIFICATIONS_CONTROLLER_IDENTIFIER, "ajaxGetNotificationsJson"],
    ];


    /**
     * @var array interceproty, ktere se provadeji pro kazdy conroller admina
     */
    protected $adminInterceptorIdentifiers = [
        Config::ADMIN_VERSION_INTERCEPTOR_IDENTIFIER,
        Config::ADMIN_MAX_FILE_SIZE_INTERCEPTOR_IDENTIFIER
    ];

    /**
     * @var array interceptory provadene kolem modulovych controlleru
     */
    protected $adminInternalInterceptorIdentifiers = [
        Config::LOGGED_USER_CHECK_INTERCEPTOR_IDENTIFIER,
        Config::HANDLE_LOGGED_USER_INTERFACE_LOCALE_INTERCEPTOR,
        Config::LOGGED_USER_INTERCEPTOR_IDENTIFIER,
        Config::MODULES_ACTIONS_INTERCEPTOR_IDENTIFIER,
        Config::MODULES_SUPPORTED_COUNTRIES_INTERCEPTOR_IDENTIFIER
    ];
    
    /**
     * interceptory vazane na jednotlive controllery
     * pole poli, klic je uid controlleru 
     * a hodnota je pole s id jednotlivych controlleru
     * @var array 
     */
    protected $controllerInterceptors = [];

    /**
     * @var DBConnection
     */
    protected $dbConnection;
    
    /**
     * @var \IZON\Admin\Services\SessionService|null
     */
    protected $sessionService;


    /**
     * @var Logger logger pro tridu
     */
    protected $log;

    
    public function __construct(DBConnection $dbConnection, $sessionService = null) {
        $this->dbConnection = $dbConnection;
        $this->sessionService = $sessionService;
        // logovani
        $this->log = Logger::getLogger(__CLASS__);
    }
    
    public function findRoute(HttpRequest $request) {
        $this->log->info("Trying to find routing for url: ". $request->getURL());
        
        // projit specialni adresy admina, hlavne prihlaseni a odhlaseni
        foreach($this->adminLoginLogouRoutes as $url => $rule) {
            if( preg_match("#". $url ."#", $request->getURL()) ) {
                $routeInfo = new RouteInfo();
                $routeInfo->setURL($request->getURL());
                $routeInfo->setPattern($url);
                $routeInfo->setContrlollerId($rule[0]);
                $routeInfo->setMethodName($rule[1]);
                
                // add context
                $context = new AdminContext($request->getSession(), null, $this->sessionService);
                $routeInfo->setContext($context);
                
                $interceptors = $this->adminInterceptorIdentifiers;
                if( isset($this->controllerInterceptors[$rule[0]]) ) {
                    $interceptors = array_merge($interceptors, $this->controllerInterceptors[$rule[0]]);
                }
                $routeInfo->setInterceptors($interceptors);
                
                $this->log->info("Calling of login or logout url: ". $url);
                
                return $routeInfo;
            }
        }
        
        // projde mapovani adres pro admin modul
        // hlavne dashboard, na ktery se presmerovava po prihlaseni
        foreach($this->adminSpecificRoutes as $url => $rule) {
            if( preg_match("#". $url ."#", $request->getURL()) ) {
                $routeInfo = new RouteInfo();
                $routeInfo->setURL($request->getURL());
                $routeInfo->setPattern($url);
                $routeInfo->setContrlollerId($rule[0]);
                
                // add context
                $context = new AdminContext($request->getSession(), null, $this->sessionService);
                $routeInfo->setContext($context);
                
                $interceptors = array_merge($this->adminInterceptorIdentifiers, $this->adminInternalInterceptorIdentifiers);
                if( isset($this->controllerInterceptors[$rule[0]]) ) {
                    $interceptors = array_merge($interceptors, $this->controllerInterceptors[$rule[0]]);
                }
                $routeInfo->setInterceptors($interceptors);
                
                if( array_key_exists(1, $rule) ) {
                    $routeInfo->setMethodName($rule[1]);
                }
                
                $this->log->info("Calling of default admin routes: ". $url);
                
                return $routeInfo;
            }
        }
        
        if( preg_match("#". $this->adminRoutePattern ."/{0,1}$#", $request->getURL()) ) { // vola se adresa /admin nebo /admin/
            // presmeruj na dashboard
            $routeInfo = new RouteInfo();
            $routeInfo->setRedirectCode(301);
            $routeInfo->setURL($this->findURL(Config::DASHBOARD_CONTROLLER_IDENTIFIER, [], NULL, $request->getLocale()));

            $this->log->info("Redirecting to dashboard: ". $url);

            return $routeInfo;
        }
        
        // je adresa modulu adminu
        if( preg_match("#". $this->adminRoutePattern ."/.*#", $request->getURL()) ) { // je url admina
            $this->log->info("Calling admin route: ". $request->getURL());
            
            $modulePattern = $this->adminRoutePattern .'/([a-zA-Z0-9/-]+)/([a-zA-Z0-9\.]+)/([a-zA-Z0-9]*)';
            if( preg_match('#'. $modulePattern .'#', $request->getURL(), $matches) ) {  // reqexp musi byt obalen delimiterem
                $moduleIdentifier = $matches[1];
                $bareControllerId = $matches[2];
                $methodName =  $matches[3];
                
                if($methodName == '') {
                    $methodName = "execute";
                }
            } else {
                throw new AdminPageNotFoundException("Request url ". $request->getURL() ." doesn't match pattern module $modulePattern");
            }
            
            // TODO: pouzit neco univerzalnejsiho
            /* @var $conn \PDO */
            $conn = $this->dbConnection->getPDO();
            $sql = 'select m.* 
                from core_admin_modules m 
                where m.identifier = :moduleIdentifier';
            $statement = $conn->prepare($sql);
            $statement->bindValue(":moduleIdentifier", $moduleIdentifier);
            $statement->execute();
        
            if( !$row = $statement->fetch(PDO::FETCH_ASSOC) ) {
                $e = new Exception("Calling to nonexisting module $moduleIdentifier and modules controller $bareControllerId");
                $this->log->warning("Calling to nonexisting module $moduleIdentifier and modules controller $bareControllerId");
                throw $e;
            }
            
            // ziska identifikator specificky pro modul
            $controllerId = Config::getModuleEntryName($bareControllerId,
                                                        $row["identifier"]);
            
            $this->log->info("Complere controllerId = ". $controllerId);
            
            $routeInfo = new RouteInfo();
            $routeInfo->setURL($request->getURL());
            $routeInfo->setPattern($this->adminRoutePattern);
            $routeInfo->setContrlollerId($controllerId);
            $routeInfo->setMethodName($methodName);
            
            $interceptors = array_merge($this->adminInterceptorIdentifiers, $this->adminInternalInterceptorIdentifiers);
                if( isset($this->controllerInterceptors[$rule[0]]) ) {
                    $interceptors = array_merge($interceptors, $this->controllerInterceptors[$rule[0]]);
                }
            $routeInfo->setInterceptors($interceptors);
            
            // creates information about called module
            $moduleInfo = $this->createModuleInfo($row, $bareControllerId, $methodName);
            $request->addParameter(ModuleInfo::MODULE_INFO_INDENTIFIER, $moduleInfo);
            
            // add context
            $context = new AdminContext($request->getSession(), $moduleInfo, $this->sessionService);
            $routeInfo->setContext($context);
                    
            // nastavuje id controlleru, ktery se volal
            $request->setCalledControllerId($controllerId);
            
            return $routeInfo;
        }

        return NULL;
    }

    /**
     * 
     * @param string $controllerId
     * @param array $parameters
     */
    public function findURL($controllerId, array $parameters, $methodName, Locale $locale) {
        // login and logout urls
        foreach($this->adminLoginLogouRoutes as $url => $cId) {
            if( $cId[0] == $controllerId ) {
                // TODO: add parameters
                return str_replace("^", "", $url);
            }
        }
        
        // dashboard and other specific urls
        foreach($this->adminSpecificRoutes as $url => $cId) {
            if( $cId[0] == $controllerId ) {
                $url = str_replace("^", "", $url);
                
                $paramsString = "";
                foreach($parameters as $key => $value) {
                    if( $paramsString != '' ) {
                        $paramsString .= "&";
                     }
                     $paramsString .= "$key=$value";
                }

                if( $paramsString != '') {
                    $url = $url ."?". $paramsString;
                }
                return $url;
            }
        }
        
        if( \IZON\String\startsWith($controllerId, "MODULE#") ) { // je cesta k modulu
            $parts = explode("#", $controllerId);
            $typeAndIdentifierPart = explode("|", $parts[1]);
            
            // pokud je zadane $methodName tak ma prednost pred action
            if( $methodName != '') {
                $parameters["action"] = $methodName;
            }
            
            $url = "/admin/". $typeAndIdentifierPart[0] ."/". $parts[2] ."/". $parameters["action"];
            unset($parameters["action"]);
            
            $paramsString = "";
            foreach($parameters as $key => $value) {
                if( $paramsString != '' ) {
                    $paramsString .= "&";
                 }
                 $paramsString .= "$key=$value";
            }
            
            if( $paramsString != '') {
                $url = $url ."?". $paramsString;
            }
            return $url;
        }
        
        return NULL;
    }
    
    /**
     * nastavuje interceptory jen pro specificke controllery
     * @param array $interceptors pole poli kde klicem je identifier controlleru a hodnota je pole interceptoru, ktere se na nem maji provezt
     */
    function setControllerInterceptors(array $interceptors) {
        $this->controllerInterceptors = $interceptors;
    }
    
    public function addInterceptor($interceptorIndetifier) {
        $this->adminInterceptorIdentifiers[] = $interceptorIndetifier;
    }
    
    /**
     * 
     * @param array $row
     * @param string $controllerId
     * @param string $action
     * @return ModuleInfo informace o modulu, na ktery se routuje
     */
    protected function createModuleInfo(array $row, 
                                        $controllerId, 
                                        $action) {
        $moduleInfo = new ModuleInfo();
        
        $moduleInfo->setType($row["type"]);
        $moduleInfo->setIdentifier($row["identifier"]);
        $moduleInfo->setName($row["name"]);
        $moduleInfo->setControllerId($controllerId);
        $moduleInfo->setAction($action);
        
        // TODO: nacist nazev akce
        $moduleInfo->setActionName($row["name"]);
        
        return $moduleInfo;
    }
}
