<?php
/**
 * Site Menu Class
 *
 * This code is used to make up Site Menu plugin
 *
 * @category Plugins
 * @package  Mtc_Site_Menu
 * @author   mtc. Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
 * @author   mtc. Peter Carlyle <peter.carlyle@mtcmedia.co.uk>
 * @author   mtc. Dawid Bestrzynski <dawid.bestrzynski@mtcmedia.co.uk>
 */

namespace Mtc\Plugins\SiteMenu\Classes;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Collection;
use Twig\Environment;
use CategoryHelper;
use ProductFilter;
use Mtc\Shop\Brand;
use CMSNav;
use Util;

/**
 * Site Menu plugin Class.
 * Creates and manipulates with menu items.
 * Extends Eloquent model
 *
 * @author   mtc. Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
 *
 * @version  2017-04-18
 */
class SiteMenu extends Model
{
    /**
     * @var String Classes associated table
     */
    protected $table = 'site_menu';

    /**
     * @var Array List of fillable variables
     */
    protected $fillable = [
        'name',
        'url',
        'target',
        'parent_id',
        'published',
        'category',
        'order'
    ];

    /**
     * @var boolean Disables use of eloquent timestamps
     */
    public $timestamps = false;

    /**
     * SiteMenu::save()
     *
     * Wrapper for save function. Invalidates cache for all menus
     *
     * @access public
     *
     * @param array $options
     *
     * @return null
     */
    public function save(array $options = [])
    {
        parent::save($options);

        foreach (self::getMenus() as $menu) {
            self::invalidateCache($menu);
        }
        // return the status of the save operation
        return $this->wasRecentlyCreated;
    }

    /**
     * SiteMenu::destroy()
     *
     * Wrapper for destroy function. Invalidates cache
     * Refreshed cache of all menus
     *
     * @access public
     *
     * @param $ids
     *
     * @return null
     */
    public static function destroy($ids)
    {
        parent::destroy($ids);

        foreach (self::getMenus() as $menu) {
            self::invalidateCache($menu);
        }
    }

    /**
     * SiteMenu::renderMenu()
     *
     * Retrieve menu for site usage
     * Nothing returned as called via hooks and they do not support value returning
     *
     * @access public
     * @static
     *
     * @param  String $menu   name of menu called as String
     *
     * @return SiteMenu[] a list of SiteMenu entries
     */
    public static function fetchTree($menu)
    {

        if (!is_dir(self::getCacheDir())) {
            mkdir(self::getCacheDir());
        }

        // Make sure cache refreshes every 5 minutes
        if (!file_exists(self::getCacheFile($menu))
            || time() - filemtime(self::getCacheFile($menu)) > Util::SECONDS_IN_A_MINUTE * 5
        ) {
            $site_menu = self::where('name', $menu)->first();
            $menu_tree = $site_menu->getSiteMenuTree(false);
            $fh        = fopen(self::getCacheFile($menu), 'w');
            fwrite($fh, serialize($menu_tree));
        } else {
            $menu_tree = unserialize(file_get_contents(self::getCacheFile($menu)));
        }

        return $menu_tree;
    }

    /**
     * SiteMenu::renderMenu()
     *
     * Render menu for site usage
     * Nothing returned as called via hooks and they do not support value returning
     *
     * @access public
     * @static
     *
     * @param  String           $menu    name of menu called as String
     * @param  array            $options Styling options passed
     * @param  Environment $twig    Twig render handler
     *
     * @return null
     */
    public static function renderMenu($menu, $options = [], \Twig\Environment $twig)
    {
        //Default template menu
        $template_location = 'SiteMenu/menu.twig';

        //Check if megamenu option enabled
        if (isset($options['mega_menu']) && $options['mega_menu'] === true) {
            $template_location = 'SiteMenu/mega_menu.twig';
        }

        // fallback to container class defined in settings
        if (!isset($options['first_container_class'])) {
            $options['first_container_class'] = SITE_MENU_FIRST_CONTAINER_CLASS;
        }
        $current_menu = self::where('name', $menu)->first();

        if (!$current_menu->published) {
            return false;
        }

        $options['container_class'] = $current_menu->css_class;
        $menu = self::fetchTree($menu);
        if (count($menu)) {
            echo $twig->display($template_location, [
                'items'     => $menu,
                'options'   => $options,
                'top_level' => true,
            ]);
        }
    }

    /**
     * SiteMenu::allBrands()
     *
     * Retrieve root menu items
     *
     * @access public
     * @return array list of all brands
     */
    public function allBrands()
    {
        return Brand::getAllBrands();
    }

    /**
     * SiteMenu::listCategories()
     *
     * Retrieve list of categories to display
     *
     * @access public
     * @return array list of categories as an array
     */
    public function listCategories()
    {
        if ($this->category_list == -1) {
            return CategoryHelper::getCategoryTree(0, 1);
        } else {
            return CategoryHelper::getCategoryTree($this->category_list, 1);
        }
    }

