<?php

namespace Croogo\Core\Event;

use Cake\Cache\Cache;
use Cake\Core\App;
use Cake\Core\Configure;
use Cake\Core\Plugin;
use Cake\Event\EventManager as CakeEventManager;
use Cake\Log\Log;

/**
 * Croogo Event Manager class
 *
 * Descendant of EventManager, customized to map event listener objects
 *
 * @since 1.4
 * @package Croogo.Croogo.Event
 * @see EventManager
 * @author   Rachman Chavik <rchavik@xintesa.com>
 * @license  http://www.opensource.org/licenses/mit-license.php The MIT License
 * @link     http://www.croogo.org
 */
class EventManager extends CakeEventManager
{

    /**
     * A map of registered event listeners
     */
    protected $_listenersMap = [];

    /**
     * Returns the globally available instance of a EventManager
     * @return EventManager the global event manager
     */
    public static function instance($manager = null)
    {
        if (empty(self::$_generalManager)) {
            return parent::instance(new EventManager());
        }

        return parent::instance($manager);
    }

    /**
     * Load Event Handlers during bootstrap.
     *
     * Plugins can add their own custom EventHandler in Config/events.php
     * with the following format:
     *
     * return array(
     *     'EventHandlers' => array(
     *         'Example.ExampleEventHandler' => array(
     *             'eventKey' => null,
     *             'options' => array(
     *                 'priority' => 1,
     *                 'passParams' => false,
     *                 'className' => 'Plugin.ClassName',
     *      )));
     *
     * @return void
     */
    public static function loadListeners()
    {
        $eventManager = EventManager::instance();
        $cached = Cache::read('EventHandlers', 'cached_settings');
        if ($cached === false) {
            $eventHandlers = Configure::read('EventHandlers');
            $validKeys = ['eventKey' => null, 'options' => []];
            $cached = [];
            if (!empty($eventHandlers) && is_array($eventHandlers)) {
                foreach ($eventHandlers as $eventHandler => $eventOptions) {
                    $eventKey = null;
                    if (is_numeric($eventHandler)) {
                        $eventHandler = $eventOptions;
                        $eventOptions = [];
                    }
                    list($plugin, $class) = pluginSplit($eventHandler);
                    if (!empty($eventOptions)) {
                        extract(array_intersect_key($eventOptions, $validKeys));
                    }
                    if (isset($eventOptions['options']['className'])) {
                        list($plugin, $class) = pluginSplit($eventOptions['options']['className']);
                    }
                    $class = App::className($eventHandler, 'Event');
                    if (class_exists($class)) {
                        $cached[] = compact('plugin', 'class', 'eventKey', 'eventOptions');
                    } else {
                        Log::error(__d('croogo', 'EventHandler %s not found in plugin %s', $class, $plugin));
                    }
                }
                Cache::write('EventHandlers', $cached, 'cached_settings');
            }
        }
        foreach ($cached as $cache) {
            extract($cache);
            if (Plugin::isLoaded($plugin)) {
                $class = App::className($class, 'Event');
                $settings = isset($eventOptions['options']) ? $eventOptions['options'] : [];
                $listener = new $class($settings);
                $eventManager->on($listener, $eventKey, $eventOptions);
            }
        }
    }

    /**
     * Adds a new listener to an event.
     * @see EventManager::attach()
     * @return void
     */
    public function attach($callable, $eventKey = null, array $options = [])
    {
        parent::on($eventKey, $options, $callable);
        if (is_object($callable)) {
            $key = get_class($callable);
            $this->_listenersMap[$key] = $callable;
        }
    }

    /**
     * Removes a listener from the active listeners.
     * @see EventManager::detach()
     * @return void
     */
    public function detach($callable, $eventKey = null)
    {
        if (is_object($callable)) {
            $key = get_class($callable);
            unset($this->_listenersMap[$key]);
        }
        parent::off($callable, $eventKey);
    }

    /**
     * Detach all listener objects belonging to a plugin
     * @param $plugin string
     * @return void
     */
    public function detachPluginSubscribers($plugin)
    {
        $eventHandlers = Configure::read('EventHandlers');
        if (empty($eventHandlers)) {
            return;
        }
        $eventHandlers = array_keys($eventHandlers);
        $eventHandlers = preg_grep('/^' . preg_quote($plugin, '/') . '/', $eventHandlers);
        foreach ($eventHandlers as $eventHandler) {
            $className = App::className($eventHandler, 'Event');
            if (isset($this->_listenersMap[$className])) {
                $this->detach($this->_listenersMap[$className]);
            }
        }
    }
}
