<?php

namespace Teknisa\Libs\Service;

use Teknisa\Libs\Exception\Login as LoginException;
use Teknisa\Libs\Util\Utilities;
use Teknisa\Libs\Factory\Menu as MenuFactory;
use Teknisa\Libs\Util\InstanceProvider;

class Menu {
    const ALL_ELEMENTS_VISIBLE = 'ALL';
    const ZHU_LOG_PACKAGE = 'zhulog';
    const ZHU_LOG_CONTAINER = 'log';

    protected $menuConfigPath;
    protected $customizationMenus;
    protected MenuFactory $menuFactory;

    public function __construct(MenuFactory $menuFactory) {
        $this->menuFactory = $menuFactory;
        $config = json_decode(file_get_contents(__DIR__ . '/../../../../config/config.json'), true);
        $this->menuConfigPath = $config['menuConfigPath'];
    }

    /**
     * @param bool $concatenateGroupMenu
     * @param array $visibleMenus
     * @param array $expiredMenus
     * @param string $urlOrigin
     * @param string $currentModule
     * @param string $currentLanguage
     * @return array
     * @throws \Exception
     */
    public function buildMenu($concatenateGroupMenu, $visibleMenus, $expiredMenus, $urlOrigin, $currentModule, $currentLanguage) {
        $this->customizationMenus = $this->getCustomizationMenus();

        $menuConfig     = $this->getMenuConfig();
        $modulesConfig  = $this->getJsonMetadata($menuConfig['modulesFile'], true);
        $menusMetaData  = $this->getMenusMetadata($menuConfig, $modulesConfig, $currentLanguage);
        $menus          = $this->joinMenus($menusMetaData['MENUS']);
        $menus          = $this->mergeMenus($menus, $urlOrigin, $modulesConfig, $currentModule, $menusMetaData['MODULES_TRANSLATIONS']);
        
        $this->updateInvisibleAndExpiredMenus($menus, $visibleMenus, $expiredMenus);

        if($concatenateGroupMenu) {
            $menus = $this->concatenateGroupMenu($menuConfig, $menus, $modulesConfig, $menusMetaData['MODULES_TRANSLATIONS']);
        }

        $this->fixCustomizationMenuTree($menus);
        return array(
            'MENUS' => $menus,
            'ROUTES_TRANSLATE' => $this->generateRoutesTranslateDataSource($menusMetaData['ROUTES_TRANSLATE'])
        );
    }

    /**
     * @return array
     * @throws \Exception
     */
    private function getMenuConfig()
    {
        $menuConfig = @file_get_contents($this->menuConfigPath);
        if (!$menuConfig) {
            LoginException::menuConfigNotFound();
        }
        return json_decode($menuConfig, true);
    }

    /**
     * @param $menuConfig
     * @return array
     * @throws \Exception
     */
    private function getContainersJson($menuConfig)
    {
        $containers = @file_get_contents($this->updateFilePath($menuConfig['containersFile']));
        if (!$containers) {
            LoginException::containersNotFound();
        }
        return json_decode($containers, true);
    }

    /**
     * @param $modulesFilePath
     * @param bool $isException
     * @throws \Exception
     * @return array
     */
    private function getJsonMetadata($modulesFilePath, $isException = false)
    {
        $modulesConfig = @file_get_contents($this->updateFilePath($modulesFilePath));
        if (!$modulesConfig && $isException) {
            LoginException::modulesConfigNotFound();
        }
        return $modulesConfig ? json_decode($modulesConfig, true) : $modulesConfig;
    }

    /**
     * @param $modulesFilePath
     * @param $currentLanguage
     * @throws \Exception
     * @return array
     */
    private function getTranslationJsonMetadata($modulesFilePath, $currentLanguage)
    {
        $wordsPath    = 'words/' . $currentLanguage . '.json';
        $menuFile     = strpos($modulesFilePath, 'menu.json') ? 'menu.json' : 'aclStruct.json';
        if(!strpos($modulesFilePath, 'modules')) {
            $modulesFilePath = '/mobile/' . $modulesFilePath;
        }
        $languageFile = str_replace($menuFile, $wordsPath, $modulesFilePath);
        $fileContents = @file_get_contents($this->updateFilePath($languageFile));
        return $fileContents ? json_decode($fileContents, true) : $fileContents;
    }

