<?php
namespace Solwin\Verticalmenu\Model\ResourceModel\Menu;

class Tree extends \Magento\Framework\Data\Tree\Dbp
{
    /**
     * ID field
     * 
     * @var string
     */
    const ID_FIELD = 'id';

    /**
     * Path field
     * 
     * @var string
     */
    const PATH_FIELD = 'path';

    /**
     * Order field
     * 
     * @var string
     */
    const ORDER_FIELD = 'order';

    /**
     * Level field
     * 
     * @var string
     */
    const LEVEL_FIELD = 'level';

    /**
     * Event manager
     * 
     * @var \Magento\Framework\Event\ManagerInterface
     */
    protected $eventManager;

    /**
     * Collection factory
     * 
     * @var \Solwin\Verticalmenu\Model\ResourceModel\Menu\CollectionFactory
     */
    protected $collectionFactory;

    /**
     * Menu Resource instance
     * 
     * @var \Solwin\Verticalmenu\Model\ResourceModel\Menu
     */
    protected $menuResource;

    /**
     * Cache instance
     * 
     * @var \Magento\Framework\App\CacheInterface
     */
    protected $cache;

    /**
     * Store Manager instance
     * 
     * @var \Magento\Store\Model\StoreManagerInterface
     */
    protected $storeManager;

    /**
     * App resource
     * 
     * @var \Magento\Framework\App\ResourceConnection
     */
    protected $coreResource;

    /**
     * Inactive Menu Ids
     * 
     * @var array
     */
    protected $inactiveMenuIds;

    /**
     * Menu Collection
     * 
     * @var \Solwin\Verticalmenu\Model\ResourceModel\Menu\Collection
     */
    protected $collection;

    /**
     * constructor
     * 
     * @param \Magento\Framework\Event\ManagerInterface $eventManager
     * @param \Solwin\Verticalmenu\Model\ResourceModel\Menu\CollectionFactory $collectionFactory
     * @param \Solwin\Verticalmenu\Model\ResourceModel\Menu $menuResource
     * @param \Magento\Framework\App\CacheInterface $cache
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param \Magento\Framework\App\ResourceConnection $coreResource
     */
    public function __construct(
        \Magento\Framework\Event\ManagerInterface $eventManager,
        \Solwin\Verticalmenu\Model\ResourceModel\Menu\CollectionFactory $collectionFactory,
        \Solwin\Verticalmenu\Model\ResourceModel\Menu $menuResource,
        \Magento\Framework\App\CacheInterface $cache,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Framework\App\ResourceConnection $coreResource
    )
    {
        $this->eventManager      = $eventManager;
        $this->collectionFactory = $collectionFactory;
        $this->menuResource      = $menuResource;
        $this->cache             = $cache;
        $this->storeManager      = $storeManager;
        $this->coreResource      = $coreResource;
        parent::__construct(
            $coreResource->getConnection('solwin_verticalmenu_write'),
            $coreResource->getTableName('solwin_verticalmenu_menu'),
            [
                \Magento\Framework\Data\Tree\Dbp::ID_FIELD => 'menu_id',
                \Magento\Framework\Data\Tree\Dbp::PATH_FIELD => 'path',
                \Magento\Framework\Data\Tree\Dbp::ORDER_FIELD => 'position',
                \Magento\Framework\Data\Tree\Dbp::LEVEL_FIELD => 'level'
            ]
        );
    }


    /**
     * Add data to collection
     *
     * @param Collection $collection
     * @param boolean $sorted
     * @param array $exclude
     * @param boolean $toLoad
     * @param boolean $onlyActive
     * @return $this
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     * @SuppressWarnings(PHPMD.NPathComplexity)
     */
    public function addCollectionData(
        $collection = null,
        $sorted = false,
        $exclude = [],
        $toLoad = true,
        $onlyActive = false
    ) {
        if ($collection === null) {
            $collection = $this->getCollection($sorted);
        } else {
            $this->setCollection($collection);
        }

        if (!is_array($exclude)) {
            $exclude = [$exclude];
        }

        $nodeIds = [];
        foreach ($this->getNodes() as $node) {
            if (!in_array($node->getId(), $exclude)) {
                $nodeIds[] = $node->getId();
            }
        }
        $collection->addIdFilter($nodeIds);
        if ($onlyActive) {
            $disabledIds = $this->getDisabledIds($collection, $nodeIds);
            if ($disabledIds) {
                $collection->addFieldToFilter('menu_id', ['nin' => $disabledIds]);
            }
        }


        if ($toLoad) {
            $collection->load();

            foreach ($collection as $menu) {
                if ($this->getNodeById($menu->getId())) {
                    $this->getNodeById($menu->getId())->addData($menu->getData());
                }
            }

            foreach ($this->getNodes() as $node) {
                if (!$collection->getItemById($node->getId()) && $node->getParent()) {
                    $this->removeNode($node);
                }
            }
        }

        return $this;
    }

