<?php
/**
 * Pimcore
 *
 * This source file is available under two different licenses:
 * - GNU General Public License version 3 (GPLv3)
 * - Pimcore Enterprise License (PEL)
 * Full copyright and license information is available in
 * LICENSE.md which is distributed with this source code.
 *
 * @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
 * @license    http://www.pimcore.org/license     GPLv3 and PEL
 */

namespace Pimcore\Bundle\CoreBundle\EventListener;

use Pimcore\Event\AdminEvents;
use Pimcore\Event\AssetEvents;
use Pimcore\Event\DataObjectEvents;
use Pimcore\Event\DocumentEvents;
use Pimcore\Event\Model\ElementEventInterface;
use Pimcore\Model\Asset;
use Pimcore\Model\DataObject;
use Pimcore\Model\DataObject\ClassDefinition;
use Pimcore\Model\DataObject\Concrete as ConcreteObject;
use Pimcore\Model\Document;
use Pimcore\Model\Element\AbstractElement;
use Pimcore\Model\Element\Service;
use Pimcore\Model\Element\WorkflowState;
use Pimcore\Workflow\ActionsButtonService;
use Pimcore\Workflow\Manager;
use Pimcore\Workflow\Place;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Workflow\Registry;

class WorkflowManagementListener implements EventSubscriberInterface
{
    /**
     * @var bool
     */
    protected $enabled = true;

    /**
     * @var Manager
     */
    private $workflowManager;

    /**
     * @var Registry
     */
    private $workflowRegistry;

    /**
     * @var Place\StatusInfo
     */
    private $placeStatusInfo;

    /**
     * @var RequestStack
     */
    private $requestStack;

    /**
     * @var ActionsButtonService
     */
    private $actionsButtonService;

    public function __construct(Manager $workflowManager, Registry $workflowRegistry, Place\StatusInfo $placeStatusInfo, RequestStack $requestStack, ActionsButtonService $actionsButtonService)
    {
        $this->workflowManager = $workflowManager;
        $this->workflowRegistry = $workflowRegistry;
        $this->placeStatusInfo = $placeStatusInfo;
        $this->requestStack = $requestStack;
        $this->actionsButtonService = $actionsButtonService;
    }

    /**
     * @inheritDoc
     */
    public static function getSubscribedEvents()
    {
        return [

            DataObjectEvents::POST_DELETE => 'onElementPostDelete',
            DocumentEvents::POST_DELETE => 'onElementPostDelete',
            AssetEvents::POST_DELETE => 'onElementPostDelete',

            AdminEvents::OBJECT_GET_PRE_SEND_DATA => 'onAdminElementGetPreSendData',
            AdminEvents::ASSET_GET_PRE_SEND_DATA => 'onAdminElementGetPreSendData',
            AdminEvents::DOCUMENT_GET_PRE_SEND_DATA => 'onAdminElementGetPreSendData',
        ];
    }

    /**
     * Cleanup status information on element delete
     *
     * @param ElementEventInterface $e
     */
    public function onElementPostDelete(ElementEventInterface $e)
    {
        /**
         * @var Asset|Document|ConcreteObject $element
         */
        $element = $e->getElement();

        $list = new WorkflowState\Listing;
        $list->setCondition('cid = ? and ctype = ?', [$element->getId(), Service::getType($element)]);

        foreach ($list->load() as $item) {
            $item->delete();
        }
    }

