<?php

namespace xmvc;

use Closure;
use xmvc\event\BaseEvent;
use xmvc\interface\IEventListener;
use xmvc\interface\IEventSubscriber;

/**
 * 事件管理者(Manager)调度器(Dispatcher)类
 */
class EventManager
{
    protected static $instance;
    protected $listeners = [];
    protected $subscribers = [];
    /** 存储事件记录的数组, 格式为 [['name' => string(事件名称), 'event' => BaseEvent], ...] */
    private array $eventLog = [];

    public function __construct()
    {
        $cfg = loadConfig('events');
        foreach ($cfg as $eventName => $listeners) {
            foreach ($listeners as $listener) {
                $this->listeners[$eventName][] = [$listener, 0];
            }
        }
        $services = Container::getByPrefix('subscriber.');
        foreach ($services as $serviceName => $subscriber) {
            $this->addSubscriber($subscriber, $serviceName);
        }
    }


    /** 获取事件调度器维一实例 */
    public static function getInstance(): self
    {
        if (null === static::$instance) {
            static::$instance = new static();
        }
        return static::$instance;
    }

    /**
     *  优先级排序
     */
    public function sortPriority(array &$arr): void
    {
        usort($arr, function ($a, $b) {
            return $b[1] <=> $a[1];
        });
    }

    /**
     * 添加监听器
     */
    public function addListener(string $eventName, IEventListener|array $listeners, int $priority = 0): void
    {
        if (!is_array($listeners)) {
            $listeners = [$listeners];
        }
        foreach ($listeners as $listener) {
            $this->listeners[$eventName][] = [
                $listener, // 监听器实例 或 延时创建 [类,创建参数]
                $priority // 优先级
            ];
        }
        $this->sortPriority($this->listeners[$eventName]);
    }

    /**
     * 添加订阅者
     */
    public function addSubscriber(IEventSubscriber|array $object, string $serviceName = ''): void
    {
        $isArray = is_array($object);
        $subscriber = $isArray ? $object[0] : $object;

        $subscribedEvents = $subscriber::getSubscribedEvents();
        foreach ($subscribedEvents as $eventName => $item) {
            [$functionName, $priority] = $item;
            $this->subscribers[$eventName][] = [
                [
                    $subscriber, /// 订阅者类名字符串 或 IEventSubscriber 实例
                    $functionName, // 处理事件的函数名
                    $serviceName // 容器中的服务名称字符串, 延时创建
                ],
                $priority, // 优先级别
            ];
            $this->sortPriority($this->subscribers[$eventName]);
        }
    }

    /**
     * 触发事件, 发送事件给订阅者和监听器
     *
     * @param string $eventName
     * @param BaseEvent $event
     */
    public function trigger(string $eventName, BaseEvent $event): void
    {
        // 订阅者 - 优先
        if (isset($this->subscribers[$eventName])) {
            foreach ($this->subscribers[$eventName] as $i => $item) {
                [$subscriber, $priority] = $item;
                if (is_string($subscriber[0])) {
                    // 延时创建
                    [$subscriber, $functionName, $serviceName] = $subscriber;
                    $subscriber = [Container::lazyCreate($serviceName), $functionName];
                    $this->subscribers[$eventName][$i][0] = $subscriber;
                }
                call_user_func($subscriber, $event);
                if ($event->isPropagationStopped()) {
                    return; //中断传播
                }
            }
        }

        // 监听器
        if (isset($this->listeners[$eventName])) {
            foreach ($this->listeners[$eventName] as $i => $item) {
                [$listener, $priority] = $item;
                if (!is_object($listener)) {
                    // 延时创建
                    $listener[1] = $listener[1] ?: [];
                    $listener = new $listener[0](...$listener[1]);
                    $this->listeners[$eventName][$i] = [$listener, $priority];
                }
                $listener->handle($event);
                if ($event->isPropagationStopped()) {
                    return; //中断传播
                }
            }
        }
    }

    /**
     * 按指定的类名遍历订阅者列表
     * @param string $eventName 事件名称
     * @param string|array $eachClass 要遍历的订阅者类名
     * @param Closure $function 处理函数
     * @param boolean $isRestIndex 是否重置数组索引
     */
    public function eachSubscribers(string $eventName, string|array $eachClass, Closure $function, bool $isResetIndex = false): void
    {
        $this->eachHandlers($eventName, $eachClass, $function, $this->subscribers, $isResetIndex, 'subscriber');
    }