    /**
     * Add inactive categories ids
     *
     * @param mixed $ids
     * @return $this
     */
    public function addInactiveMenuIds($ids)
    {
        if (!is_array($this->inactiveMenuIds)) {
            $this->initInactiveMenuIds();
        }
        $this->inactiveMenuIds = array_merge($ids, $this->inactiveMenuIds);
        return $this;
    }

    /**
     * Retrieve inactive Menus ids
     *
     * @return $this
     */
    protected function initInactiveMenuIds()
    {
        $this->inactiveMenuIds = [];
        $this->eventManager->dispatch('solwin_verticalmenu_menu_tree_init_inactive_menu_ids', ['tree' => $this]);
        return $this;
    }

    /**
     * Retrieve inactive Menus ids
     *
     * @return array
     */
    public function getInactiveMenuIds()
    {
        if (!is_array($this->inactiveMenuIds)) {
            $this->initInactiveMenuIds();
        }

        return $this->inactiveMenuIds;
    }

    /**
     * Return disable Menu ids
     *
     * @param Collection $collection
     * @param array $allIds
     * @return array
     */
    protected function getDisabledIds($collection, $allIds)
    {
        //TODO: implement this for frontend
        return [];
    }

    /**
     * Retrieve inactive Menu item ids
     *
     * @param Collection $collection
     * @param int $storeId
     * @return array
     */
    protected function getInactiveItemIds($collection, $storeId)
    {
        //TODO: implement this for frontend
        return [];
    }

    /**
     * Check is Menu items active
     *
     * @param int $id
     * @return boolean
     * @SuppressWarnings(PHPMD.BooleanGetMethodName)
     */
    protected function getItemIsActive($id)
    {
        //TODO: implement this for frontend
        return false;
    }

    /**
     * Get Menus collection
     *
     * @param boolean $sorted
     * @return Collection
     */
    public function getCollection($sorted = false)
    {
        if ($this->collection === null) {
            $this->collection = $this->getDefaultCollection($sorted);
        }
        return $this->collection;
    }

    /**
     * Clean unneeded collection
     *
     * @param Collection|array $object
     * @return void
     */
    protected function clean($object)
    {
        if (is_array($object)) {
            foreach ($object as $obj) {
                $this->clean($obj);
            }
        }
        unset($object);
    }

    /**
     * set collection
     *
     * @param Collection $collection
     * @return $this
     */
    public function setCollection($collection)
    {
        if ($this->collection !== null) {
            $this->clean($this->collection);
        }
        $this->collection = $collection;
        return $this;
    }

    /**
     * get default collection
     *
     * @param boolean $sorted
     * @return Collection
     */
    protected function getDefaultCollection($sorted = false)
    {
        $collection = $this->collectionFactory->create();
        if ($sorted) {
            if (is_string($sorted)) {
                // $sorted is supposed to be attribute name
                $collection->addFieldToSort($sorted);
            } else {
                $collection->addFieldToSort('menu_title');
            }
        }

        return $collection;
    }

    /**
     * Executing parents move method and cleaning cache after it
     *
     * @param mixed $menu
     * @param mixed $newParent
     * @param mixed $prevNode
     * @return void
     */
    public function move($menu, $newParent, $prevNode = null)
    {
        $this->menuResource->move($menu->getId(), $newParent->getId());
        parent::move($menu, $newParent, $prevNode);

        $this->afterMove();
    }

    /**
     * Move tree after
     *
     * @return $this
     */
    protected function afterMove()
    {
        $this->cache->clean([\Solwin\Verticalmenu\Model\Menu::CACHE_TAG]);
        return $this;
    }

