<?php
/*---------------------------------------------------------------------------
 * @Project: Alto CMS
 * @Project URI: http://altocms.com
 * @Description: Advanced Community Engine
 * @Copyright: Alto CMS Team
 * @License: GNU GPL v2 & MIT
 *----------------------------------------------------------------------------
 */

set_include_path(get_include_path() . PATH_SEPARATOR . __DIR__);

class Loader {

    /**
     * @param array $aConfig
     */
    static public function Init($aConfig) {

        // Регистрация автозагрузчика классов
        spl_autoload_register('Loader::Autoload');

        Config::Load($aConfig);
        $sConfigDir = Config::Get('path.dir.config');

        // Load main config
        Config::LoadFromFile($sConfigDir . '/config.php', false);

        // Load additional config files if defined
        $aConfigLoad = F::Str2Array(Config::Get('config_load'));
        if ($aConfigLoad) {
            self::_loadConfigSections($sConfigDir, $aConfigLoad);
        }

        // Includes all *.php files from {path.root.engine}/include/
        $sIncludeDir = Config::Get('path.dir.engine') . '/include/';
        self::_includeAllFiles($sIncludeDir);

        // Load main config level (modules, local & plugins)
        self::_loadConfigFiles($sConfigDir, Config::LEVEL_MAIN);

        // Define app config dir
        $sAppConfigDir = Config::Get('path.dir.app') . '/config/';
        // Ups config level
        Config::ResetLevel(Config::LEVEL_APP);
        // Load application config level (modules, local & plugins)
        self::_loadConfigFiles($sAppConfigDir, Config::LEVEL_APP);

        // Load additional config files (the set could be changed in this point)
        $aConfigLoad = F::Str2Array(Config::Get('config_load'));
        if ($aConfigLoad) {
            self::_loadConfigSections($sAppConfigDir, $aConfigLoad, Config::LEVEL_APP);
        }

        // Load include files of plugins
        self::_loadIncludeFiles(Config::LEVEL_MAIN);
        self::_loadIncludeFiles(Config::LEVEL_APP);


        self::_checkRequiredDirs();

        $aSeekDirClasses = array(
            Config::Get('path.dir.app'),
            Config::Get('path.dir.common'),
            Config::Get('path.dir.engine'),
        );
        Config::Set('path.root.seek', $aSeekDirClasses);

        if (is_null(Config::Get('path.root.subdir'))) {
            if (isset($_SERVER['DOCUMENT_ROOT'])) {
                $sPathSubdir = '/' . F::File_LocalPath(ALTO_DIR, $_SERVER['DOCUMENT_ROOT']);
            } elseif ($iOffset = Config::Get('path.offset_request_url')) {
                $aParts = array_slice(explode('/', F::File_NormPath(ALTO_DIR)), -$iOffset);
                $sPathSubdir = '/' . implode('/', $aParts);
            } else {
                $sPathSubdir = '';
            }
            Config::Set('path.root.subdir', $sPathSubdir);
        }

        // Подгружаем конфиг из файлового кеша, если он есть
        Config::ResetLevel(Config::LEVEL_CUSTOM);
        $aConfig = Config::ReadCustomConfig(null, true);
        if ($aConfig) {
            Config::Load($aConfig, false, null, null, 'custom');
        }

        // Задаем локаль по умолчанию
        F::IncludeLib('UserLocale/UserLocale.class.php');
        // Устанавливаем признак того, является ли сайт многоязычным
        $aLangsAllow = (array)Config::Get('lang.allow');
        if (sizeof($aLangsAllow) > 1) {
            UserLocale::initLocales($aLangsAllow);
            Config::Set('lang.multilang', true);
        } else {
            Config::Set('lang.multilang', false);
        }
        UserLocale::setLocale(
            Config::Get('lang.current'),
            array('local' => Config::Get('i18n.locale'), 'timezone' => Config::Get('i18n.timezone'))
        );
        Config::Set('i18n', UserLocale::getLocale());

        F::IncludeFile((Config::Get('path.dir.engine') . '/classes/core/Engine.class.php'));
    }

