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

use ArrayObject;
use Enlight_Event_EventManager;
use Enlight_Event_Handler_Default;
use Enlight_Hook_HookArgs;
use ReflectionObject;
use Zend_Db_Adapter_Pdo_Abstract;

/**
 * Note: Dynamically registering hook listeners with the event manager only works if the respective hook proxy is
 * guaranteed to contain overrides for the hooked methods. For the listeners created by this service this is facilitated
 * by registering {@link Shopware\Plugins\ViisonCommon\Subscribers\Components\DocumentComponentDummySubscriber}, which
 * is done alongside registering all other ViisonCommon subscribers.
 */
class DocumentComponentListenerService
{
    /**
     * @var Enlight_Event_EventManager
     */
    protected $eventManager;

    /**
     * @var Zend_Db_Adapter_Pdo_Abstract
     */
    protected $database;

    /**
     * @param Enlight_Event_EventManager $eventManager
     * @param Zend_Db_Adapter_Pdo_Abstract $database
     */
    public function __construct(Enlight_Event_EventManager $eventManager, Zend_Db_Adapter_Pdo_Abstract $database)
    {
        $this->eventManager = $eventManager;
        $this->database = $database;
    }

    /**
     * Registers a one-off (before) hook handler on {@link Shopware_Models_Document_Order::processPositions()} that
     * allows changing the positions that will be visible on the document.
     *
     * @param callable $positionFilter A `callable` that expects an instance of type `Shopware_Components_Document`
     *        as its first argument, which is the document component that is creating a document, and an `array` of
     *        positions as its second argument. Finally the callable must return a value of type `array` that represents
     *        the positions that should be visible on the document. These may include positions that were not contained
     *        in the original array of positions.
     */
    public function filterPositionsOnNextDocument(callable $positionFilter)
    {
        $listener = null;
        $listener = new Enlight_Event_Handler_Default(
            'Shopware_Models_Document_Order::processPositions::before',
            function (Enlight_Hook_HookArgs $args) use (&$listener, $positionFilter) {
                // Remove this listener to ensure it is only called once
                $this->eventManager->removeListener($listener);

                $documentModel = $args->getSubject();

                // Shopware_Models_Document_Order::$_positions is protected, hence access it using reflection
                $reflectionObject = new ReflectionObject($documentModel);
                $reflectionProperty = $reflectionObject->getProperty('_positions');
                $reflectionProperty->setAccessible(true);

                // The positions property is actually an ArrayObject, which does not work with array_filter, array_map
                // etc. so we temporarily create a real array before passing it to the filter
                $positions = $reflectionProperty->getValue($documentModel)->getArrayCopy();
                $filteredPositions = $positionFilter($documentModel, $positions);

                // Fix the array indices to remove gaps, which might have been created by filtering the positions
                $filteredPositions = array_values($filteredPositions);

                // Write the filtered positions back to the document model
                $reflectionProperty->setValue($documentModel, new ArrayObject($filteredPositions));
            }
        );
        $this->eventManager->registerListener($listener);
    }

    /**
     * Registers two one-off hook handlers (befor and after) on {@link \Shopware_Components_Document::saveDocument()}
     * that allow the creation of a new document with an already existing type for the same order.
     */
    public function allowNextDocumentToHaveTypeOfExistingDocument()
    {
        // Register a listener that sets the `userID` of all documents of the same order to -1
        $beforeListener = null;
        $beforeListener = new Enlight_Event_Handler_Default(
            'Shopware_Components_Document::saveDocument::before',
            function (Enlight_Hook_HookArgs $args) use (&$beforeListener) {
                // Remove this listener to ensure it is only called once
                $this->eventManager->removeListener($beforeListener);

                $documentComponent = $args->getSubject();
                $this->database->query(
                    'UPDATE `s_order_documents`
                    SET `userID` = -1
                    WHERE `orderID` = :orderId',
                    [
                        'orderId' => $documentComponent->_order->order->id,
                    ]
                );
            }
        );
        $this->eventManager->registerListener($beforeListener);

        // Register a listener that resets the `userID` of all documents of the same order to their correct value
        $afterListener = null;
        $afterListener = new Enlight_Event_Handler_Default(
            'Shopware_Components_Document::saveDocument::after',
            function (Enlight_Hook_HookArgs $args) use (&$afterListener) {
                // Remove this listener to ensure it is only called once
                $this->eventManager->removeListener($afterListener);

                $documentComponent = $args->getSubject();
                $this->database->query(
                    'UPDATE `s_order_documents`
                    INNER JOIN `s_order`
                        ON `s_order_documents`.`orderID` = `s_order`.`id`
                    SET `s_order_documents`.`userID` = `s_order`.`userID`
                    WHERE `s_order`.`id` = :orderId',
                    [
                        'orderId' => $documentComponent->_order->order->id,
                    ]
                );
            }
        );
        $this->eventManager->registerListener($afterListener);
    }
}