    /**
     * @param $menus
     * @param $devOpsId
     */
    private function addDevOpsIdInMenus(&$menus, $devOpsId) {
        foreach($menus as $key => &$menu) {
            $menu['devOpsId'] = $devOpsId;
            if(isset($menu['menus'])) {
                $this->addDevOpsIdInMenus($menu['menus'], $devOpsId);
            }
        }
    }

    /**
     * @param $menuConfig
     * @param $modulesConfig
     * @param $currentLanguage
     * @return array
     */
    private function getMenusMetadata($menuConfig, $modulesConfig, $currentLanguage)
    {
        $menus = $routesTranslate = $modulesTranslations = array();
        $keyModulesConfig = array_keys($modulesConfig);
        $productMenu = $this->getJsonMetadata($menuConfig['parentAclStructFile']);
        if ($productMenu) {
            $this->addDevOpsIdInMenus($productMenu, $modulesConfig['product']['devOpsId']);
            $menus[] = $productMenu;
        }
        $modulesMenuPaths = $menuConfig['modulesMenuFile'];
        if(isset($menuConfig['parentMenuConfig'])) {
            $parentMenuConfig = $this->getJsonMetadata($menuConfig['parentMenuConfig']);
            if ($parentMenuConfig) {
                $modulesMenuPaths = $parentMenuConfig['modulesMenuFile'];
            }
        }
        foreach ($modulesMenuPaths as $moduleName => $modulePath) {
            if (in_array($moduleName, $keyModulesConfig)) {
                $moduleMenu = $this->getJsonMetadata($modulePath);
                if ($moduleMenu) {
                    $this->addDevOpsIdInMenus($moduleMenu, $modulesConfig[$moduleName]['devOpsId']);
                    $menus[] = $moduleMenu;
                }
                $routeTranslate = $this->getRouteTranslate($menuConfig, $modulePath, $moduleName);
                if (!empty($routeTranslate)) {
                    $routesTranslate = array_merge($routesTranslate, $routeTranslate);
                }
                $translate = $this->getTranslationJsonMetadata($modulePath, $currentLanguage);
                if (!empty($translate)) {
                    $modulesTranslations = array_merge($modulesTranslations, $translate);
                }
            }
        }
        return array(
            'MENUS' => $menus,
            'ROUTES_TRANSLATE' => $routesTranslate,
            'MODULES_TRANSLATIONS' => $modulesTranslations
        );
    }

    /**
     * @param $menus
     * @return array
     */
    private function joinMenus($menus)
    {
        $menusAux = array();
        foreach ($menus as $menuGrouped) {
            $menusAux = array_merge($menusAux, $menuGrouped);
        }
        return $menusAux;
    }

    /**
     * @param $menus
     * @param $filterCache
     * @param $urlOrigin
     * @param $modulesConfig
     * @param $currentModule
     * @param $modulesTranslations
     * @return array
     */
    private function filterMenus($menus, $filterCache, $urlOrigin, $modulesConfig, $currentModule, $modulesTranslations) {
        $menusAux = [];        
        $modulesNotRedirect = Utilities::getModulesNotRedirectParameter();
        foreach($menus as $menu) {
            $show = true;
            $menuName = isset($menu['name']) ? $menu['name'] : $menu['label'];
            if(isset($filterCache[$menuName])) {
                $show = false;
                $cachedMenu = $filterCache[$menuName];
                if(isset($cachedMenu['menus'])) {
                    $cachedMenu['menus'] = $this->overrideMenus($cachedMenu, $menu);
                    foreach($menusAux as &$menuAux) {
                        $menuAuxName = isset($menuAux['name']) ? $menuAux['name'] : $menuAux['label'];
                        $cachedMenuName = isset($cachedMenu['name']) ? $cachedMenu['name'] : $cachedMenu['label'];
                        if($menuAuxName == $cachedMenuName) {
                            $menuAux['menus'] = array_merge($menuAux['menus'], $cachedMenu['menus']);
                        }
                    }
                }
            } else {
                $cachedMenu = $filterCache[$menuName] = $menu;
            }

            if(isset($cachedMenu['menus'])) {
                foreach($cachedMenu['menus'] as $childrenMenu) {
                    if(isset($childrenMenu['menus'])) {
                        $cache = [];
                        $childrenMenu['menus'] = $this->filterMenus($childrenMenu['menus'], $cache, $urlOrigin,
                            $modulesConfig, $currentModule, $modulesTranslations);
                    }
                }
            }

            if($urlOrigin) {
                $menu = $this->updateMenuURL($menu, $urlOrigin, $modulesConfig, $currentModule, $modulesNotRedirect);
            }

            $menuLabel = isset($menu['label']) ? $menu['label'] : null;
            if($menuLabel && $modulesTranslations && isset($modulesTranslations[$menuLabel])) {
                $menu['label'] = $modulesTranslations[$menuLabel];
            }

            if($show) {
                $menusAux[] = $menu;
            }
        }

        return $menusAux;
    }

