<?php

namespace IZON\Admin\MVC\Views\Resolvers;

use Exception;

use IZON\MVC\Views\Resolvers\ViewResolver;
use IZON\Logs\Logger;

use IZON\Admin\MVC\Views\AdminPHPView;

/**
 * resolves what view to use for rendering of admin
 * all admin views must start with admin/ prefix to be processed by this view resolver
 * 
 * @author IZON s.r.o. <info@izon.cz>
 * @package IZON\Admin\MVC\Views
 */
class AdminPHPViewResolver implements ViewResolver {

    /**
     * prefix all admin views must start
     */
    const ADMIN_VIEWS_PREFIX = "admin/";


    /**
     * path relative appDir
     */
    const DEFAULT_ADMIN_VIEWS_DIR = "vendor/izon/admin/views";
    
    /**
     * path relative appDir
     */
    const DEFAULT_PROJECT_SPECIFIC_VIEWS_DIR = "app/admin/project/views";
    
    /**
     * path relative each moduleDir
     */
    const DEFAULT_MODULE_VIEWS_DIR = "views";
    
    /**
     * path relative appDir
     */
    const DEFAULT_ADMIN_LIBRARIES_DIR = "vendor/izon/admin/views/_libs";
    
    /**
     * path relative appDir
     */
    const DEFAULT_PROJECT_SPECIFIC_VIEWS_LIBRARIES_DIR = "app/admin/project/views/_libs";
    
    /**
     * path relative each moduleDir
     */
    const DEFAULT_MODUES_LIBRARIES_DIR = "_libs";
    
    /**
     * default absolute url path for admin default static content
     */
    const DEFAULT_ADMIN_STATIC_CONTENT_URL = "/admin/default/static";
    
    /**
     * path relative appDir
     */
    const DEFAULT_ADMIN_STATIC_CONTENT_DIR = "vendor/izon/admin/www";
    
    /**
     * default absolute url path for admin project specific static content
     */
    const DEFAULT_PROJECT_STATIC_CONTENT_URL = "/admin/project/static";
    
    /**
     * path relative appDir
     */
    const DEFAULT_PROJECT_STATIC_CONTENT_DIR = "app/admin/project/www";
    
    /**
     * default prefix for url paths for modules static content
     */
    const DEFAULT_MOUDULE_STATIC_CONTENT_URL_PREFIX = "admin/modules";
    
    /**
     * default suffix for url paths for modules static content
     */
    const DEFAULT_MOUDULE_STATIC_CONTENT_URL_SUFFIX = "static";
    
    /**
     * path relative module dir
     */
    const DEFAULT_MOUDULE_STATIC_CONTENT_DIR = "static";
    
    /**
     * @var string directory application is stored in 
     */
    protected $appDir;
    
    /**
     * @var array directories of all module to search view in
     */
    protected $modulesDirs;

    /**
     * @var array directories that contain wiews form modules
     */
    protected $modulesViewDirs;
    
    /**
     * @var string view admina  
     */
    protected $adminViewsDir;
    
    /**
     * @var string view specific for project
     */
    protected $projectSpecificViewsDir;
    
    /**
     * @var string[] dir containing global libraries used for all modules 
     */
    protected $librariesDirs;
    
    /**
     * @var string[] dir containing libraries in selected module for use in view
     */
    protected $modulesLibrariesDirs;
    
    /**
     * @var string dir containing project static content to be autoloaded
     */
    protected $projectStaticContentAutoloadDir;
    
    /**
     * @var string url for autoloaded static content
     */
    protected $projectStaticContentAutoloadURL;
    
    /**
     * @var string[] suffixes that can be autoloades as styles
     */
    protected $supportedStylesAutoloadSuffixes = [".css"];

    /**
     * @var string[] suffixes that can be autoloades as scripts
     */
    protected $supportedScriptsAutoloadSuffixes = [".js"];

    /**
     * @var array key is http path to prefix files value is fs directory to search file for
     */
    protected $staticContentDirs = [];
    
    /**
     * @var string[] prefiexs to cycle through for static files to load balance loading from multiple servers
     */
    protected $staticContentPrefixes = [];
    
