<?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\Workflow\EventSubscriber;

use Pimcore\Event\Workflow\GlobalActionEvent;
use Pimcore\Event\WorkflowEvents;
use Pimcore\Model\Element\AbstractElement;
use Pimcore\Model\Element\ValidationException;
use Pimcore\Workflow;
use Pimcore\Workflow\Transition;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Workflow\Event\Event;

class NotesSubscriber implements EventSubscriberInterface
{
    const ADDITIONAL_DATA_NOTES_COMMENT = 'notes';
    const ADDITIONAL_DATA_NOTES_ADDITIONAL_FIELDS = 'additional';

    /**
     * @var TranslatorInterface
     */
    private $translator;

    /**
     * @var bool
     */
    private $enabled = true;

    /**
     * @var array
     */
    private $additionalData = [];

    public function __construct(TranslatorInterface $translator)
    {
        $this->translator = $translator;
    }

    /**
     * @param Event $event
     *
     * @throws ValidationException
     */
    public function onWorkflowEnter(Event $event)
    {
        if (!$this->checkEvent($event)) {
            return;
        }

        /**
         * @var AbstractElement $subject
         * @var Transition $transition
         */
        $subject = $event->getSubject();
        $transition = $event->getTransition();

        $this->handleNotesPreWorkflow($transition, $subject);
    }

    /**
     * @param Event $event
     *
     * @throws ValidationException
     */
    public function onWorkflowCompleted(Event $event)
    {
        if (!$this->checkEvent($event)) {
            return;
        }

        /**
         * @var AbstractElement $subject
         * @var Transition $transition
         */
        $subject = $event->getSubject();
        $transition = $event->getTransition();

        $this->handleNotesPostWorkflow($transition, $subject);
    }

    /**
     * @param GlobalActionEvent $event
     *
     * @throws ValidationException
     */
    public function onPreGlobalAction(GlobalActionEvent $event)
    {
        if (!$this->checkGlobalActionEvent($event)) {
            return;
        }

        $subject = $event->getSubject();
        $globalAction = $event->getGlobalAction();

        $this->handleNotesPreWorkflow($globalAction, $subject);
    }

    /**
     * @param GlobalActionEvent $event
     *
     * @throws ValidationException
     */
    public function onPostGlobalAction(GlobalActionEvent $event)
    {
        if (!$this->checkGlobalActionEvent($event)) {
            return;
        }

        $subject = $event->getSubject();
        $globalAction = $event->getGlobalAction();

        $this->handleNotesPostWorkflow($globalAction, $subject);
    }

    /**
     * @param Workflow\Notes\NotesAwareInterface $notesAware
     * @param AbstractElement $subject
     *
     * @throws ValidationException
     */
    private function handleNotesPreWorkflow(Workflow\Notes\NotesAwareInterface $notesAware, AbstractElement $subject)
    {
        if (($setterFn = $notesAware->getNotesCommentSetterFn()) && ($notes = $this->getNotesComment())) {
            $subject->$setterFn($notes);
        }

        foreach ($notesAware->getNotesAdditionalFields() as $additionalFieldConfig) {
            $data = $this->getAdditionalDataForField($additionalFieldConfig);

            //check required
            if ($additionalFieldConfig['required'] && (empty($data) || !$data)) {
                $label = isset($additionalFieldConfig['title']) && strlen($additionalFieldConfig['title']) > 0
                    ? $additionalFieldConfig['title']
                    : $additionalFieldConfig['name'];

                throw new ValidationException(
                    $this->translator->trans('workflow_notes_requred_field_message', [$label], 'admin')
                );
            }

            //work out whether or not to set the value directly to the object or to add it to the note data
            if (!empty($additionalFieldConfig['setterFn'])) {
                $setterFn = $additionalFieldConfig['setterFn'];
                $subject->$setterFn($this->getAdditionalDataForField($additionalFieldConfig));
            }
        }
    }

    /**
     * @param Workflow\Notes\NotesAwareInterface $notesAware
     * @param AbstractElement $subject
     *
     * @throws ValidationException
     */
    private function handleNotesPostWorkflow(Workflow\Notes\NotesAwareInterface $notesAware, AbstractElement $subject)
    {
        $additionalFieldsData = [];
        foreach ($notesAware->getNotesAdditionalFields() as $additionalFieldConfig) {
            /**
             * Additional Field example
             * [
            'name' => 'dateLastContacted',
            'fieldType' => 'date',
            'label' => 'Date of Conversation',
            'required' => true,
            'setterFn' => ''
            ]
             */

            //work out whether or not to set the value directly to the object or to add it to the note data
            if (empty($additionalFieldConfig['setterFn'])) {
                $additionalFieldsData[] = Workflow\Service::createNoteData($additionalFieldConfig, $this->getAdditionalDataForField($additionalFieldConfig));
            }
        }

        Workflow\Service::createActionNote(
            $subject,
            $notesAware->getNotesType(),
            $notesAware->getNotesTitle(),
            $this->getNotesComment(),
            $additionalFieldsData
        );
    }

    /**
     * check's if the event subscriber should be executed
     *
     * @param Event $event
     *
     * @return bool
     */
    private function checkEvent(Event $event): bool
    {
        return $this->isEnabled()
               && $event->getTransition() instanceof Transition
               && $event->getSubject() instanceof AbstractElement;
    }

    private function checkGlobalActionEvent(GlobalActionEvent $event): bool
    {
        return $this->isEnabled()
               && $event->getSubject() instanceof AbstractElement;
    }

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

    /**
     * @param bool $enabled
     */
    public function setEnabled(bool $enabled): void
    {
        $this->enabled = $enabled;
    }

    /**
     * @return array|null
     */
    public function getAdditionalData(): ?array
    {
        return $this->additionalData;
    }

    /**
     * @param array|null $additionalData
     */
    public function setAdditionalData(array $additionalData = []): void
    {
        $this->additionalData = $additionalData;
    }

    private function getAdditionalDataForField(array $fieldConfig)
    {
        $additional = $this->getAdditionalFields();

        $data = $additional[$fieldConfig['name']];

        if ($fieldConfig['fieldType'] === 'checkbox') {
            return $data === 'true';
        }

        return $data;
    }

    private function getNotesComment(): string
    {
        return $this->additionalData[self::ADDITIONAL_DATA_NOTES_COMMENT] ?? '';
    }

    private function getAdditionalFields(): array
    {
        return $this->additionalData[self::ADDITIONAL_DATA_NOTES_ADDITIONAL_FIELDS] ?? [];
    }

    public static function getSubscribedEvents()
    {
        return [
            'workflow.completed' => ['onWorkflowCompleted', 1],
            'workflow.enter' => 'onWorkflowEnter',
            WorkflowEvents::PRE_GLOBAL_ACTION => 'onPreGlobalAction',
            WorkflowEvents::POST_GLOBAL_ACTION => 'onPostGlobalAction'
        ];
    }
}
