<?php

/*
 * This source file is available under two different licenses:
 *   - GNU General Public License version 3 (GPLv3)
 *   - DACHCOM Commercial License (DCL)
 * Full copyright and license information is available in
 * LICENSE.md which is distributed with this source code.
 *
 * @copyright  Copyright (c) DACHCOM.DIGITAL AG (https://www.dachcom-digital.com)
 * @license    GPLv3 and DCL
 */

namespace FormBuilderBundle\EventSubscriber;

use FormBuilderBundle\Configuration\Configuration;
use FormBuilderBundle\Event\OutputWorkflow\OutputWorkflowSignalEvent;
use FormBuilderBundle\Event\OutputWorkflow\OutputWorkflowSignalsEvent;
use FormBuilderBundle\EventSubscriber\SignalStorage\ArraySignalStorage;
use FormBuilderBundle\EventSubscriber\SignalStorage\ProviderAwareStorageInterface;
use FormBuilderBundle\EventSubscriber\SignalStorage\SignalStorageInterface;
use FormBuilderBundle\FormBuilderEvents;
use FormBuilderBundle\Registry\StorageProviderRegistry;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class SignalSubscribeHandler implements EventSubscriberInterface
{
    public const CHANNEL_FUNNEL_INITIATE = 'funnel.initiate';
    public const CHANNEL_FUNNEL_PROCESS = 'funnel.process';
    public const CHANNEL_OUTPUT_WORKFLOW = 'output_workflow';

    protected Configuration $configuration;
    protected StorageProviderRegistry $storageProviderRegistry;
    protected EventDispatcherInterface $eventDispatcher;
    protected SignalStorageInterface $signalStorage;
    protected ?string $channel = null;

    public function __construct(
        Configuration $configuration,
        StorageProviderRegistry $storageProviderRegistry,
        EventDispatcherInterface $eventDispatcher
    ) {
        $this->configuration = $configuration;
        $this->storageProviderRegistry = $storageProviderRegistry;
        $this->eventDispatcher = $eventDispatcher;
    }

    public static function getSubscribedEvents(): array
    {
        return [
            OutputWorkflowSignalEvent::NAME => 'addSignal',
        ];
    }

    public function addSignal(OutputWorkflowSignalEvent $signalEvent): void
    {
        if ($this->channel === null) {
            throw new \Exception('Cannot add signal, no channel has been defined.');
        }

        $this->signalStorage->storeSignal($signalEvent);
    }

    public function listen(string $channel, array $context = []): void
    {
        if ($this->channel !== null) {
            return;
        }

        $this->channel = $channel;
        $this->signalStorage = $this->buildSignalStorage($channel, $context);
        $this->eventDispatcher->addSubscriber($this);
    }

    public function broadcast(array $context = []): void
    {
        $channelName = $this->channel;

        if ($channelName === null) {
            return;
        }

        $this->channel = null;
        $this->eventDispatcher->removeSubscriber($this);

        $this->eventDispatcher->dispatch(
            new OutputWorkflowSignalsEvent(
                $channelName,
                $this->signalStorage->getSignals(),
                $context
            ),
            FormBuilderEvents::OUTPUT_WORKFLOW_SIGNALS
        );
    }

    private function buildSignalStorage(string $channel, array $context): SignalStorageInterface
    {
        if ($channel === self::CHANNEL_OUTPUT_WORKFLOW) {
            return new ArraySignalStorage();
        }

        if ($channel === self::CHANNEL_FUNNEL_INITIATE) {
            return new ArraySignalStorage();
        }

        if ($channel === self::CHANNEL_FUNNEL_PROCESS) {
            $funnelConfiguration = $this->configuration->getConfig('funnel');

            $signalStorageClass = $funnelConfiguration['signal_storage_class'];
            $signalStorageProvider = $funnelConfiguration['storage_provider'];

            /** @var SignalStorageInterface $signalStorageClass */
            $signalStorage = new $signalStorageClass($context);

            if ($signalStorage instanceof ProviderAwareStorageInterface) {
                $signalStorage->setStorageProvider($this->storageProviderRegistry->get($signalStorageProvider));
            }

            return $signalStorage;
        }

        throw new \Exception(sprintf('Cannot determinate signal storage for channel "%s"', $channel));
    }
}