    /**
     * Load whole Menu tree, that will include specified Menus ids.
     *
     * @param array $ids
     * @param bool $addCollectionData
     * @return $this|bool
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     * @SuppressWarnings(PHPMD.NPathComplexity)
     */
    public function loadByIds($ids, $addCollectionData = true)
    {
        $levelField = $this->_conn->quoteIdentifier('level');
        $pathField = $this->_conn->quoteIdentifier('path');
        // load first two levels, if no ids specified
        if (empty($ids)) {
            $select = $this->_conn
                ->select()
                ->from($this->_table, 'menu_id')
                ->where($levelField . ' <= 2');
            $ids = $this->_conn->fetchCol($select);
        }
        if (!is_array($ids)) {
            $ids = [$ids];
        }
        foreach ($ids as $key => $id) {
            $ids[$key] = (int)$id;
        }

        // collect paths of specified IDs and prepare to collect all their parents and neighbours
        $select = $this->_conn
            ->select()
            ->from($this->_table, ['path', 'level'])
            ->where('menu_id IN (?)', $ids);
        $where = [$levelField . '=0' => true];

        foreach ($this->_conn->fetchAll($select) as $item) {
            $pathIds = explode('/', $item['path']);
            $level = (int)$item['level'];
            while ($level > 0) {
                $pathIds[count($pathIds) - 1] = '%';
                $path = implode('/', $pathIds);
                $where["{$levelField}={$level} AND {$pathField} LIKE '{$path}'"] = true;
                array_pop($pathIds);
                $level--;
            }
        }
        $where = array_keys($where);

        // get all required records
        if ($addCollectionData) {
            $select = $this->createCollectionDataSelect();
        } else {
            $select = clone $this->_select;
            $select->order($this->_orderField . ' ' . \Magento\Framework\DB\Select::SQL_ASC);
        }
        $select->where(implode(' OR ', $where));

        // get array of records and add them as nodes to the tree
        $arrNodes = $this->_conn->fetchAll($select);
        if (!$arrNodes) {
            return false;
        }
        $childrenItems = [];
        foreach ($arrNodes as $key => $nodeInfo) {
            $pathToParent = explode('/', $nodeInfo[$this->_pathField]);
            array_pop($pathToParent);
            $pathToParent = implode('/', $pathToParent);
            $childrenItems[$pathToParent][] = $nodeInfo;
        }
        $this->addChildNodes($childrenItems, '', null);
        return $this;
    }

    /**
     * Load array of category parents
     *
     * @param string $path
     * @param bool $addCollectionData
     * @param bool $withRootNode
     * @return array
     */
    public function loadBreadcrumbsArray($path, $addCollectionData = true, $withRootNode = false)
    {
        $pathIds = explode('/', $path);
        if (!$withRootNode) {
            array_shift($pathIds);
        }
        $result = [];
        if (!empty($pathIds)) {
            if ($addCollectionData) {
                $select = $this->createCollectionDataSelect(false);
            } else {
                $select = clone $this->_select;
            }
            $select->where(
                'e.menu_id IN(?)',
                $pathIds
            )->order(
                $this->_conn->getLengthSql('e.path') . ' ' . \Magento\Framework\DB\Select::SQL_ASC
            );
            $result = $this->_conn->fetchAll($select);
        }
        return $result;
    }


    /**
     * Obtain select for Menus
     *
     * @param bool $sorted
     * @param array $optionalAttributes
     * @return \Zend_Db_Select
     */
    protected function createCollectionDataSelect($sorted = true, $optionalAttributes = [])
    {

        $select = $this->getDefaultCollection($sorted ? $this->_orderField : false)->getSelect();

        // count children products qty plus self products qty
        $menusTable = $this->coreResource->getTableName('solwin_verticalmenu_menu');

        $subConcat = $this->_conn->getConcatSql(['e.path', $this->_conn->quote('/%')]);
        $subSelect = $this->_conn->select()->from(
            ['see' => $menusTable],
            null
        )->where(
            'see.menu_id = e.menu_id'
        )->orWhere(
            'see.path LIKE ?',
            $subConcat
        );
        return $select;
    }

    /**
     * Get real existing Menu ids by specified ids
     *
     * @param array $ids
     * @return array
     */
    public function getExistingMenuIdsBySpecifiedIds($ids)
    {
        if (empty($ids)) {
            return [];
        }
        if (!is_array($ids)) {
            $ids = [$ids];
        }
        $select = $this->_conn
            ->select()
            ->from($this->_table, ['menu_id'])
            ->where('menu_id IN (?)', $ids);
        return $this->_conn->fetchCol($select);
    }
}