    /**
     * @var Logger|null
     */
    protected $log = NULL;
    
    
    function __construct($appDir, array $modulesDirs, array $config = []) {
        $this->appDir = $appDir;
        $this->modulesDirs = $modulesDirs;
                
        // directories that contain wiews form modules
        $this->modulesViewDirs = $this->initModulesViewsDirs($modulesDirs);
        
        $this->adminViewsDir = $appDir ."/". AdminPHPViewResolver::DEFAULT_ADMIN_VIEWS_DIR;
        
        // config for view specific for project
        if( isset($config["projectSpecificViewsDir"]) ) {
            $this->projectSpecificViewsDir = $config["projectSpecificViewsDir"];
        } else {
            $this->projectSpecificViewsDir = $appDir ."/". AdminPHPViewResolver::DEFAULT_PROJECT_SPECIFIC_VIEWS_DIR;
        }
        
        // directories where to load libraries
        $this->librariesDirs = [$appDir ."/". AdminPHPViewResolver::DEFAULT_ADMIN_LIBRARIES_DIR];
        if( isset($config["librariesDirs"] ) ) {
            if( !is_array($config["librariesDirs"]) ) {
                throw new Exception("librariesDirs must be array");
            }
            $this->librariesDirs = array_merge($this->librariesDirs, $config["librariesDirs"]);
        } else {
            $this->librariesDirs = array_merge($this->librariesDirs, [$appDir ."/". AdminPHPViewResolver::DEFAULT_PROJECT_SPECIFIC_VIEWS_LIBRARIES_DIR]);
        }
        
        // redefine dir for libraries in module
        if( isset($config["modulesLibrariesDirs"] ) ) {
            $this->modulesLibrariesDirs = $config["modulesLibrariesDirs"];
        } else {
            $this->modulesLibrariesDirs = [AdminPHPViewResolver::DEFAULT_MODUES_LIBRARIES_DIR];
        }
        
        // admin static content
        if( isset($config["adminStaticContentDir"]) ) {
            $this->staticContentDirs = array_merge($this->staticContentDirs, $config["adminStaticContentDir"]);
        } else {
            $this->staticContentDirs[self::DEFAULT_ADMIN_STATIC_CONTENT_URL] = $appDir ."/". self::DEFAULT_ADMIN_STATIC_CONTENT_DIR;
        }
        
        // project specific admin static content
        if( isset($config["projectStaticContentDir"]) ) {
            // TODO: implement later
            throw new \IZON\Lang\NotImplementedException();
        } else {
            $this->staticContentDirs[self::DEFAULT_PROJECT_STATIC_CONTENT_URL] = $appDir ."/". self::DEFAULT_PROJECT_STATIC_CONTENT_DIR;
            $this->projectStaticContentAutoloadURL = self::DEFAULT_PROJECT_STATIC_CONTENT_URL;
            $this->projectStaticContentAutoloadDir = $appDir ."/". self::DEFAULT_PROJECT_STATIC_CONTENT_DIR;
        }
        
        // additional static content dirs where to search for static content
        if( isset($config["adidtionalStaticContentDirs"]) ) {
            $this->staticContentDirs = array_merge($this->staticContentDirs, $config["adidtionalStaticContentDirs"]);
        }
        
        // folders for static content in modules
        foreach($this->getModulesDirs() as $type => $dir) {
            $this->staticContentDirs[self::DEFAULT_MOUDULE_STATIC_CONTENT_URL_PREFIX ."/". $type ."/". self::DEFAULT_MOUDULE_STATIC_CONTENT_URL_SUFFIX] = $dir ."/". self::DEFAULT_MOUDULE_STATIC_CONTENT_DIR;
        }
        
        // prefiexs 
        if( isset($config["staticContentPrefixes"]) ) {
            $this->staticContentPrefixes = $config["staticContentPrefixes"];
        } else {
            $this->staticContentPrefixes = [
                "",
            ];
        }
        
        $this->log = Logger::getLogger(self::class);
    }

    public function buildView($viewName) {
        if( !\IZON\String\startsWith($viewName, self::ADMIN_VIEWS_PREFIX) ) {
            $this->log->info(self::class ." not handling view ". $viewName ." it doesn't contain admin/ prefix");
            return NULL;
        }
        $this->log->info("bulding view with name ". $viewName);
        
        // search for view in project specific dir
        $stripedName = mb_substr($viewName, mb_strlen("admin/"));
        
        $viewFilePath =  $this->projectSpecificViewsDir ."/". $stripedName . AdminPHPView::VIEW_SUFFIX;
        if(file_exists($viewFilePath)
            && is_file($viewFilePath) ) {
            $this->log->info("View $viewName found in app/views.");
            
            $view = new AdminPHPView($viewFilePath);
            // folders to load libraries
            $librariesDirs = $this->getLibrariesDirs($viewName);
            $view->setLibrariesDirs($librariesDirs);
            $view->setStaticContentDirs($this->staticContentDirs);
            $view->setStaticContentPrefixes($this->staticContentPrefixes);
            $view->setStylesAutoloadURLs($this->getProjectStylesAutoloadURLs());
            $view->setScriptsAutoloadURLs($this->getProjectScriptsAutoloadURLs());
            
            return $view;
        }
        
        // search for view in modules
        $moduleNameMatches = null;
        if( preg_match("#^admin/([a-zA-Z0-9\-]+/[a-zA-Z0-9\-]+)/.*$#", $viewName, $moduleNameMatches) ) {
            $moduleType = $moduleNameMatches[1]; // typ of module to search

            if( isset($this->modulesViewDirs[$moduleType]) ) {
                 $moduleDir = $this->modulesViewDirs[$moduleType];
                 // path to view in module
                 $viewFilePath = $moduleDir ."/". mb_substr($stripedName, mb_strlen($moduleType)+1) . AdminPHPView::VIEW_SUFFIX;
                 if(file_exists($viewFilePath)
                    && is_file($viewFilePath) ) {
                    
                    $this->log->info("View $viewName found in module $moduleType");
                    
                    $view = new AdminPHPView($viewFilePath);
                    // folders to load libraries
                    $librariesDirs = $this->getLibrariesDirs($viewName);
                    $view->setLibrariesDirs($librariesDirs);
                    $view->setStaticContentDirs($this->staticContentDirs);
                    $view->setStaticContentPrefixes($this->staticContentPrefixes);
                    $view->setStylesAutoloadURLs($this->getProjectStylesAutoloadURLs());
                    $view->setScriptsAutoloadURLs($this->getProjectScriptsAutoloadURLs());
                    
                    return $view;
                 }
             }
            
        }
        
        $viewFilePath = $this->adminViewsDir ."/". $stripedName . AdminPHPView::VIEW_SUFFIX;
        if( file_exists($viewFilePath)
            && is_file($viewFilePath) ) {
            $this->log->info("View $viewName found in vendor/izon/admin");
            
            $view = new AdminPHPView($viewFilePath);
            // folders to load libraries
            $librariesDirs = $this->getLibrariesDirs($viewName);
            $view->setLibrariesDirs($librariesDirs);
            $view->setStaticContentDirs($this->staticContentDirs);
            $view->setStaticContentPrefixes($this->staticContentPrefixes);
            $view->setStylesAutoloadURLs($this->getProjectStylesAutoloadURLs());
            $view->setScriptsAutoloadURLs($this->getProjectScriptsAutoloadURLs());
            return $view;
        }
        
        $this->log->info("View not found. Pass to next resolver");
        
        return NULL;
    }
    
