<?php
namespace apexl\Io\modules\system\services;

use apexl\ClassEngine\EngineSingleton;
use apexl\Config\Singleton;
use apexl\Io\includes\System;
use apexl\Io\interfaces\IoModuleInterface;
use HaydenPierce\ClassFinder\ClassFinder;

class modules extends EngineSingleton {

    /** @var array  */
    protected $moduleNamespaces = [];
    /** @var Singleton */
    protected $config;

    protected $loadedModules = [];

    protected $services = [];

    protected function __construct()
    {
        parent::__construct();
        $this->config = Singleton::getInstance();
        //register any additional module namespaces to load.
        $this->moduleNamespaces = $config->app->module->namespaces ?? [];
        //add the core namespace last.
        $this->moduleNamespaces[] = 'apexl\Io\modules';

        $this->getAvailableModules();
    }

    /**
     * Function to get modules in all known namespaces.
     * @todo add caching class to allow the application to cache into a service such as memcache or redis.
     * @throws \Exception
     */
    protected function getAvailableModules(){

        foreach($this->moduleNamespaces as $namespace) {
            $classes = ClassFinder::getClassesInNamespace($namespace, ClassFinder::RECURSIVE_MODE);
            $foundModules = [];
            foreach($classes as $class){
                //we only want to name immediately after the modules namespace (for now) so strip the rest.
                $moduleSpace = str_replace($namespace.'\\', '', $class);
                $moduleSpaces = explode('\\', $moduleSpace);
                $moduleName = array_shift($moduleSpaces);
                if(!in_array($moduleName, $foundModules)) {
                    $module = '\\' . $namespace . '\\' . $moduleName . '\\' . $moduleName . 'Module';
                    $this->loadedModules[$moduleName] = $module;
                    $foundModules[] = $moduleName;
                }
            }
        }
    }

    public function addModule($namespace){
        $namespace = explode('\\', $namespace);
        $this->loadedModules[end($namespace)] = $namespace;
    }

    /**
     * Method to initialise available modules.
     * @todo change this to only initialise installed or those that dont need install.
     * @return array
     */
    public function initialiseActiveModuleServices(){
        //first loop over the modules and add any services they register.
        foreach ($this->loadedModules as $module) {
            //we have to double instantiate so that $app is available the second time around.
            $module = new $module();
            $this->initialiseModuleServices($module);
        }

        return $this->services;
    }

    /**
     * Get the loaded modules.
     * @return array
     */
    public function getLoadedModules(){
        return $this->loadedModules;
    }

    protected function initialiseModuleServices(IoModuleInterface $module){
        if(method_exists($module, 'addServices')){
            $this->services = array_merge($this->services, $module->addServices());
        }
    }

    public function initialiseActiveModules(){
        foreach($this->loadedModules as $module){
            $module = new $module();
            //we have to double instantiate so that $app is available the second time around.
            $this->initialiseModule($module);
        }
    }

    /**
     * Push the provided module to the top of the loaded list.
     * @param $module
     */
    public function setModuleFirst($module){
        if(isset($this->loadedModules[$module])){
            $path = $this->loadedModules[$module];
            unset($this->loadedModules[$module]);
            $this->loadedModules = [$module => $path] + $this->loadedModules;
        }
    }

    protected function initialiseModule(IoModuleInterface $module){
        //First we need to register the routes, we do this with reflection
        $module->routes();
        //not all modules will add middleware so add a check for those that do.
        if(method_exists($module, 'addMiddleware')){
            $module->addMiddleware();
        }
        //@todo look at rebuilding this / caching this. Do not load if not installed.
        if(class_exists('\apexl\Io\modules\user\userModule') && System::moduleIsInstalled($module)){
            $permissions = \apexl\Io\modules\user\services\Permissions::getInstance();
            if(method_exists($module, 'registerPermissions')){
                $module->registerPermissions($permissions);
            }
        }
    }
}