    private function setCustomizationMenus(&$menu, $setApplicationIcon = false) {
        foreach($this->customizationMenus as $idx => $customizationMenu) {
            if($customizationMenu['parentUniqueId'] == $menu['id']) {
                foreach($this->customizationMenus as $tmpIdx => $tmpCustomMenu) {
                    if($tmpCustomMenu['parentUniqueId'] == $customizationMenu['id']) {
                        $tmpCustomMenu['parent'] = $customizationMenu['name'];
                        $customizationMenu['menus'] = empty($customizationMenu['menus']) ? array() : $customizationMenu['menus'];
                        $customizationMenu['menus'][] = $tmpCustomMenu;
                        if(isset($customizationMenu['events'])) {
                            unset($customizationMenu['events']);
                        }
                        unset($this->customizationMenus[$tmpIdx]);
                    }
                }
                $customizationMenu['parent'] = $menu['name'];
                if($setApplicationIcon) {
                    $customizationMenu['icon'] = 'application';
                }
                $menu['menus'] = empty($menu['menus']) ? array() : $menu['menus'];
                $menu['menus'][] = $customizationMenu;
                unset($this->customizationMenus[$idx]);
            }
        }
    }

    private function fixCustomizationMenuTree(&$menus) {
        foreach($menus as &$menu) {
            foreach($this->customizationMenus as $key => $customMenu) {
                if($customMenu['parentUniqueId'] == $menu['id']) {
                    $customMenu['parent'] = $menu['name'];
                    $menu['menus'] = empty($menu['menus']) ? array() : $menu['menus'];
                    $menu['menus'][] = $customMenu;
                    if(isset($menu['events'])) {
                        unset($menu['events']);
                    }
                    unset($this->customizationMenus[$key]);
                }
            }
            if(isset($menu['menus'])) {
                $this->fixCustomizationMenuTree($menu['menus']);
            }
        }
    }

    private function getCustomizationMenus() {
        $loginCustomizationMenus = InstanceProvider::getSession()->get('LOGIN_CUSTOMIZATION_MENUS');
        $customizationMenus = array();
        $newCustomMenu = array();
        if(!empty($loginCustomizationMenus)) {
            foreach($loginCustomizationMenus as $customMenu) {
                $route = empty($customMenu['ROUTE']) || $customMenu['ROUTE'][0] == '/' ? $customMenu['ROUTE'] : '/' . $customMenu['ROUTE'];
                $name = empty($customMenu['ROUTE']) ? $customMenu['UNIQUE_ID'] : ltrim(str_replace('/', '-', $route), '-');
                $menu = array (
                    'id'              => $customMenu['UNIQUE_ID'],
                    'label'           => $customMenu['LABEL'],
                    'order'           => $customMenu['NRORDEM'],
                    'icon'            => null,
                    'isVisible'       => true,
                    'name'            => $name,
                    'route'           => $route,
                    'parent'          => null,
                    'parentUniqueId'  => $customMenu['PARENT_UNIQUE_ID'],
                    'isCustomization' => true,
                    'events'          => array (
                        array (
                            'name' => 'MenuOnClick',
                            'code' => 'LibCustomizationController.customizationMenuOnClick(args.owner)'
                        )
                    )
                );
                $customizationMenus[] = $menu;
            }
        }
        return $customizationMenus;
    }

