<?php

/**
 * Class represents records from table resource_category
 * {autogenerated}
 * @property int $resource_category_id PK
 * @property string|null $title
 * @property string|null $description
 * @property int|null $parent_id
 * @property int|null $sort_order
 * {/autogenerated}
 */
class ResourceCategory extends Am_Record
{
    protected $_childNodes = [];

    function getChildNodes()
    {
        return $this->_childNodes;
    }

    function removeChildNode(ResourceCategory $node)
    {
        foreach ($this->_childNodes as $k => $n) {
            if ($n == $node)
                unset($this->_childNodes[$k]);
        }
    }

    function createChildNode()
    {
        $c = new self($this->getTable());
        $c->parent_id = $this->pk();
        if (!$c->parent_id)
            throw new Am_Exception_InternalError("Could not add child node to not-saved object in " . __METHOD__);
        $this->_childNodes[] = $c;
        return $c;
    }

    public function fromRow(array $vars)
    {
        if (isset($vars['childNodes'])) {
            foreach ($vars['childNodes'] as $row) {
                $r = new self($this->getTable());
                $r->fromRow($row);
                $this->_childNodes[] = $r;
            }
            unset($vars['childNodes']);
        }
        return parent::fromRow($vars);
    }

    public function save()
    {
        if ($this->pk() == $this->parent_id) {
            $this->parent_id = 0;
        }
        parent::save();
    }

    function getAllowedResources(User $user)
    {
        $res = [];

        foreach ($this->getDi()->resourceAccessTable->getAllowedResources($user, ResourceAccess::USER_VISIBLE_TYPES) as $r) {
            if (in_array($this->pk(), $r->getCategories())) {
                $res[] = $r;
            }
        }

        return $res;
    }
}

/** @extends Am_Table<ResourceCategory> */
class ResourceCategoryTable extends Am_Table
{
    protected $_key = 'resource_category_id';
    protected $_table = '?_resource_category';
    protected $_recordClass = 'ResourceCategory';

    const COUNT = 'count';

    /**
     * Do not include categories that do not have products.
     */
    const EXCLUDE_EMPTY = 'exclude_empty';

    /**
     * @todo protect against endless cycle (child[parent_id] <-> parent[parent_id])
     * @return ResourceCategory
     */
    function getTree($cast_objects = true)
    {
        $ret = [];
        foreach ($this->_db->select("SELECT
            resource_category_id AS ARRAY_KEY,
            parent_id AS PARENT_KEY, pc.*
            FROM ?_resource_category AS pc
            ORDER BY parent_id, 0+sort_order, title") as $r) {
            $ret[] = $cast_objects ? $this->createRecord($r) : $r;
        }
        return $ret;
    }

    /**
     * retrieve category tree with access check
     * skip empty branches
     *
     * @param User $user
     */
    function getAllowedTree(User $user)
    {
        $tree = $this->getTree(true);

        $this->_resources = $this->getDi()->resourceAccessTable->getAllowedResources($user, ResourceAccess::USER_VISIBLE_TYPES);

        $_ = $this->getAdapter()->selectCol("SELECT resource_category_id FROM ?_resource_resource_category;");
        $this->_resources_category_cnt = array_combine($_, array_fill(0, count($_), 0));

        $r_rc = $this->getAdapter()->selectCol(<<<CUT
                SELECT GROUP_CONCAT(resource_category_id), CONCAT(resource_id, '-', resource_type) AS ARRAY_KEY
                    FROM ?_resource_resource_category
                    GROUP BY ARRAY_KEY;
CUT
            );

        foreach($this->_resources as $k => $r) {
            $_ = isset($r_rc[$r->pk() . '-' . $r->getAccessType()]) ? explode(',', $r_rc[$r->pk() . '-' . $r->getAccessType()]) : [];
            foreach ($_ as $cat_id) {
                $this->_resources_category_cnt[$cat_id]++;
            }
        }

        foreach ($tree as $k => $node) {
            $node->cnt = $this->_calcCntAndFilterEmpty($node, $user);
            if (!$node->cnt)
                unset($tree[$k]);
        }

        return $tree;
    }

    protected function _calcCntAndFilterEmpty(ResourceCategory $node, User $user)
    {
        $res = $node->self_cnt = $this->_resources_category_cnt[$node->pk()] ?? 0;
        foreach ($node->getChildNodes() as $n) {
            $n->cnt = $this->_calcCntAndFilterEmpty($n, $user);
            if (!$n->cnt)
                $node->removeChildNode($n);
            $res += $n->cnt;
        }
        return $res;
    }

    function getOptions()
    {
        $ret = [];
        $rows = $this->_db->select("SELECT resource_category_id AS ARRAY_KEY,
                parent_id AS PARENT_KEY,
                resource_category_id, title
                FROM ?_resource_category
                ORDER BY parent_id, 0+sort_order, title");

        foreach ($rows as $id => $r) {
            $this->renderNode($r, $id, '', [], '', $ret);
        }

        return $ret;
    }

    function getSubCategoryIds($cat_id)
    {
        $res = [];
        $childNodes = $this->extractSubtree($this->getTree(false), $cat_id);
        while($node = array_shift($childNodes)) {
            array_push($res, $node['resource_category_id']);
            foreach ($node['childNodes'] as $n) {
                array_push($childNodes, $n);
            }
        }
        return $res;
    }

    protected function extractSubtree($rows, $root)
    {
        $childNodes = $rows;
        while($node = array_shift($childNodes)) {
            if ($node['resource_category_id'] == $root) return $node['childNodes'];
            foreach ($node['childNodes'] as $n) {
                array_push($childNodes, $n);
            }
        }
        return [];
    }

    protected function renderNode($r, $id, $type, array $options, $title, &$ret)
    {
        $do_count = !empty($options[self::COUNT]);
        $exclude_empty = !empty($options[self::EXCLUDE_EMPTY]);

        $title .= ( $title ? '/' : '') . $r['title'];
        $ctitle = $title;

        if ($exclude_empty && !$r['count']) {
            foreach ($r['childNodes'] as $cid => $c)
                $this->renderNode($c, $cid, $type, $options, $ctitle, $ret);
            return;
        }

        if ($do_count)
            $title .= sprintf(' (%d)', $r['count']);

        $ret[$id] = $title;

        foreach ($r['childNodes'] as $cid => $c)
            $this->renderNode($c, $cid, $type, $options, $ctitle, $ret);
    }

    function moveNodes($fromId, $toId)
    {
        $this->_db->query("UPDATE {$this->_table} SET parent_id=?d WHERE parent_id=?d",
            $toId, $fromId);
    }

    public function delete($key)
    {
        parent::delete($key);
        $this->_db->query("DELETE FROM ?_resource_resource_category WHERE resource_category_id=?d", $key);
    }
}