    protected function initModulesViewsDirs($modulesDirs) {
        $modulesViewsDirs = [];
        foreach($modulesDirs as $moduleName => $modulesDir) {
            $absoluteModulesDirPath = $modulesDir ."/". AdminPHPViewResolver::DEFAULT_MODULE_VIEWS_DIR;
            if( file_exists($absoluteModulesDirPath) ) {
                if( !is_dir($absoluteModulesDirPath) ) {
                    throw new Exception("module view directory". $absoluteModulesDirPath ." is file");
                }
                $modulesViewsDirs[$moduleName] = $absoluteModulesDirPath;
            }
        }
        return $modulesViewsDirs;
    }
    
    protected function getLibrariesDirs($viewName) {
        // admin and app libraries
        $librariesDirs = $this->librariesDirs;
        
        // module libraries only if view in module
        $moduleNameMatches = null;
        if( preg_match("#^admin/([a-zA-Z0-9\-]+/[a-zA-Z0-9\-]+)/.*$#", $viewName, $moduleNameMatches) ) {
            $moduleType = $moduleNameMatches[1]; // typ modulu ve kterem se ma hledat
            if( isset( $this->modulesViewDirs[$moduleType] ) ) {
                $moduleDir = $this->modulesViewDirs[$moduleType];
                // view is from module, load libraties from this folder
                foreach($this->modulesLibrariesDirs as $moduleLibraryDir) {
                    $moduleLibrariesDir = $moduleDir ."/". $moduleLibraryDir;
                    if( file_exists($moduleLibrariesDir)
                        && is_dir($moduleLibrariesDir) ) {
                        $librariesDirs[] = $moduleLibrariesDir;
                    }
                }
            }
        }
        
        return $librariesDirs;
    }
    
    /**
     * @return string[] array of script autoloads
     */
    protected function getProjectStylesAutoloadURLs() {
        $autoloadURLs = [];
        $stylesDir = $this->projectStaticContentAutoloadDir ."/css";
        if( file_exists($stylesDir) && is_dir($stylesDir) ) { // dir exists
            foreach( scandir($stylesDir) as $file ) { // go through all files in dir
                if( $this->hasSuffix($file, $this->supportedStylesAutoloadSuffixes) ) {
                    $autoloadURLs[] = [
                        "url" => $this->projectStaticContentAutoloadURL ."/css/". $file,
                        "type" => "css"
                    ];
                }
            }
        }
        return $autoloadURLs;
    }
    
    /**
     * @return string[] array of script autoloads
     */
    protected function getProjectScriptsAutoloadURLs() {
        $autoloadURLs = [];
        $scriptsDir = $this->projectStaticContentAutoloadDir ."/js";
        if( file_exists($scriptsDir) && is_dir($scriptsDir) ) { // dir exists
            foreach( scandir($scriptsDir) as $file ) { // go through all files in dir
                if( $this->hasSuffix($file, $this->supportedScriptsAutoloadSuffixes) ) {
                    $autoloadURLs[] = [
                        "url" => $this->projectStaticContentAutoloadURL ."/js/". $file,
                        "type" => "js"
                    ];
                }
            }
        }
        return $autoloadURLs;
    }

    /**
     * returns true if $fileName has one of $suffiexs
     * @param string $fileName
     * @param string[] $suffiexs
     * @return boolean
     */
    protected function hasSuffix($fileName, array $suffiexs) {
        foreach( $suffiexs as $suffiex ) {
            if( \IZON\String\endsWith($fileName, $suffiex) ) {
                return true;
            }
        }
        return false;
    }

    protected function getModulesDirs() {
        return $this->modulesDirs;
    }
}