    /**
     * SiteMenu::invalidateCache()
     *
     * Delete the cache file
     *
     * @access public
     * @static
     *
     * @param String $menu to remove the specific cache file for the menu being updated
     *
     * @return null
     */
    public static function invalidateCache($menu)
    {
        if (file_exists(self::getCacheFile($menu['name']))) {
            unlink(self::getCacheFile($menu['name']));
        }
    }

    /**
     * SiteMenu::getCacheFile()
     *
     * Retrieve cache file name
     * Cache file is built to be SITE and fork independent
     *
     * @access public
     * @static
     *
     * @param $menu
     *
     * @return string path to cache file
     */
    public static function getCacheFile($menu)
    {
        $db_connection = config('database.default');
        $db_name = config("database.connections.{$db_connection}.database");
        return self::getCacheDir() . '/' . SITE . '_' . $db_name. '_site_menu_' . $menu . '.cache';
    }

    /**
     * SiteMenu::getCacheDir()
     *
     * Return path to cache dir
     * This will be cache dir outside public_html
     *
     * @access public
     * @static
     * @return string path to cache dir
     */
    public static function getCacheDir()
    {
        return base_path('cache');
    }

    /**
     * SiteMenu::getMenus()
     *
     * Retrieve all top level Site menus
     * Used to get list of menus for use in admin area
     *
     * @access   public
     * @static
     * @return Collection
     *
     * @author   mtc. Dawid Bestrzynski <dawid.bestrzynski@mtcmedia.co.uk>
     */
    public static function getMenus()
    {
        return self::where('parent_id', 0)->get();
    }

    /**
     * SiteMenu::getSubMenu()
     *
     * Retrieve a collection of child SiteMenus either all or only direct ones
     * @author   mtc. Dawid Bestrzynski <dawid.bestrzynski@mtcmedia.co.uk>
     *
     * @param boolean $direct just direct children or all children
     *
     * @return  Collection
     */
    public function getSubMenu($direct = false)
    {
        $sub_menu  = new Collection;
        $parent_id = $this->id ? $this->id : 0;

        $sub_items = self::where('parent_id', $parent_id)
                         ->orderBy('order', 'ASC')
                         ->get();

        foreach ($sub_items as $sub_item) {
            $sub_menu->push($sub_item);
            if (!$direct) {
                $sub_menu = $sub_menu->merge($sub_item->getSubMenu());
            }
        }

        return $sub_menu;
    }

    /**
     * SiteMenu::getSiteMenuTree()
     *
     * Retrieve a collection of child SiteMenus either all or only direct ones
     * @author   mtc. Dawid Bestrzynski <dawid.bestrzynski@mtcmedia.co.uk>
     *
     * @param boolean $all all SiteMenus in the tree or only published ones
     *
     * @return  Collection
     */
    public function getSiteMenuTree($all = true)
    {
        $tree = new Collection;


        if ($all) {
            $sub_items = self::where('parent_id', $this->id)
                            ->orderBy('order', 'ASC')
                            ->get();
        } else {
            $sub_items = self::where('parent_id', $this->id)
                            ->where('published', 1)
                            ->orderBy('order', 'ASC')
                            ->get();
        }

        foreach ($sub_items as $sub_item) {
            $sub_item->subs = $sub_item->getSiteMenuTree($all);

            if (!empty($sub_item->site_tree)) {
                $sub_item->cms_tree = CMSNav::tree($sub_item->site_tree);
            }

            if (!empty($sub_item->brand_list)) {
                $sub_item->sub_brands = self::allBrands();
            }

            // todo just direct subcategories? or more depth?
            if (!empty($sub_item->category_list)) {
                $sub_item->sub_categories = $sub_item->listCategories();
            }

            $tree->push($sub_item);
        }

        return $tree;
    }

    /**
     * SiteMenu::getAllSiteMenuOptions()
     *
     * Retrieve a string with option tags containing all SiteMenus to use in SiteMenu add/edit form
     * @author   mtc. Dawid Bestrzynski <dawid.bestrzynski@mtcmedia.co.uk>
     *
     * @param integer $direct_parent ID of direct parent SiteMenu to mark the correct option tag as selected
     *
     * @static
     * @return  array
     */
    public static function getAllSiteMenuOptions($direct_parent = 0, $exclude_tree = null)
    {
        $options   = [];
        $options[] = [
            'id'       => 0,
            'name'     => 'Root',
            'selected' => $direct_parent == 0,
            'sublevel' => 0,
        ];

        $branches = self::query()
            ->where('parent_id', 0)
            ->orderBy('order', 'ASC')
            ->where('id', '!=', $exclude_tree)
            ->get();

        foreach ($branches as $branch) {
            /** @var self $branch */
            $branch->getSiteMenuOptions($options, $direct_parent, $exclude_tree);
        }

        return $options;
    }