    /**
     * @param string $sDirInclude
     */
    static protected function _includeAllFiles($sDirInclude) {

        $aIncludeFiles = glob($sDirInclude . '*.php');
        if ($aIncludeFiles) {
            foreach ($aIncludeFiles as $sPath) {
                F::IncludeFile($sPath);
            }
        }
    }

    /**
     * @param string $sConfigDir
     * @param int    $nConfigLevel
     */
    static protected function _loadConfigFiles($sConfigDir, $nConfigLevel) {

        // * Загружаем конфиги модулей вида /config/modules/[module_name]/config.php
        $sDirConfig = $sConfigDir . '/modules/';
        $aFiles = glob($sDirConfig . '*/config.php');
        if ($aFiles) {
            foreach ($aFiles as $sConfigFile) {
                $sDirModule = basename(dirname($sConfigFile));
                $aConfig = F::IncludeFile($sConfigFile, true, true);
                if (!empty($aConfig) && is_array($aConfig)) {
                    $sKey = 'module.' . $sDirModule;
                    Config::Load(array($sKey => $aConfig), false, null, $nConfigLevel, $sConfigFile);
                }
            }
        }

        /*
         * LS-compatible
         * Подгружаем файлы локального и продакшн-конфига
         */
        $sConfigFile = $sConfigDir . '/config.local.php';
        if (F::File_Exists($sConfigFile)) {
            if ($aConfig = F::File_IncludeFile($sConfigFile, true, Config::Get())) {
                Config::Load($aConfig, true, null, $nConfigLevel, $sConfigFile);
            }
        }

        /*
         * Загружает конфиг-файлы плагинов вида /plugins/[plugin_name]/config/*.php
         * и include-файлы вида /plugins/[plugin_name]/include/*.php
         */
        $sPluginsDir = F::GetPluginsDir($nConfigLevel == Config::LEVEL_APP);
        if ($aPluginsList = F::GetPluginsList()) {
            $aPluginsList = array_map('trim', $aPluginsList);
            foreach ($aPluginsList as $sPlugin) {
                // Загружаем все конфиг-файлы плагина
                $aConfigFiles = glob($sPluginsDir . '/' . $sPlugin . '/config/*.php');
                if ($aConfigFiles) {
                    foreach ($aConfigFiles as $sConfigFile) {
                        $aConfig = F::IncludeFile($sConfigFile, true, true);
                        if (!empty($aConfig) && is_array($aConfig)) {
                            // Если конфиг этого плагина пуст, то загружаем массив целиком
                            $sKey = 'plugin.' . $sPlugin;
                            if (!Config::isExist($sKey)) {
                                Config::Set($sKey, $aConfig, null, $nConfigLevel, $sConfigFile);
                            } else {
                                // Если уже существуют привязанные к плагину ключи,
                                // то сливаем старые и новое значения ассоциативно-комбинированно
                                /** @see AltoFunc_Array::MergeCombo() */
                                Config::Set($sKey, F::Array_MergeCombo(Config::Get($sKey), $aConfig), null, $nConfigLevel. $sConfigFile);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Загружает include-файлы вида /plugins/[plugin_name]/include/*.php
     *
     * @param int $nConfigLevel
     */
    static protected function _loadIncludeFiles($nConfigLevel) {

        $sPluginsDir = F::GetPluginsDir($nConfigLevel == Config::LEVEL_APP);
        if ($aPluginsList = F::GetPluginsList()) {
            $aPluginsList = array_map('trim', $aPluginsList);
            foreach ($aPluginsList as $sPlugin) {
                // Подключаем include-файлы плагина
                $aIncludeFiles = glob($sPluginsDir . '/' . $sPlugin . '/include/*.php');
                if ($aIncludeFiles) {
                    foreach ($aIncludeFiles as $sPath) {
                        F::IncludeFile($sPath);
                    }
                }
            }
        }
    }

    /**
     * @param string $sConfigDir
     * @param array  $aConfigSections
     * @param int    $nConfigLevel
     */
    protected static function _loadConfigSections($sConfigDir, $aConfigSections, $nConfigLevel = 0) {

        foreach ($aConfigSections as $sName) {
            $sFile = $sConfigDir . '/' . $sName . '.php';
            if (F::File_Exists($sFile)) {
                self::_loadSectionFile($sFile, $sName, $nConfigLevel);
            }

            $sFile = $sConfigDir . '/' . $sName . '.local.php';
            if (F::File_Exists($sFile)) {
                self::_loadSectionFile($sFile, $sName, $nConfigLevel);
            }
        }
    }

    /**
     * Load subconfig file
     *
     * @param string $sFile
     * @param string $sName
     * @param int    $nConfigLevel
     */
    static protected function _loadSectionFile($sFile, $sName, $nConfigLevel = 0) {

        if (F::File_Exists($sFile)) {
            $aCfg = F::File_IncludeFile($sFile, true, true);
            if ($aCfg) {
                if (isset($aCfg[$sName])) {
                    $aConfig = $aCfg[$sName];
                } else {
                    $aConfig = $aCfg;
                }
                Config::Load(array($sName => $aConfig), false, null, $nConfigLevel, $sFile);
            }
        }
    }

    /**
     * Check required dirs
     */
    static protected function _checkRequiredDirs() {

        if (!F::File_CheckDir(Config::Get('path.dir.app'), false)) {
            die('Application folder "' . F::File_LocalDir(Config::Get('path.dir.app')) . '" does not exist');
        }
        if (!F::File_CheckDir(Config::Get('path.tmp.dir'), true)) {
            die('Required folder "' . F::File_LocalDir(Config::Get('path.tmp.dir')) . '" does not exist');
        } elseif (!is_writeable(Config::Get('path.tmp.dir'))) {
            die('Required folder "' . F::File_LocalDir(Config::Get('path.tmp.dir')) . '" does not writeable');
        }
        if (!F::File_CheckDir(Config::Get('path.runtime.dir'), true)) {
            die('Required folder "' . F::File_LocalDir(Config::Get('path.runtime.dir')) . '" does not exist');
        } elseif (!is_writeable(Config::Get('path.runtime.dir'))) {
            die('Required folder "' . F::File_LocalDir(Config::Get('path.runtime.dir')) . '" does not writeable');
        }
    }

    /**
     * Автоопределение класса или файла экшена
     *
     * @param   string $sAction
     * @param   string $sEvent
     * @param   bool   $bFullPath
     *
     * @return  string|null
     */
    static public function SeekActionClass($sAction, $sEvent = null, $bFullPath = false) {

        $bOk = false;
        $sFileName = 'Action' . ucfirst($sAction) . '.class.php';

        // Сначала проверяем файл экшена среди стандартных
        $aSeekDirs = array(Config::Get('path.dir.app'), Config::Get('path.dir.common'));
        if ($sActionFile = F::File_Exists('/classes/actions/' . $sFileName, $aSeekDirs)) {
            $sActionClass = 'Action' . ucfirst($sAction);
            $bOk = true;
        } else {
            // Если нет, то проверяем файл экшена среди плагинов
            $aPlugins = F::GetPluginsList();
            foreach ($aPlugins as $sPlugin) {
                if ($sActionFile = F::File_Exists('plugins/' . $sPlugin . '/classes/actions/' . $sFileName, $aSeekDirs)
                ) {
                    $sActionClass = 'Plugin' . F::StrCamelize($sPlugin) . '_Action' . ucfirst($sAction);
                    $bOk = true;
                    break;
                }
            }
        }
        if ($bOk) {
            return $bFullPath ? $sActionFile : $sActionClass;
        }
        return null;
    }

    /**
     * @param string $sFile
     * @param string $sCheckClassname
     *
     * @return bool|mixed
     */
    static protected function _includeFile($sFile, $sCheckClassname = null) {

        if (class_exists('F', false)) {
            $xResult = F::IncludeFile($sFile);
        } else {
            $xResult = include_once $sFile;
        }
        if ($sCheckClassname) {
            return class_exists($sCheckClassname, false);
        }
        return $xResult;
    }

    /**
     * Автозагрузка классов
     *
     * @param string $sClassName    Название класса
     *
     * @return bool
     */
    static public function Autoload($sClassName) {

        if (Config::Get('classes')) {
            if ($sParentClass = Config::Get('classes.alias.' . $sClassName)) {
                return self::_classAlias($sParentClass, $sClassName);
            }
            if (self::_autoloadDefinedClass($sClassName)) {
                return true;
            }
        }

        if (class_exists('Engine', false) && (E::GetStage() >= E::STAGE_INIT)) {
            $aInfo = E::GetClassInfo($sClassName, E::CI_CLASSPATH | E::CI_INHERIT);
            if ($aInfo[E::CI_INHERIT]) {
                $sInheritClass = $aInfo[E::CI_INHERIT];
                $sParentClass = E::getInstance()->Plugin_GetParentInherit($sInheritClass);
                return self::_classAlias($sParentClass, $sClassName);
            } elseif ($aInfo[E::CI_CLASSPATH]) {
                return self::_includeFile($aInfo[E::CI_CLASSPATH], $sClassName);
            }
        }
        if (self::_autoloadPSR0($sClassName)) {
            return true;
        }
        return false;
    }

    /**
     * Try to load class using config info
     *
     * @param string $sClassName
     *
     * @return bool
     */
    static protected function _autoloadDefinedClass($sClassName) {

        if ($sFile = Config::Get('classes.class.' . $sClassName)) {
            // defined file name for the class
            if (is_array($sFile)) {
                $sFile = isset($sFile['file']) ? $sFile['file'] : null;
            }
            if ($sFile) {
                return self::_includeFile($sFile, $sClassName);
            }
        }
        // May be Namespace_Package or Namespace\Package
        if (strpos($sClassName, '\\') || strpos($sClassName, '_')) {
            $aPrefixes = Config::Get('classes.prefix');
            foreach ($aPrefixes as $sPrefix => $aOptions) {
                if (strpos($sClassName, $sPrefix) === 0) {
                    // defined prefix for vendor/library
                    if (is_array($aOptions)) {
                        if (isset($aOptions['path'])) {
                            $sPath = $aOptions['path'];
                        } else {
                            $sPath = '';
                        }
                    } else {
                        $sPath = $aOptions;
                    }
                    if ($sPath) {
                        if (isset($aOptions['classmap'][$sClassName])) {
                            $sFile = $sPath . '/' . $aOptions['classmap'][$sClassName];
                            return self::_includeFile($sFile, $sClassName);
                        }
                        return self::_autoloadPSR0($sClassName, $sPath);
                    }
                }
            }
        }
        return false;
    }

    static protected $_aFailedClasses = array();

    /**
     * Try to load class using PRS-0 naming standard
     *
     * @param string       $sClassName
     * @param string|array $xPath
     *
     * @return bool
     */
    static protected function _autoloadPSR0($sClassName, $xPath = null) {

        if (!$xPath) {
            $xPath = Config::Get('path.dir.libs');
        }

        $sCheckKey = serialize(array($sClassName, $xPath));
        if (!isset(self::$_aFailedClasses[$sCheckKey])) {
            if (strpos($sClassName, '\\')) {
                // Namespaces
                $sClassName = str_replace('\\', DIRECTORY_SEPARATOR, $sClassName);
            } elseif (strpos($sClassName, '_')) {
                // Old style with '_'
                $sClassName = str_replace('_', DIRECTORY_SEPARATOR, $sClassName);
            } else {
                return false;
            }
            if ($sFile = F::File_Exists($sClassName . '.php', $xPath)) {
                return self::_includeFile($sFile, $sClassName);
            } elseif ($sFile = F::File_Exists($sClassName . '.class.php', $xPath)) {
                return self::_includeFile($sFile, $sClassName);
            }
        }
        self::$_aFailedClasses[$sCheckKey] = false;
        return false;
    }

    /**
     * @var array Array of class aliases
     */
    static protected $_aClassAliases = array();

    /**
     * Creates an alias for a class
     *
     * @param string $sOriginal
     * @param string $sAlias
     * @param bool   $bAutoload
     *
     * @return bool
     */
    static protected function _classAlias($sOriginal, $sAlias, $bAutoload = TRUE) {

        $bResult = class_alias($sOriginal, $sAlias, $bAutoload);
        if (defined('DEBUG') && DEBUG) {
            self::$_aClassAliases[$sAlias] = array(
                'original' => $sOriginal,
                'autoload' => $bAutoload,
                'result' => $bResult,
            );
        }

        return $bResult;
    }

    /**
     * Returns of class aliases
     *
     * @return array
     */
    static public function GetAliases() {

        return self::$_aClassAliases;
    }
}

// EOF