<?php
// Copyright (c) Pickware GmbH. All rights reserved.
// This file is part of software that is released under a proprietary license.
// You must not copy, modify, distribute, make publicly available, or execute
// its contents or parts thereof without express permission by the copyright
// holder, unless otherwise permitted by law.

namespace Shopware\Plugins\ViisonCommon\Classes;

use ReflectionClass;
use Enlight\Event\SubscriberInterface;
use Shopware_Components_Plugin_Bootstrap as PluginBootstrap;

abstract class AbstractSubscriberRegistrator
{
    /**
     * @var PluginBootstrap $pluginBootstrap
     */
    protected $pluginBootstrap;

    /**
     * @param PluginBootstrap $pluginBootstrap
     */
    public function __construct(PluginBootstrap $pluginBootstrap)
    {
        $this->pluginBootstrap = $pluginBootstrap;
    }

    /**
     * Returns an array containing instances of all subscribers that will be registered
     * when calling 'registerSubscribers()'.
     *
     * @return SubscriberInterface[]
     */
    abstract protected function createSubscribers();

    /**
     * Creates instances of all ViisonShippingCommon subscribers and adds them to the event manager,
     * if no instance if the same subscriber class has already been added.
     */
    public function registerSubscribers()
    {
        $eventManager = $this->pluginBootstrap->get('events');
        foreach ($this->createSubscribers() as $subscriber) {
            if (!$this->isSubscriberRegistered($subscriber)) {
                $eventManager->addSubscriber($subscriber);
            }
        }
    }

    /**
     * Checks the event manager for a registered subscriber of the same class as the given
     * subscriber and, if found, returns true. Otherwise false.
     *
     * @param SubscriberInterface $subscriber
     * @return boolean
     */
    protected function isSubscriberRegistered(SubscriberInterface $subscriber)
    {
        // Check the passed subscriber for any subscribed events
        $subscribedEvents = $subscriber::getSubscribedEvents();
        if (!$subscribedEvents) {
            return true;
        }

        // Get all currently registered event listeners using reflection. This is necessary, because calling
        // 'getListener()' or 'getAllListeners()' both trigger the *lazy loading* of the ContainerAwareEventManager
        // (which is not lazy at all) and causes all suscribers on services to be initialized. However, since this
        // method is executed really 'early' (before the main dispatch loop starts), some DI resource like 'shop' are
        // not yet available. If now a subscriber that is loaded *lazily* depends on one of these resources, the DI
        // container throws an exception (see also https://github.com/VIISON/ShopwarePickwareERP/issues/680).
        /** @var \Enlight_Event_EventManager $eventManager */
        $eventManager = $this->pluginBootstrap->get('events');
        $reflection = new \ReflectionClass($eventManager);
        $property = $reflection->getProperty('listeners');
        $property->setAccessible(true);
        $listeners = $property->getValue($eventManager);

        // Use the first subscribed event to determine whether the passed subscriber is already registered
        $eventNames = array_keys($subscribedEvents);
        $eventName = mb_strtolower($eventNames[0]);
        if (!isset($listeners[$eventName])) {
            return false;
        }
        foreach ($listeners[$eventName] as $listener) {
            if ($listener instanceof \Enlight_Event_Handler_Default) {
                $listenerInstance = $listener->getListener();
                if (is_array($listenerInstance) && count($listenerInstance) > 0 && is_a($listenerInstance[0], get_class($subscriber))) {
                    return true;
                }
            }
        }

        return false;
    }
}
