<?php

namespace Velis\Acl;

use User\User;
use Velis\Acl;
use Velis\App;
use Velis\Lang;
use Velis\Model\Cacheable;
use Velis\Model\DataObject;
use Velis\Output;

/**
 * ACL privilege model
 * @author Olek Procki <olo@velis.pl>
 */
class Priv extends DataObject implements Cacheable
{
    /**
     * Returns related table name
     * @return string
     */
    protected function _getTableName()
    {
        return 'acl.priv_tab';
    }

    protected function _getPrimaryKeyField()
    {
        if (self::hasField('priv_acro') && self::hasField('app_module_id')) {
            return ['app_module_id','priv_acro'];
        } else {
            return 'priv_id';
        }
    }


    public static function loadInstance($appModule, $acro)
    {
        if (self::hasField('priv_acro')) {
            $params = ['p.priv_acro' => $acro,
                       'p.app_module_id' => $appModule];
        } else {
            $params = ['p.acro' => $acro,
                       'am.acro' => $appModule];
        }

        $query = "SELECT p.* FROM acl.priv_tab p "
               . " JOIN acl.app_module_tab am USING (app_module_id)"
               . " WHERE 1=1 "
               . self::$_db->conditions($params);

        $row = self::$_db->getRow($query, $params);

        if ($row) {
            return new self($row);
        }
    }


    /**
     * Return privs list
     *
     * @param $moduleAcro
     * @return \Velis\Acl\Priv[]
     */
    public static function getList($module = null, $params = null, $order = null, $limit = null, $fields = null)
    {
        $query = "SELECT p.* FROM acl.priv_tab AS p";
        $params = array();

        if ($module != null) {
            if (!Module::hasField('acro')) {
                $query .= " WHERE p.app_module_id = :module_id";
                $params['module_id'] = $module;
            } else {
                $query .= ", acl.app_module_tab AS am WHERE p.app_module_id = am.app_module_id AND am.acro=:module";
                $params['module'] = $module;
            }
        }

        $query .= " ORDER BY p.app_module_id, " . (self::hasField('acro') ? "p.acro" : "p.priv_acro");

        $privs = array();
        foreach (self::$_db->getAll($query, $params) as $row) {
            if (array_key_exists('priv_acro', $row)) {
                $privs[] = new self($row);
            } else {
                $privs[$row['priv_id']] = new self($row);
            }
        }

        return $privs;
    }


    /**
     * Returns roles where priv is attached
     * @return \Velis\Acl\Role[]
     */
    public function getRoles()
    {
        if (self::hasField('priv_acro')) {
            $params = array('rp.priv_acro' => $this['priv_acro'], 'rp.app_module_id' => $this['app_module_id']);
        } else {
            $params = array('rp.priv_id' => $this->id());
        }

        $query = "SELECT r.* FROM acl.role_tab AS r, acl.role_priv_tab AS rp
                  WHERE r.role_id = rp.role_id";
        $query .= self::$_db->conditions($params);

        $roles = array();

        foreach (self::$_db->getAll($query, $params) as $row) {
            $roles[$row['role_id']] = new Role($row);
        }

        return $roles;
    }


    /**
     * Returns users attached to priv (directly)
     * @return \Velis\User[]
     */
    public function getUsers()
    {
        if (self::hasField('priv_acro')) {
            $params = array('up.priv_acro' => $this['priv_acro'], 'up.app_module_id' => $this['app_module_id']);
        } else {
            $params = array('up.priv_id' => $this->id());
        }

        $query = "SELECT u.* FROM
                    acl.user AS u,
                    acl.user_priv_tab AS up

                  WHERE u.user_id = up.user_id"
                    . self::$_db->conditions($params) .
                    " AND up.direction='+' ORDER BY u.login";

        $result = array();

        foreach (self::$_db->getAll($query, $params) as $row) {
            $result[$row['user_id']] = new User($row);
        }
        return $result;
    }


