<?php

/*
 * This file is part of Sulu.
 *
 * (c) Sulu GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Component\DocumentManager\Subscriber\Phpcr;

use Sulu\Component\DocumentManager\Event\ConfigureOptionsEvent;
use Sulu\Component\DocumentManager\Event\FindEvent;
use Sulu\Component\DocumentManager\Event\HydrateEvent;
use Sulu\Component\DocumentManager\Events;
use Sulu\Component\DocumentManager\Exception\DocumentManagerException;
use Sulu\Component\DocumentManager\Exception\DocumentNotFoundException;
use Sulu\Component\DocumentManager\MetadataFactoryInterface;
use Sulu\Component\DocumentManager\NodeManager;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * This class is responsible for finding documents.
 */
class FindSubscriber implements EventSubscriberInterface
{
    /**
     * @var MetadataFactoryInterface
     */
    private $metadataFactory;

    /**
     * @var NodeManager
     */
    private $nodeManager;

    /**
     * @var EventDispatcherInterface
     */
    private $eventDispatcher;

    public function __construct(
        MetadataFactoryInterface $metadataFactory,
        NodeManager $nodeManager,
        EventDispatcherInterface $eventDispatcher
    ) {
        $this->metadataFactory = $metadataFactory;
        $this->nodeManager = $nodeManager;
        $this->eventDispatcher = $eventDispatcher;
    }

    public static function getSubscribedEvents()
    {
        return [
            Events::FIND => ['handleFind', 500],
            Events::CONFIGURE_OPTIONS => 'configureOptions',
        ];
    }

    public function configureOptions(ConfigureOptionsEvent $event)
    {
        $options = $event->getOptions();
        $options->setDefaults([
            'type' => null,
        ]);
    }

    /**
     * @throws DocumentManagerException
     * @throws DocumentNotFoundException
     */
    public function handleFind(FindEvent $event)
    {
        $options = $event->getOptions();
        $aliasOrClass = $options['type'];
        $node = $this->nodeManager->find($event->getId());

        $hydrateEvent = new HydrateEvent($node, $event->getLocale(), $options);
        $this->eventDispatcher->dispatch($hydrateEvent, Events::HYDRATE);
        $document = $hydrateEvent->getDocument();

        if ($aliasOrClass) {
            $this->checkAliasOrClass($aliasOrClass, $document);
        }

        $event->setDocument($hydrateEvent->getDocument());
    }

    private function checkAliasOrClass($aliasOrClass, $document)
    {
        if ($this->metadataFactory->hasAlias($aliasOrClass)) {
            $class = $this->metadataFactory->getMetadataForAlias($aliasOrClass)->getClass();
        } elseif (!\class_exists($aliasOrClass)) {
            throw new DocumentManagerException(\sprintf(
                'Unknown class specified and no alias exists for "%s", known aliases: "%s"',
                $aliasOrClass, \implode('", "', $this->metadataFactory->getAliases())
            ));
        } else {
            $class = $aliasOrClass;
        }

        if (\get_class($document) !== $class) {
            throw new DocumentNotFoundException(\sprintf(
                'Requested document of type "%s" but got document of type "%s"',
                $aliasOrClass,
                \get_class($document)
            ));
        }
    }
}