    /**
     * Fired before information is sent back to the admin UI about an element
     *
     * @param GenericEvent $e
     *
     * @throws \Exception
     */
    public function onAdminElementGetPreSendData(GenericEvent $e)
    {
        $element = self::extractElementFromEvent($e);
        $data = $e->getArgument('data');

        //create a new namespace for WorkflowManagement
        //set some defaults
        $data['workflowManagement'] = [
            'hasWorkflowManagement' => false,
        ];

        foreach ($this->workflowManager->getAllWorkflows() as $workflowName) {
            $workflow = $this->workflowManager->getWorkflowIfExists($element, $workflowName);
            $workflowConfig = $this->workflowManager->getWorkflowConfig($workflowName);

            if (empty($workflow)) {
                continue;
            }

            $data['workflowManagement']['hasWorkflowManagement'] = true;
            $data['workflowManagement']['workflows'] = $data['workflowManagement']['workflows'] ?? [];

            $allowedTransitions = $this->actionsButtonService->getAllowedTransitions($workflow, $element);
            $globalActions = $this->actionsButtonService->getGlobalActions($workflow, $element);

            $data['workflowManagement']['workflows'][] = [
                'name' => $workflow->getName(),
                'label' => $workflowConfig->getLabel(),
                'allowedTransitions' => $allowedTransitions,
                'globalActions' => $globalActions
            ];

            $marking = $workflow->getMarking($element);

            if (!sizeof($marking->getPlaces())) {
                continue;
            }

            $permissionsRespected = false;
            foreach ($this->workflowManager->getOrderedPlaceConfigs($workflow, $marking) as $placeConfig) {
                if (!$permissionsRespected && !empty($placeConfig->getPermissions($workflow, $element))) {
                    $data['userPermissions'] = array_merge((array)$data['userPermissions'], $placeConfig->getUserPermissions($workflow, $element));

                    if ($element instanceof ConcreteObject) {
                        $workflowLayoutId = $placeConfig->getObjectLayout($workflow, $element);
                        $hasSelectedCustomLayout = $this->requestStack->getMasterRequest() && $this->requestStack->getMasterRequest()->query->has('layoutId') && $this->requestStack->getMasterRequest()->query->get('layoutId') !== '';

                        if (!is_null($workflowLayoutId) && !$hasSelectedCustomLayout) {

                            //load the new layout into the object container
                            $validLayouts = DataObject\Service::getValidLayouts($element);

                            //check that the layout id is valid before trying to load
                            if (!empty($validLayouts)) {

                                // check user permissions again
                                if ($validLayouts && $validLayouts[$workflowLayoutId]) {
                                    $customLayout = ClassDefinition\CustomLayout::getById($workflowLayoutId);
                                    $customLayoutDefinition = $customLayout->getLayoutDefinitions();
                                    DataObject\Service::enrichLayoutDefinition($customLayoutDefinition, $e->getArgument('object'));
                                    $data['layout'] = $customLayoutDefinition;
                                    $data['currentLayoutId'] = $workflowLayoutId;
                                }
                            }
                        }
                    }
                    $permissionsRespected = true;
                }
            }
        }

        if ($data['workflowManagement']['hasWorkflowManagement']) {
            $data['workflowManagement']['statusInfo'] = $this->placeStatusInfo->getToolbarHtml($element);
        }

        $e->setArgument('data', $data);
    }

    /**
     * @param DataObject\AbstractObject $object
     * @param array $notes
     *
     * @return array
     */
    private function enrichNotes(DataObject\AbstractObject $object, array $notes)
    {
        if (!empty($notes['commentGetterFn'])) {
            $commentGetterFn = $notes['commentGetterFn'];
            $notes['commentPrefill'] = $object->$commentGetterFn();
        } elseif (!empty($notes)) {
            $notes['commentPrefill'] = '';
        }

        return $notes;
    }

    /**
     * @param GenericEvent $e
     *
     * @return AbstractElement
     *
     * @throws \Exception
     */
    private static function extractElementFromEvent(GenericEvent $e)
    {
        $element = null;

        foreach (['object', 'asset', 'document'] as $type) {
            if ($e->hasArgument($type)) {
                $element = $e->getArgument($type);
            }
        }

        if (empty($element)) {
            throw new \Exception('No element found in event');
        }

        return $element;
    }

    public function enable()
    {
        $this->enabled = true;
    }

    public function disable()
    {
        $this->enabled = false;
    }

    /**
     * @return bool
     */
    public function isEnabled()
    {
        return $this->enabled;
    }
}