    /**
     * 按指定的类名遍历监听器列表
     *
     * @param string $eventName 事件名称
     * @param string|array $eachClass 要遍历的监听器类名
     * @param Closure $function 处理函数
     * @param boolean $isRestIndex 是否重置数组索引
     */
    public function eachListeners(string $eventName, string|array $eachClass, Closure $function, bool $isResetIndex = false): void
    {
        $this->eachHandlers($eventName, $eachClass, $function, $this->listeners, $isResetIndex, 'listener');
    }

    /**
     * 通用的遍历处理函数
     */
    protected function eachHandlers(string $eventName, string|array $eachClass, Closure $function, array &$handlers, bool $isResetIndex, string $type): void
    {
        if (!is_array($eachClass)) {
            $eachClass = [$eachClass];
        }
        if (isset($handlers[$eventName])) {
            foreach ($handlers[$eventName] as $index => $item) {
                [$handler, $priority] = $item;
                if ($type === 'subscriber') {
                    $class = is_string($handler[0]) ? $handler[0] : $handler[0]::class;
                } else {
                    $class = is_object($handler) ? $handler::class : $handler[0];
                }
                foreach ($eachClass as $eClass) {
                    if ($class == $eClass) {
                        $function($index);
                    }
                }
            }
            if ($isResetIndex) {
                $handlers[$eventName] = array_values($handlers[$eventName]); // 重置数组索引
            }
        }
    }

    /**
     * 设置监听器优先级别
     *
     * @param string $eventName 事件名称
     * @param string|array $listenteCalss 监听器类名
     * @param int $priority 优先级别, 数字越大优先级越高
     */
    public function setPriorityListenter(string $eventName, string|array $listenteCalss, int $priority): void
    {
        $this->eachListeners(
            $eventName,
            $listenteCalss,
            function (int $index) use ($eventName, $priority) {
                $this->listeners[$eventName][$index][1] = $priority;
                $this->sortPriority($this->listeners[$eventName]);
            }
        );
    }

    /**
     * 设置订阅者优先级别
     *
     * @param string $eventName 事件名称
     * @param string|array $subscriberCalss 订阅者类名
     * @param int $priority 优先级别, 数字越大优先级越高
     */
    public function setPrioritySubscriber(string $eventName, string|array $subscriberCalss, int $priority): void
    {
        $this->eachSubscribers(
            $eventName,
            $subscriberCalss,
            function (int $index) use ($eventName, $priority) {
                $this->subscribers[$eventName][$index][1] = $priority;
                $this->sortPriority($this->subscribers[$eventName]);
            }
        );
    }

    /**
     * 移除订阅者
     *
     * @param string $eventName 事件名称
     * @param string|array $removeSubscriberCalss 要移除订阅者类名
     */
    public function removeSubscriber(string $eventName, string|array $removeSubscriberCalss): void
    {
        $this->eachSubscribers(
            $eventName,
            $removeSubscriberCalss,
            function (int $index) use ($eventName) {
                unset($this->subscribers[$eventName][$index]);
            },
            true
        );
    }

    /**
     * 移除监听器
     *
     * @param string $eventName 事件名称
     * @param string|array $removeListenerCalss 要移除的事件监听器类名
     */
    public function removeListenter(string $eventName, string|array $removeListenerCalss): void
    {
        $this->eachListeners(
            $eventName,
            $removeListenerCalss,
            function (int $index) use ($eventName) {
                unset($this->listeners[$eventName][$index]);
            },
            true
        );
    }

    /**
     * 获取指定事件名称下的所有订阅者和监听器
     *
     * @param string $eventName 事件名称
     * @return array ['subscribers' =>[ 订阅者数组],'subscribers' =>[ 订阅者数组]]
     */
    public function getListeners(string $eventName): array
    {
        $subscribers = isset($this->subscribers[$eventName]) ? $this->subscribers[$eventName] : [];
        $listeners = isset($this->listeners[$eventName]) ? $this->listeners[$eventName] : [];
        return ['subscribers' => $subscribers, 'listeners' => $listeners];
    }

    /**
     * 记录事件和数据，并触发记录的事件
     *
     * @param string $eventName 事件名称
     * @param BaseEvent $event 传递给事件监听器的事件 $event
     */
    public function record(string $eventName, BaseEvent $event): void
    {
        $this->eventLog[] = ['name' => $eventName, 'event' => $event];
        $this->trigger($eventName, $event);
    }

    /** 重放记录的事件 */
    public function replay(): void
    {
        foreach ($this->eventLog as $event) {
            $this->trigger($event['name'], $event['event']);
        }
    }

    /** 清除事件记录 */
    public function clearLog(): void
    {
        $this->eventLog = [];
    }
}