    private function updateMenuURL($menu, $urlOrigin, $modulesConfig, $currentModule, $modulesNotRedirect) {
        if(isset($menu['windowName'])) {
            $hash   = strpos($menu['windowName'], '#') ? '#' : '%23';
            $window = explode($hash, $menu['windowName']);
            if(count($window) == 1) {
                $module = $currentModule;
            } else {
                $module = explode('_', $window[0])[0];
            }

            if($module != $currentModule && isset($modulesConfig[$module]['baseUrl']) && 
               (empty($modulesNotRedirect) || !in_array($module, $modulesNotRedirect))
              ) {
                $menu['url'] = $urlOrigin . '/' . $modulesConfig[$module]['baseUrl'] . "/#/" . $menu['windowName'];
                $menu['msContainerName'] = $menu['windowName'];
                $menu['events'] = array(
                    array(
                        'name' => 'MenuOnClick',
                        'code' => 'LibUtilities.openMicroServiceUrl(args)'
                    )
                );
                unset($menu['windowName']);
            }
        }

        return $menu;
    }

    /**
     * @param $uncheckedMenus
     * @param $urlOrigin
     * @param $modulesConfig
     * @param $currentModule
     * @param $modulesTranslations
     * @return array
     */
    private function mergeMenus($uncheckedMenus, $urlOrigin, $modulesConfig, $currentModule, $modulesTranslations) {
        $filterCache  = [];
        $checkedMenus = $this->filterMenus($uncheckedMenus, $filterCache, $urlOrigin, $modulesConfig, $currentModule, $modulesTranslations);
        foreach($checkedMenus as &$checkedMenu) {
            $this->setCustomizationMenus($checkedMenu);
            if(isset($checkedMenu['menus'])) {
                $checkedMenu['menus'] = $this->mergeMenus($checkedMenu['menus'], $urlOrigin, $modulesConfig, $currentModule, $modulesTranslations);
            }
        }

        return $checkedMenus;
    }

    /**
     * @param $cachedMenu
     * @param $menu
     * @return array
     */
    private function overrideMenus($cachedMenu, $menu)
    {
        $cachedMenu['menus'] = array_merge($cachedMenu['menus'], $menu['menus']);
        $menusToOverride = array_filter($cachedMenu['menus'], function ($menu) {
            return isset($menu['override']);
        });
        $cachedMenusAux = [];
        foreach ($cachedMenu['menus'] as $cached) {
            $shouldFilter = false;
            foreach ($menusToOverride as &$menuToOverride) {
                $overrideMenuId = isset($menuToOverride['id']) ? $menuToOverride['id'] : null;
                $cachedMenuId = isset($cached['id']) ? $cached['id'] : false;
                $shouldFilter = $cachedMenuId        !== $overrideMenuId &&
                    ((isset($cached['windowName']) && $cached['windowName'] == $menuToOverride['override']) ||
                        $cached['name'] == $menuToOverride['override']);
                if ($shouldFilter && isset($cached['menus']) && isset($menuToOverride['menus'])) {
                    $menuToOverride['menus'] = array_merge($menuToOverride['menus'], $cached['menus']);
                }
                if ($shouldFilter) {
                    break;
                }
            }
            if (!$shouldFilter) {
                $cachedMenusAux[] = $cached;
            }
        }
        return $cachedMenusAux;
    }

    /**
     * @param $menus
     * @param $visibleMenus
     * @param $expiredMenus
     */
    private function updateInvisibleAndExpiredMenus(&$menus, $visibleMenus, $expiredMenus) {
        $allMenus = !empty($visibleMenus) && $visibleMenus[0] == self::ALL_ELEMENTS_VISIBLE;
        foreach($menus as $key => &$menu) {
            if(empty($menu['isCustomization'])) {
                if ($this->isExpiredMenu($expiredMenus, $menu, $allMenus)) {
                    $menu['disabled'] = true;
                    $menu['icon']     = 'error';
                    if(isset($menu['windowName'])) {
                        unset($menu['windowName']);
                    }
                    if(isset($menu['menus'])) {
                        $this->updateInvisibleAndExpiredMenus($menu['menus'], $visibleMenus, $expiredMenus);
                    } else {
                        $menu['events'] = array(
                            array(
                                'name' => 'MenuOnClick',
                                'code' => 'LibLoginController.expiredMenuOnClick()'
                            )
                        );
                    }
                } else if ($this->isInvisibleMenu($visibleMenus, $menu, $allMenus)) {
                    unset($menus[$key]);
                } else {
                    if(isset($menu['menus'])) {
                        $this->updateInvisibleAndExpiredMenus($menu['menus'], $visibleMenus, $expiredMenus);
                    }
                }
            }
        }
        $menus = array_values($menus);
    }