    /**
     * Return priviledge name
     * @return string
     */
    public function getAcro()
    {
        return self::hasField('priv_acro') ? $this['priv_acro'] : $this['acro'];
    }


    /**
     * Returns priviledge description
     * @return string
     */
    public function getDescription()
    {
        return $this['description_' . Lang::$lang];
    }


    /**
     * Returns app module
     * @return \Velis\Acl\Module
     */
    public function getModule()
    {
        return Module::get($this->getModuleId());
    }


    /**
     * Returns app module id
     * @return int
     */
    public function getModuleId()
    {
        return $this['app_module_id'];
    }


    /**
     * Removes priv & relations
     * @return bool
     */
    public function remove()
    {
        self::$_db->startTrans();

        if (self::hasField('priv_acro')) {
            $params = array('priv_acro' => $this['priv_acro'], 'app_module_id' => $this['app_module_id']);
        } else {
            $params = array('priv_id' => $this->id());
        }

        self::$_db->execDML("DELETE FROM acl.user_priv_tab WHERE 1=1" . self::$_db->conditions($params), $params);
        self::$_db->execDML("DELETE FROM acl.role_priv_tab WHERE 1=1" . self::$_db->conditions($params), $params);
        if (App::settings('AclChangelog')) {
            self::$_db->execDML("DELETE FROM acl.changelog_tab WHERE 1=1" . self::$_db->conditions($params), $params);
        }
        parent::_remove();

        self::$_db->commit();

        return true;
    }


    /**
    * Returns privs based on active modules
    * @return Priv[]
    */
    public static function listAvaliable($module = null)
    {
        $privs = self::listCached();

        foreach ($privs as $key => $priv) {
            if (!in_array($priv['app_module_id'], App::$config->getEnabledModules())) {
                unset($privs[$key]);
            }
            if ($module && $priv['app_module_id'] != $module) {
                unset($privs[$key]);
            }
        }
        return $privs;
    }

    /**
     * Check role have exclude priv
     * @param array|string
     * @return boolean|null
     */
    public function isExcludedForRole($roleId)
    {
        if (is_array($roleId)) {
            return $this->checkForArrayOfRoles($roleId);
        }
        return $this->checkForSingleRole($roleId);
    }

    /**
     * Check role have lock priv
     * @param string $roleId
     * @return bool|null
     */
    private function checkForSingleRole($roleId)
    {
        $field = Output::toSnakeCase($roleId) . '_excluded';
        if (!self::hasField($field)) {
            $altField = explode('_', $field)[0] . '_excluded';
            if (self::hasField($altField)) {
                $field = $altField;
            }
        }

        return $this[$field];
    }

    /**
     *  Execute check for array
     *  @param array
     *  @return boolean
     */
    private function checkForArrayOfRoles($rolesId)
    {
        foreach ($rolesId as $roleId) {
            if ($this->checkForSingleRole($roleId)) {
                return true;
            }
        }
        return false;
    }


    /**
     * Return translated comment
     * @return string|void
     */
    public function getComment()
    {
        $obj = clone $this;
        unset($obj['comment_text']);
        return $obj->getTranslatedName('comment_text');
    }


    /**
     * Returns privs assigned to user
     *
     * @param \Velis\User $user
     * @return array
     */
    public static function byUser($user): array
    {
        $dataSource = Acl::getUserPrivsDatasource();
        $query = "SELECT * FROM $dataSource WHERE user_id = :user_id";
        $privs = [];

        $restore = self::$_db->checkDuplicatedQueries(false);
        foreach (self::$_db->getAll($query, ['user_id' => $user->id()]) as $row) {
            $key = array_key_exists('app_module_id', $row) ? 'app_module_id' : 'app_module_acro';
            $privs[$row[$key]][] = $row['priv_acro'];
        }
        self::$_db->checkDuplicatedQueries($restore);

        return $privs;
    }
}
