<?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\Classes\Installation\Document;

use Doctrine\Common\Collections\ArrayCollection;
use Shopware\Components\Model\ModelManager;
use Shopware\Models\Document\Document as DocumentType;
use Shopware\Models\Document\Element as DocumentElement;

class DocumentInstallationHelper
{
    /**
     * @var ModelManager
     */
    private $entityManager;

    /**
     * @param ModelManager $entityManager
     */
    public function __construct(ModelManager $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    /**
     * Create or update a document type identified by key.
     *
     * If SW version older than 5.5 is used, the name is used for identification.
     *
     * @param string $key the key (unique technical name for identification)
     * @param string $name name of the document type
     * @param string $template the template to use
     * @param string $numberRangeName the name of the numbers generator to use
     * @param int $left left margin to set
     * @param int $right right margin to set
     * @param int $top top margin to set
     * @param int $bottom bottom margin to set
     * @param int $pageBreak the number of items after which to force a page break
     * @return DocumentType The document type that either already exists or was created.
     */
    public function ensureDocumentType(
        $key,
        $name,
        $template,
        $numberRangeName,
        $left = 0,
        $right = 0,
        $top = 0,
        $bottom = 0,
        $pageBreak = 0
    ) {
        /** @var DocumentType|null $documentType */
        $documentType = null;

        $documentTypeRepo = $this->entityManager->getRepository('Shopware\\Models\\Document\\Document');

        if (self::useKeyAsIdentifier()) {
            $documentType = $documentTypeRepo->findOneBy([
                'key' => $key,
            ]);
        } else {
            $documentType = $documentTypeRepo->findOneBy([
                'name' => $name,
            ]);
        }

        if ($documentType) {
            return $documentType;
        }

        $documentType = new DocumentType();
        if (self::useKeyAsIdentifier()) {
            $documentType->setKey($key);
        }
        $documentType->setName($name);
        $documentType->setTemplate($template);
        $documentType->setNumbers($numberRangeName);
        $documentType->setLeft($left);
        $documentType->setRight($right);
        $documentType->setTop($top);
        $documentType->setBottom($bottom);
        $documentType->setPageBreak($pageBreak);
        $this->entityManager->persist($documentType);
        $this->entityManager->flush($documentType);

        return $documentType;
    }

    /**
     * Creates a new DocumentElement, if none with the given $elementName already exists for the $documentType.
     *
     * @param DocumentType $documentType The DocumentType to add the element for
     * @param string $elementName name of the element to add or update
     * @param string $value the default value to set for the DocumentElement
     * @param string $style the style to set for the DocumentElement
     * @return DocumentElement The existing or created Element
     */
    public function ensureDocumentElement(DocumentType $documentType, $elementName, $value = '', $style = '')
    {
        if (!$documentType->getElements()) {
            $documentType->setElements(new ArrayCollection());
        }

        $element = $documentType->getElements()->filter(function (DocumentElement $element) use ($elementName) {
            return $element->getName() === $elementName;
        })->first();

        if ($element) {
            return $element;
        }

        $element = new DocumentElement();
        $element->setName($elementName);
        $element->setValue($value);
        $element->setStyle($style);
        $element->setDocument($documentType);
        $documentType->getElements()->add($element);
        $this->entityManager->persist($element);
        $this->entityManager->flush($element);

        return $element;
    }

    /**
     * Rename a DocumentElement in an idempotent way.
     *
     * @param DocumentType $documentType the DocumentType to update a DocumentElement for
     * @param string $oldName the current name of the DocumentElement to rename
     * @param string $newName the desired new name of the DocumentElement
     * @return null|DocumentElement The new DocumentElement
     */
    public function ensureDocumentElementRenamed(DocumentType $documentType, $oldName, $newName)
    {
        // Fix SW's initializing of the "elements" property with null in DocumentType
        if (!$documentType->getElements()) {
            $documentType->setElements(new ArrayCollection());
        }

        /** @var DocumentElement|null $oldElement */
        $oldElement = $documentType->getElements()->filter(function (DocumentElement $element) use ($oldName) {
            return $element->getName() === $oldName;
        })->first();
        /** @var DocumentElement|null $newElement */
        $newElement = $documentType->getElements()->filter(function (DocumentElement $element) use ($newName) {
            return $element->getName() === $newName;
        })->first();

        if ($oldElement) {
            if ($newElement) {
                $documentType->getElements()->removeElement($oldElement);
                $this->entityManager->remove($oldElement);
                $this->entityManager->flush($oldElement);
            } else {
                $oldElement->setName($newName);
                $this->entityManager->flush($oldElement);
                $newElement = $oldElement;
            }
        }

        if (!$newElement) {
            throw new \RuntimeException(sprintf(
                'Document element with name "%s" not found for document type "%s" (id=%d, key=%s)',
                $oldName,
                $documentType->getName(),
                $documentType->getId(),
                self::useKeyAsIdentifier() ? $documentType->getKey() : 'n/a'
            ));
        }

        return $newElement;
    }

    /**
     * Whether to use the key or the name of the DocumentType for identification.
     *
     * The property key of a DocumentType was introduces with SW 5.5
     *
     * @return bool
     */
    private static function useKeyAsIdentifier()
    {
        return method_exists('Shopware\\Models\\Document\\Document', 'setKey');
    }
}
