<?php

namespace App\Acl;

use SebastianBergmann\Environment\Runtime;
use SebastianBergmann\CodeCoverage\RuntimeException;


class Resource
{
    /**
     * @var Resource[]
     */
    protected static $resources = [];

    /**
     * @return Resource
     */
    public static function factory(string $name)
    {
        if (!isset(static::$resources[$name])) {
            throw new \InvalidArgumentException('There is no resource registered with name ' . $name);
        }

        return static::$resources[$name];
    }

    /**
     * @param array $resources
     */
    public static function setResources(array $resources)
    {
        static::$resources = [];

        static::registerResources($resources);
    }

    /**
     * @return Resource[]
     */
    public static function getResources()
    {
        return static::$resources;
    }

    /**
     * @return string[]
     */
    public static function getResourcesPrivilegesList()
    {
        $privileges = ['*'];

        foreach (static::getResources() as $resource) {

            $privileges[] = $resource->getResourcePrivilege();
            $privileges[] = $resource->getActionWildcardPrivilege();

            $privileges += $resource->getActionsPrivileges();
        }

        return array_unique($privileges);
    }

    /**
     * @param array|Resource $resource
     */
    public static function registerResource($resource)
    {
        if (!($resource instanceof static)) {
            $resource = new static($resource);
        }

        static::$resources[$resource->getName()] = $resource;
    }

    /**
     * @param array $resources
     */
    public static function registerResources(array $resources)
    {
        foreach ($resources as $resource) {
            static::registerResource($resource);
        }
    }

    /**
     * @var string
     */
    protected $id;

    /**
     * @var string
     */
    protected $name;

    /**
     * @var array
     */
    protected $actions = [];

    public function __construct(array $properties = [])
    {
        if (isset($properties['id'])) {
            $this->setId($properties['id']);
        }

        if (isset($properties['name'])) {
            $this->setName($properties['name']);
        }

        if (isset($properties['actions'])) {
            $this->setActions($properties['actions']);
        }
    }

    /**
     * @return string
     */
    public function getId()
    {
        if (empty($this->id)) {
            throw new \LogicException('Trying to use id while not initialized');
        }

        return $this->id;
    }

    /**
     * @param string $id
     * @return Resource
     */
    public function setId(string $id)
    {
        if (empty($id)) {
            throw new \InvalidArgumentException('Argument $id must be non empty string');
        }

        $this->id = $id;
        
        return $this;
    }

    /**
     * @return string
     */
    public function getName()
    {
        if (empty($this->name)) {
            throw new \LogicException('Trying to use name while not initialized');
        }

        return $this->name;
    }

    /**
     * @param string $name
     * @return Resource
     */
    public function setName(string $name)
    {
        if (empty($name)) {
            throw new \InvalidArgumentException('Argument $name must be non empty string');
        }

        $this->name = $name;
        
        return $this;
    }

    /**
     * @return array
     */
    public function getActions()
    {
        return $this->actions;
    }

    /**
     * @return array
     */
    public function getActionIds()
    {
        return array_keys($this->actions);
    }

    /**
     * @param array $actions
     * @return Resource
     */
    public function setActions(array $actions)
    {
        $this->actions = [];

        $this->registerActions($actions);
        
        return $this;
    }

    /**
     * @param string $action
     * @return Resource
     */
    public function registerAction(string $actionId, string $actionName = '')
    {
        if (empty($actionId)) {
            throw new \InvalidArgumentException('Argument $action must be non empty string');
        }

        if ($actionId == '*') {
            throw new \InvalidArgumentException('Argument $action must not have special value of "*"');
        }

        if (empty($actionName)) {
            $actionName = $actionId;
        }

        if (!$this->actionIsRegistered($actionId)) {
            $this->actions[$actionId] = $actionName;
        }

        return $this;
    }

    /**
     * @param string $action
     * @return boolean
     */
    public function actionIsRegistered(string $actionId)
    {
        if (empty($actionId)) {
            return false;
        }

        if (isset($this->actions[$actionId])) {
            return true;
        }

        return false;
    }

    /**
     * @param array $actions
     * @return Resource
     */
    public function registerActions(array $actions)
    {
        foreach ($actions as $actionId => $actionName) {
            if (is_int($actionId)) {
                $actionId = $actionName;
                $actionName = '';
            }

            $this->registerAction($actionId, $actionName);
        }

        return $this;
    }

    /**
     * The array of privileges for registered actions
     * 
     * @see Resource::getActionPrivilege
     * @return string[]
     */
    public function getActionsPrivileges()
    {
        $privileges = [];

        $id = $this->getId();

        foreach ($this->getActionIds() as $actionId) {
            $privileges[] = $this->getActionPrivilege($actionId);
        }

        return $privileges;
    }

    /**
     * Return privilage key to register on Gate for action 
     * ex. resource.admin.edit where edit is action and admin is resource name
     * 
     * @return string
     */
    public function getActionPrivilege(string $actionId)
    {
        if (!$this->actionIsRegistered($actionId)) {
            throw new \InvalidArgumentException('There is no registered action with id: ' . $actionId);
        }

        return $this->getId() . '.' . $actionId;
    }

    /**
     * General resource privilage key which indicates if someone has any access to the resource
     * ex. resource.admin
     * @return string
     */
    public function getResourcePrivilege()
    {
        //privilege for resource generally "resource.resourceId"
        return $this->getId();
    }

    /**
     * The wildacr privilege for all actions in resource
     * ex. resource.admin.*
     * @return string
     */
    public function getActionWildcardPrivilege()
    {
        //joker privelege to enable all actions on resource
        return $this->getId() . '*';
    }

    public function __toString()
    {
        return $this->getId();
    }
}   