<?php

namespace Velis\Bpm\Workflow;

use Psr\SimpleCache\InvalidArgumentException;
use ReflectionException;
use Velis\App;
use Velis\Exception as VelisException;
use Velis\Filter;
use Velis\Model\Cacheable;
use Velis\Model\DataObject;
use Velis\Mvc\Controller\Exception\NotFoundException;

/**
 * Workflow event model
 * @author Olek Procki <olo@velis.pl>
 */
class Event extends DataObject implements Cacheable
{
    public const BEFORE = 'Before';
    public const AFTER  = 'After';

    /**
     * Sort mode
     * @var string
     */
    protected static $_sortMode = self::SORT_STRING;


    /**
     * Action types available for this event
     * @var ActionType[]
     */
    protected $_actionTypes;


    /**
     * Subject reflection
     * @var array
     */
    protected $_subjectReflection;


    /**
     * Subject placeholders
     * @var array
     */
    protected $_subjectPlaceholders;


    /**
     * Returns related table name
     * @return string
     */
    protected function _getTableName()
    {
        return 'app.workflow_event_tab';
    }


    /**
     * Returns event name
     * @return string
     */
    public function getName()
    {
        return $this->getTranslatedName();
    }


    /**
     * Returns event identifier
     * @return string
     */
    public function identifier(): string
    {
        $beforeOrAfter = null;

        if (func_num_args()) {
            $beforeOrAfter = func_get_arg(0);
        }

        if (!$beforeOrAfter) {
            $beforeOrAfter = self::AFTER;
        }

        return 'workflow:' . Filter::filterToDash($beforeOrAfter . $this->id());
    }


    /**
     * Fires event
     *
     * @param string $event
     * @param object $subject
     * @param mixed $data
     * @param string $beforeOrAfter
     * @throws VelisException
     * @throws InvalidArgumentException
     */
    public static function fire($event, $subject, $data = null, $beforeOrAfter = self::AFTER)
    {
        if (!App::hasModule('Workflow')) {
            return;
        }

        if ($instance = self::get($event)) {
            App::getEvents()->fire(
                $instance->identifier($beforeOrAfter),
                $subject,
                $data
            );
        // CAFM don't have TicketLabelRemoved event by default
        // so there is no need to throw an exception
        } elseif (App::devMode() && $event !== 'TicketLabelRemoved') {
            VelisException::raise("Event $event not found!");
        }
    }


    /**
     * Fires event's "before" actions
     *
     * @param string $event
     * @param object $subject
     * @param mixed $data
     * @return void
     * @throws InvalidArgumentException
     * @throws VelisException
     */
    public static function fireBefore($event, $subject, $data = null)
    {
        self::fire($event, $subject, $data, self::BEFORE);
    }


    /**
     * Returns subject reflection
     * @return array
     */
    public function getSubjectReflection()
    {
        if (!isset($this->_subjectReflection)) {
            $this->_subjectReflection = [];

            if ($class = $this->subject_class) {
                $this->_subjectReflection = $class::getWorkflowReflection();
            }
        }
        return $this->_subjectReflection;
    }


    /**
     * Returns subject available placeholders
     * @return array
     */
    public function getSubjectPlaceholders()
    {
        if (!isset($this->_subjectPlaceholders)) {
            $this->_subjectPlaceholders = [];

            if ($class = $this->subject_class) {
                $this->_subjectPlaceholders = $class::getWorkflowPlaceholders();
            }
        }
        return $this->_subjectPlaceholders;
    }


    /**
     * Returns available action types
     * @return ActionType[]
     * @throws ReflectionException
     */
    public function getActionTypes(): array
    {
        if (!isset($this->_actionTypes)) {
            $this->_actionTypes = ActionType::sort(
                ActionType::listAll(
                    $this->_getPrimaryKeyParam()
                )
            );
        }

        return $this->_actionTypes;
    }


    /**
     * Returns events sorted by names
     * @return Event[]
     */
    public static function listSorted()
    {
        static $events = null;

        if (!$events) {
            $events = [];

            foreach (self::sort(self::listCached()) as $event) {
                $events[$event->id()] = $event;
            }
        }

        return $events;
    }
}
