<?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;

use PHPCR\NodeInterface;
use ProxyManager\Factory\LazyLoadingGhostFactory;
use ProxyManager\Proxy\LazyLoadingInterface;
use Sulu\Component\DocumentManager\Collection\ChildrenCollection;
use Sulu\Component\DocumentManager\Collection\ReferrerCollection;
use Sulu\Component\DocumentManager\Event\HydrateEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Handle creation of proxies.
 */
class ProxyFactory
{
    /**
     * @var LazyLoadingGhostFactory
     */
    private $proxyFactory;

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

    /**
     * @var DocumentRegistry
     */
    private $registry;

    /**
     * @var MetadataFactoryInterface
     */
    private $metadataFactory;

    public function __construct(
        LazyLoadingGhostFactory $proxyFactory,
        EventDispatcherInterface $dispatcher,
        DocumentRegistry $registry,
        MetadataFactoryInterface $metadataFactory
    ) {
        $this->proxyFactory = $proxyFactory;
        $this->dispatcher = $dispatcher;
        $this->registry = $registry;
        $this->metadataFactory = $metadataFactory;
    }

    /**
     * Create a new proxy object from the given document for
     * the given target node.
     *
     * TODO: We only pass the document here in order to correctly evaluate its locale
     *       later. I wonder if it necessary.
     *
     * @param object $fromDocument
     * @param array $options
     *
     * @return \ProxyManager\Proxy\GhostObjectInterface
     */
    public function createProxyForNode($fromDocument, NodeInterface $targetNode, $options = [])
    {
        // if node is already registered then just return the registered document
        $locale = $this->registry->getOriginalLocaleForDocument($fromDocument);
        if ($this->registry->hasNode($targetNode, $locale)) {
            $document = $this->registry->getDocumentForNode($targetNode, $locale);

            // If the parent is not loaded in the correct locale, reload it in the correct locale.
            if ($this->registry->getOriginalLocaleForDocument($document) !== $locale) {
                $hydrateEvent = new HydrateEvent($targetNode, $locale);
                $hydrateEvent->setDocument($document);
                $this->dispatcher->dispatch($hydrateEvent, Events::HYDRATE);
            }

            return $document;
        }

        $initializer = function(LazyLoadingInterface $document, $method, array $parameters, &$initializer) use (
            $targetNode,
            $options,
            $locale
        ) {
            $hydrateEvent = new HydrateEvent($targetNode, $locale, $options);
            $hydrateEvent->setDocument($document);
            $this->dispatcher->dispatch($hydrateEvent, Events::HYDRATE);

            $initializer = null;
        };

        $targetMetadata = $this->metadataFactory->getMetadataForPhpcrNode($targetNode);
        $proxy = $this->proxyFactory->createProxy($targetMetadata->getClass(), $initializer);
        $locale = $this->registry->getOriginalLocaleForDocument($fromDocument);
        $this->registry->registerDocument($proxy, $targetNode, $locale);

        return $proxy;
    }

    /**
     * Create a new children collection for the given document.
     *
     * @param object $document
     *
     * @return ChildrenCollection
     */
    public function createChildrenCollection($document, array $options = [])
    {
        $node = $this->registry->getNodeForDocument($document);
        $locale = $this->registry->getOriginalLocaleForDocument($document);

        return new ChildrenCollection(
            $node,
            $this->dispatcher,
            $locale,
            $options
        );
    }

    /**
     * Create a new collection of referrers from a list of referencing items.
     *
     * @param object $document
     *
     * @return ReferrerCollection
     */
    public function createReferrerCollection($document)
    {
        $node = $this->registry->getNodeForDocument($document);
        $locale = $this->registry->getOriginalLocaleForDocument($document);

        return new ReferrerCollection(
            $node,
            $this->dispatcher,
            $locale
        );
    }
}