    /**
     * @param $menuConfig
     * @param $aclStruct
     * @return array
     */
    private function concatenateGroupMenu($menuConfig, $aclStruct, $modulesConfig, $modulesTranslations)
    {
        $containers = $this->getContainersJson($menuConfig);
        $menus = [];
        foreach ($containers as $project) {
            if (isset($project['groupMenu'])) {
                foreach ($project['groupMenu'] as &$groupMenu) {
                    $groupMenu['devOpsId'] = $modulesConfig['product']['devOpsId'];
                    if(!empty($groupMenu['label']) && $modulesTranslations && isset($modulesTranslations[$groupMenu['label']])) {
                        $groupMenu['label'] = $modulesTranslations[$groupMenu['label']];
                    }
                    foreach ($aclStruct as $struct) {
                        if (isset($struct['parent']) && $struct['parent'] == $groupMenu['name']) {
                            $groupMenu['menus'][] = $struct;
                        }
                    }
                    $this->setCustomizationMenus($groupMenu, true);
                }
                $menus = $project['groupMenu'];
            }
        }
        return $menus;
    }

    /**
     * @param $visibleMenus
     * @param $menu
     * @param $allMenus
     * @return bool
     */
    private function isInvisibleMenu($visibleMenus, $menu, $allMenus)
    {
        return (!$allMenus &&
            (
                (isset($menu['isVisible']) && $menu['isVisible'] == false) ||
                (!isset($menu['id'])) ||
                ($visibleMenus !== null && !in_array($menu['id'], $visibleMenus))
            )
        );
    }

    /**
     * @param $expiredMenus
     * @param $menu
     * @param $allMenus
     * @return bool
     */
    private function isExpiredMenu($expiredMenus, $menu, $allMenus)
    {
        return (!$allMenus &&
            (
                $expiredMenus &&
                (isset($menu['isVisible']) && $menu['isVisible']) &&
                (isset($menu['id']) && in_array($menu['id'], $expiredMenus))
            )
        );
    }

    private function updateFilePath($path) {
        return Utilities::getProductBasePath() . $path;
    }

    /**
     * @param $menuConfig
     * @param $modulePath
     * @param $moduleName
     * @return array
     */
    private function getRouteTranslate($menuConfig, $modulePath, $moduleName)
    {
        $modulePathArr = explode('/', $modulePath);
        if ($moduleName == Utilities::PRODUCT_MENU_NAME) {
            $modulePathArr = explode('/', $menuConfig['parentAclStructFile']);
        }
        array_pop($modulePathArr);
        $routeTranslateFile = join('/', $modulePathArr) . '/routeTranslate.json';
        return $this->getJsonMetadata($routeTranslateFile);
    }

    private function generateRoutesTranslateDataSource($routesTranslate) {
        array_walk($routesTranslate, function(&$routeLabel, $route) {
            $routeLabel = array(
                'ROUTE' => $route,
                'ROUTE_LABEL' => $routeLabel
            );
        });

        return array_values($routesTranslate);
    }
    
    public function findFavorites($operator, $nrorg) {
        try {
            return $this->menuFactory->findFavorites($operator, $nrorg);
        } catch (\Exception $e) {}
    }
    
    public function changeFavorites($item, $status, $operator, $nrorg) {
        try {
            if($status == 'add') {
                 $this->menuFactory->addFavorites($item['id'], $item['name'], $item['label'], $operator, $nrorg);
            } else {
                if(\Zeedhi\Framework\Util\Functions::arrayKeyExists('id', $item)){
                    $this->menuFactory->deleteFavorites($item['id'], $operator, $nrorg);
                } else {
                    foreach($item as $i) {
                        $this->menuFactory->deleteFavorites($i['id'], $operator, $nrorg);
                    }
                }
            }
        } catch (\Exception $e) {}
    }  
} 