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

class Menu extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
    /**
     * Date model
     * 
     * @var \Magento\Framework\Stdlib\DateTime\DateTime
     */
    protected $date;

    /**
     * constructor
     * 
     * @param \Magento\Framework\Stdlib\DateTime\DateTime $date
     * @param \Magento\Framework\Model\ResourceModel\Db\Context $context
     */
    public function __construct(
        \Magento\Framework\Stdlib\DateTime\DateTime $date,
        \Magento\Framework\Model\ResourceModel\Db\Context $context
    )
    {
        $this->date = $date;
        parent::__construct($context);
    }


    /**
     * Initialize resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init('solwin_verticalmenu_menu', 'menu_id');
    }

    /**
     * Retrieves Menu Menu Title from DB by passed id.
     *
     * @param string $id
     * @return string|bool
     */
    public function getMenuMenu_titleById($id)
    {
        $adapter = $this->getConnection();
        $select = $adapter->select()
            ->from($this->getMainTable(), 'menu_title')
            ->where('menu_id = :menu_id');
        $binds = ['menu_id' => (int)$id];
        return $adapter->fetchOne($select, $binds);
    }
    /**
     * before save callback
     *
     * @param \Magento\Framework\Model\AbstractModel|\Solwin\Verticalmenu\Model\Menu $object
     * @return $this
     */
    protected function _beforeSave(\Magento\Framework\Model\AbstractModel $object)
    {
        $object->setUpdatedAt($this->date->date());
        if ($object->isObjectNew()) {
            $object->setCreatedAt($this->date->date());
        }
        /** @var \Solwin\Verticalmenu\Model\Menu $object */
        parent::_beforeSave($object);
        if (!$object->getChildrenCount()) {
            $object->setChildrenCount(0);
        }

        if ($object->isObjectNew()) {
            if ($object->getPosition() === null) {
                $object->setPosition($this->getMaxPosition($object->getPath()) + 1);
            }
            $path = explode('/', $object->getPath());
            $level = count($path)  - ($object->getId() ? 1 : 0);
            $toUpdateChild = array_diff($path, [$object->getId()]);

            if (!$object->hasPosition()) {
                $object->setPosition($this->getMaxPosition(implode('/', $toUpdateChild)) + 1);
            }
            if (!$object->hasLevel()) {
                $object->setLevel($level);
            }
            if (!$object->hasParentId() && $level && !$object->getInitialSetupFlag()) {
                $object->setParentId($path[$level - 1]);
            }
            if (!$object->getId() && !$object->getInitialSetupFlag()) {
                $object->setPath($object->getPath() . '/');
            }
            if (!$object->getInitialSetupFlag()) {
                $this->getConnection()->update(
                    $this->getMainTable(),
                    ['children_count' => new \Zend_Db_Expr('children_count+1')],
                    ['menu_id IN(?)' => $toUpdateChild]
                );
            }
        }
        return $this;
    }
    /**
     * after save callback
     *
     * @param \Magento\Framework\Model\AbstractModel|\Solwin\Verticalmenu\Model\Menu $object
     * @return $this
     */
    protected function _afterSave(\Magento\Framework\Model\AbstractModel $object)
    {
        /** @var \Solwin\Verticalmenu\Model\Menu $object */
        if (substr($object->getPath(), -1) == '/') {
            $object->setPath($object->getPath() . $object->getId());
            $this->savePath($object);
        }
        return parent::_afterSave($object);
    }

    /**
     * @param $path
     * @return int|string
     */
    protected function getMaxPosition($path)
    {
        $adapter = $this->getConnection();
        $positionField = $adapter->quoteIdentifier('position');
        $level = count(explode('/', $path));
        $bind = ['c_level' => $level, 'c_path' => $path . '/%'];
        $select = $adapter->select()->from(
            $this->getTable('solwin_verticalmenu_menu'),
            'MAX(' . $positionField . ')'
        )->where(
                $adapter->quoteIdentifier('path') . ' LIKE :c_path'
            )->where(
                $adapter->quoteIdentifier('level') . ' = :c_level'
            );

        $position = $adapter->fetchOne($select, $bind);
        if (!$position) {
            $position = 0;
        }
        return $position;
    }

    /**
     * Update path field
     *
     * @param \Solwin\Verticalmenu\Model\Menu $object
     * @return $this
     */
    protected function savePath($object)
    {
        if ($object->getId()) {
            $this->getConnection()->update(
                $this->getMainTable(),
                ['path' => $object->getPath()],
                ['menu_id = ?' => $object->getId()]
            );
            $object->unsetData('path_ids');
        }
        return $this;
    }


    /**
     * @param AbstractModel $object
     * @return $this
     */
    protected function _beforeDelete(\Magento\Framework\Model\AbstractModel $object)
    {
        parent::_beforeDelete($object);

        /**
         * Update children count for all parent Menus
         */
        $parentIds = $object->getParentIds();
        if ($parentIds) {
            $childDecrease = $object->getChildrenCount() + 1;
            // +1 is itself
            $data = ['children_count' => new \Zend_Db_Expr('children_count - ' . $childDecrease)];
            $where = ['menu_id IN(?)' => $parentIds];
            $this->getConnection()->update($this->getMainTable(), $data, $where);
        }
        $this->deleteChildren($object);
        return $this;
    }

    /**
     * @param \Magento\Framework\DataObject $object
     * @return $this
     */
    public function deleteChildren(\Magento\Framework\DataObject $object)
    {
        $adapter = $this->getConnection();
        $pathField = $adapter->quoteIdentifier('path');

        $select = $adapter->select()->from(
            $this->getMainTable(),
            ['menu_id']
        )->where(
                $pathField . ' LIKE :c_path'
            );

        $childrenIds = $adapter->fetchCol($select, ['c_path' => $object->getPath() . '/%']);

        if (!empty($childrenIds)) {
            $adapter->delete($this->getMainTable(), ['menu_id IN (?)' => $childrenIds]);
        }

        /**
         * Add deleted children ids to object
         * This data can be used in after delete event
         */
        $object->setDeletedChildrenIds($childrenIds);
        return $this;
    }

    /**
     * @param \Solwin\Verticalmenu\Model\Menu $menu
     * @param \Solwin\Verticalmenu\Model\Menu $newParent
     * @param null $afterMenuId
     * @return $this
     */
    public function changeParent(
        \Solwin\Verticalmenu\Model\Menu $menu,
        \Solwin\Verticalmenu\Model\Menu $newParent,
        $afterMenuId = null
    ) {
        $childrenCount = $this->getChildrenCount($menu->getId()) + 1;
        $table = $this->getMainTable();
        $adapter = $this->getConnection();
        $levelFiled = $adapter->quoteIdentifier('level');
        $pathField = $adapter->quoteIdentifier('path');

        /**
         * Decrease children count for all old Menu parent Menus
         */
        $adapter->update(
            $table,
            ['children_count' => new \Zend_Db_Expr('children_count - ' . $childrenCount)],
            ['menu_id IN(?)' => $menu->getParentIds()]
        );

        /**
         * Increase children count for new Menu parents
         */
        $adapter->update(
            $table,
            ['children_count' => new \Zend_Db_Expr('children_count + ' . $childrenCount)],
            ['menu_id IN(?)' => $newParent->getPathIds()]
        );

        $position = $this->processPositions($menu, $newParent, $afterMenuId);

        $newPath = sprintf('%s/%s', $newParent->getPath(), $menu->getId());
        $newLevel = $newParent->getLevel() + 1;
        $levelDisposition = $newLevel - $menu->getLevel();

        /**
         * Update children nodes path
         */
        $adapter->update(
            $table,
            [
                'path' => new \Zend_Db_Expr(
                    'REPLACE(' . $pathField . ',' . $adapter->quote(
                        $menu->getPath() . '/'
                    ) . ', ' . $adapter->quote(
                        $newPath . '/'
                    ) . ')'
                ),
                'level' => new \Zend_Db_Expr($levelFiled . ' + ' . $levelDisposition)
            ],
            [$pathField . ' LIKE ?' => $menu->getPath() . '/%']
        );
        /**
         * Update moved Menu data
         */
        $data = [
            'path' => $newPath,
            'level' => $newLevel,
            'position' => $position,
            'parent_id' => $newParent->getId(),
        ];
        $adapter->update($table, $data, ['menu_id = ?' => $menu->getId()]);

        // Update Menu object to new data
        $menu->addData($data);
        $menu->unsetData('path_ids');

        return $this;
    }

    /**
     * @param \Solwin\Verticalmenu\Model\Menu $menu
     * @param \Solwin\Verticalmenu\Model\Menu $newParent
     * @param $afterMenuId
     * @return int
     */
    protected function processPositions(
        \Solwin\Verticalmenu\Model\Menu $menu,
        \Solwin\Verticalmenu\Model\Menu $newParent,
        $afterMenuId
    )
    {
        $table = $this->getMainTable();
        $adapter = $this->getConnection();
        $positionField = $adapter->quoteIdentifier('position');

        $bind = ['position' => new \Zend_Db_Expr($positionField . ' - 1')];
        $where = [
            'parent_id = ?' => $menu->getParentId(),
            $positionField . ' > ?' => $menu->getPosition(),
        ];
        $adapter->update($table, $bind, $where);

        /**
         * Prepare position value
         */
        if ($afterMenuId) {
            $select = $adapter->select()->from($table, 'position')->where('menu_id = :menu_id');
            $position = $adapter->fetchOne($select, ['menu_id' => $afterMenuId]);
            $position += 1;
        } else {
            $position = 1;
        }

        $bind = ['position' => new \Zend_Db_Expr($positionField . ' + 1')];
        $where = ['parent_id = ?' => $newParent->getId(), $positionField . ' >= ?' => $position];
        $adapter->update($table, $bind, $where);

        return $position;
    }

    /**
     * @param $menuId
     * @return string
     */
    public function getChildrenCount($menuId)
    {
        $select = $this->getConnection()->select()->from(
            $this->getMainTable(),
            'children_count'
        )->where(
                'menu_id = :menu_id'
            );
        $bind = ['menu_id' => $menuId];

        return $this->getConnection()->fetchOne($select, $bind);
    }}