    /**
     * SiteMenu::getSiteMenuOptions()
     *
     * Add items containing child SiteMenus to the $option_array to be used in SiteMenu add/edit form
     * @author   mtc. Dawid Bestrzynski <dawid.bestrzynski@mtcmedia.co.uk>
     *
     * @param integer $direct_parent ID of direct parent SiteMenu to mark the correct option tag as selected
     * @param integer $sublevel level of nesting of the submenu
     *
     * @return  void
     */
    public function getSiteMenuOptions(&$option_array, $direct_parent = 0, $exclude = null, $sublevel = 0)
    {
        $option_array[] = [
            'id' => $this->id,
            'name' => $this->name,
            'selected' => $direct_parent == $this->id,
            'sublevel' => $sublevel,
        ];

        $sub_items = self::query()
            ->where('parent_id', $this->id)
            ->orderBy('order', 'ASC')
            ->where('id', '!=', $exclude)
            ->get();

        foreach ($sub_items as $sub_item) {
            $sub_item->getSiteMenuOptions($option_array, $direct_parent, $exclude, $sublevel + 1);
        }
    }

    /**
     * SiteMenu::getBreadcrumbs()
     *
     * Retrieve a array with links to be used for breadcrumbs
     * @author   mtc. Dawid Bestrzynski <dawid.bestrzynski@mtcmedia.co.uk>
     *
     * @param boolean $end_class flag for adding class="end" for the current SiteMenu
     *
     * @return  array
     */
    public function getBreadcrumbs($end_class = true)
    {
        $breadcrumbs   = [];
        $breadcrumbs[] = [
            'url'       => '/admin/',
            'title'     => 'Home',
            'end_class' => false,
        ];

        if ($_SERVER['SCRIPT_NAME'] == '/plugins/SiteMenu/admin/index.php'
           && !isset($_REQUEST['id'])
           && (!isset($_REQUEST['menu_id']) || $this->id == null)) {
            $manage_end = true;
        } else {
            $manage_end = false;
        }
        $breadcrumbs[] = [
            'url'       => '/plugins/SiteMenu/admin/index.php',
            'title'     => 'Manage menus',
            'end_class' => $manage_end,
        ];

        foreach ($this->getAncestors()->reverse() as $ancestor) {
            $breadcrumbs[] = [
                'url'       => '/plugins/SiteMenu/admin/index.php?id=' . $ancestor->id,
                'title'     => clean_page($ancestor->name),
                'end_class' => false,
            ];
        }

        $breadcrumbs[] = [
            'url'       => !$end_class ? '/plugins/SiteMenu/admin/index.php?id=' . $this->id : '',
            'title'     => clean_page($this->name),
            'end_class' => $end_class,
        ];

        if ((!$end_class || $this->id == 0) && $_SERVER['SCRIPT_NAME'] == '/plugins/SiteMenu/admin/edit.php') {
            $breadcrumbs[] = [
                'url'       => '',
                'title'     => 'Add menu item',
                'end_class' => true,
            ];
        }

        return $breadcrumbs;
    }

    /**
     * SiteMenu::getAncestors()
     *
     * Retrieve a collection of the ancestors of a SiteMenu
     * @author   mtc. Dawid Bestrzynski <dawid.bestrzynski@mtcmedia.co.uk>
     *
     * @return  Collection
     */
    public function getAncestors()
    {
        $ancestors = new Collection;

        if ($this->parent_id != 0) {
            $ancestors->push($this->parentMenu);
            $ancestors = $ancestors->merge($this->parentMenu->getAncestors());
        }

        return $ancestors;
    }

    /**
     * SiteMenu::parentMenu()
     *
     * Eloquent relation with the direct parent SiteMenu
     * @author   mtc. Dawid Bestrzynski <dawid.bestrzynski@mtcmedia.co.uk>
     *
     * @return  SiteMenu
     */

    public function parentMenu()
    {
        return $this->hasOne(SiteMenu::class, 'id', 'parent_id');
    }

    /**
     * SiteMenu::directChildrenCount()
     *
     * Retrieve the number of direct children of a SiteMenu
     * @author   mtc. Dawid Bestrzynski <dawid.bestrzynski@mtcmedia.co.uk>
     *
     * @return  integer
     */
    public function directChildrenCount()
    {
        return self::where('parent_id', $this->id)->count();
    }

    /**
     * SiteMenu::images()
     *
     * Relationship with images linked to current menu item
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function images()
    {
        return $this->hasMany(SiteMenuImage::class, 'sitemenu_id')
            ->orderBy('order', 'ASC')
            ->orderBy('id', 'ASC');
    }


}
