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

use Doctrine\ORM\EntityManager;
use Shopware\Models\Order\Detail as OrderDetail;
use Shopware\CustomModels\ViisonSetArticles\Repository as SetArticleRepository;
use Shopware_Models_Document_Order;

class DocumentManipulationService
{
    /**
     * @var EntityManager
     */
    private $entityManager;

    /**
     * @var PluginConfigService
     */
    private $pluginConfigService;

    /**
     * @param EntityManager $entityManager
     * @param PluginConfigService $pluginConfigService
     */
    public function __construct($entityManager, PluginConfigService $pluginConfigService)
    {
        $this->entityManager = $entityManager;
        $this->pluginConfigService = $pluginConfigService;
    }

    /**
     * Adds 'isSetArticle' and 'isSubArticle' attributes to order position variables for easier processing later and in
     * the document template
     *
     * @param array $pages
     * @return array
     */
    public function addSetArticleAttributes($pages)
    {
        foreach ($pages as &$page) {
            foreach ($page as &$position) {
                $position['isSetArticle'] = ($position['attributes']['viison_setarticle_orderid'] !== null);
                $position['isSubArticle'] = $position['isSetArticle'] &&
                    ($position['attributes']['viison_setarticle_orderid'] !== $position['id']);
            }
        }

        return $pages;
    }

    /**
     * Substitutes complete sets (all sub articles from one set article) with their respective set article.
     *
     * If a customer decides not to pick set articles but only their sub articles, a delivery note may be "empty" when
     * sub articles are excluded and the set article was never picked. If a set if completely picked (all sub articles
     * are picked), but the set article missing, substitute these sub articles with their respective set article
     * position. Sub articles are removed from the document. This is only relevant for delivery notes.
     *
     * @param int $orderId
     * @param array $pages
     * @param int $pageBreak
     * @return array
     */
    public function substituteCompleteSubArticlesWithSetArticle($orderId, $pages, $pageBreak)
    {
        $positions = [];
        foreach ($pages as $page) {
            foreach ($page as $position) {
                $positions[$position['id']] = $position;
            }
        }

        /**
         * @var array $setArticlesInPositions
         *
         * Contains all set article positions grouped by the main set article of the positions. Also includes incomplete
         * sets or sub articles without a respective set article (set article may be missing from this document).
         */
        $setArticlesInPositions = [];
        foreach ($positions as $position) {
            if ($position['isSubArticle']) {
                $setArticleOrderDetailId = $position['attributes']['viison_setarticle_orderid'];
                if (!array_key_exists($setArticleOrderDetailId, $setArticlesInPositions)) {
                    $setArticlesInPositions[$setArticleOrderDetailId] = [];
                }
                $setArticlesInPositions[$setArticleOrderDetailId][] = $position;
            }
        }

        // Find complete sets in the positions where the set article is missing, so that these sub articles may be
        // removed
        /** @var SetArticleRepository $setArticleRepository */
        $setArticleRepository = $this->entityManager->getRepository('Shopware\\CustomModels\\ViisonSetArticles\\SetArticle');
        $setArticleCompositions = $setArticleRepository->getSetArticleCompositionByOrderDetailId(
            array_keys($setArticlesInPositions)
        );

        $setArticleOrderDetails = $this->entityManager->getRepository('Shopware\\Models\\Order\\Detail')->findBy([
            'id' => array_keys($setArticlesInPositions),
        ]);
        $setArticleOrderDetailsById = [];
        foreach ($setArticleOrderDetails as $setArticleOrderDetail) {
            $setArticleOrderDetailsById[$setArticleOrderDetail->getId()] = $setArticleOrderDetail;
        }

        $toBeRemovedOrderDetailIds = [];
        $toBeAddedOrderDetailIds = [];
        foreach ($setArticlesInPositions as $setArticleOrderDetailId => $subArticlesInPositions) {
            if (!array_key_exists($setArticleOrderDetailId, $setArticleCompositions)) {
                // No set article combination was found. This should not happen, but continue without manipulation.
                continue;
            }

            if ($this->isCompleteSet($setArticleCompositions[$setArticleOrderDetailId], $subArticlesInPositions, $setArticleOrderDetailsById[$setArticleOrderDetailId])) {
                // Mark all sub articles to be removed later.
                foreach ($subArticlesInPositions as $subArticleInPositions) {
                    $toBeRemovedOrderDetailIds[] = $subArticleInPositions['id'];
                }

                // Check if the set article of this complete set is present. If not, use the order detail from the db to
                // add a new position to the document.
                if (!array_key_exists($setArticleOrderDetailId, $positions)
                    && array_key_exists($setArticleOrderDetailId, $setArticleOrderDetailsById)
                ) {
                    $toBeAddedOrderDetailIds[] = $setArticleOrderDetailId;
                }
            }
        }

        // Add any new set article positions to the document to the front of the document positions.
        $remainingPositions = [];
        if (count($toBeAddedOrderDetailIds) > 0) {
            // We need to instantiate a new document order because the current order is already filtered
            $documentOrder = new Shopware_Models_Document_Order($orderId);
            $documentOrder->getPositions();
            $documentOrderPositions = $documentOrder->__toArray()['_positions'];
            foreach ($toBeAddedOrderDetailIds as $toBeAddedOrderDetailId) {
                foreach ($documentOrderPositions as $documentOrderPosition) {
                    if ((int) $documentOrderPosition['id'] === $toBeAddedOrderDetailId) {
                        $remainingPositions[] = $documentOrderPosition;

                        // Continue to next $toBeAddedOrderDetailId
                        break;
                    }
                }
            }
        }

        // Remove any complete set sub articles from the document
        foreach ($positions as $position) {
            if (!in_array($position['id'], $toBeRemovedOrderDetailIds)) {
                $remainingPositions[] = $position;
            }
        }

        // Chunk positions into pages and keep keys (position indicator on the document)
        $pages = array_chunk($remainingPositions, $pageBreak, true);

        return $pages;
    }

    /**
     * Removes sub articles of incomplete sets from the document if the plugin configuration is set so.
     *
     * This is done implicitly by removing the 'isSubArticle' attribute if necessary so that sub articles are not
     * removed and template-handled. Otherwise the attribute is kept and sub articles are removed from the document
     * later on. See removeSubArticles().
     *
     * @param array $pages
     * @return array
     */
    public function removeIncompleteSubArticlesIfNecessary($pages)
    {
        $displaySubArticlesConfiguration = $this->pluginConfigService->getDisplaySubArticlesOnDocuments();
        if ($displaySubArticlesConfiguration === PluginConfigService::NEVER_SHOW_SUB_ARTICLES_ON_DOCUMENTS) {
            // 'Remove' the sub articles by keeping the 'isSubArticle' attribute. See removeSubArticles().
            return $pages;
        }

        // Add this point the display sub article configuration has to be
        // PluginConfigService::ONLY_SHOW_SUB_ARTICLES_ON_PARTIAL_DELIVERY_NOTES. And since complete sets have already
        // been substituted (see substituteCompleteSubArticlesWithSetArticle()) the only sub articles that remain are
        // incomplete sets.

        // 'Keep' all remaining sub articles by removing the 'isSubArticle' attribute.
        foreach ($pages as &$page) {
            foreach ($page as &$position) {
                $position['isSubArticle'] = false;
            }
        }

        return $pages;
    }

    /**
     * Remove SubArticles from the document positions entirely.
     *
     * @param array $pages
     * @param int $pageBreak
     * @return array
     */
    public function removeSubArticles($pages, $pageBreak = 10)
    {
        $remainingPositions = [];
        foreach ($pages as $page) {
            foreach ($page as $position) {
                if (!$position['isSubArticle']) {
                    $remainingPositions[] = $position;
                }
            }
        }

        // Chunk positions into pages and keep keys (position indicator on the document)
        $pages = array_chunk($remainingPositions, $pageBreak, true);

        return $pages;
    }

    /**
     * @param array $setArticleComposition
     * @param array $subArticlesInPositions
     * @param OrderDetail $setArticle
     * @return bool
     */
    private function isCompleteSet($setArticleComposition, $subArticlesInPositions, $setArticle)
    {
        if (count($setArticleComposition) !== count($subArticlesInPositions)) {
            return false;
        }

        $checkedSubArticles = 0;
        foreach ($setArticleComposition as $subArticleInComposition) {
            foreach ($subArticlesInPositions as $subArticleInPositions) {
                if ($subArticleInPositions['articleordernumber'] === $subArticleInComposition['ordernumber']) {
                    if ((int) $subArticleInPositions['quantity'] !== ($subArticleInComposition['quantity'] * $setArticle->getQuantity())) {
                        // Sub article composition requires a different quantity of what is on the current document
                        return false;
                    }
                    $checkedSubArticles++;
                    // Continue to next $subArticle (outer loop)
                    break;
                }
            }
        }

        return !($checkedSubArticles < count($setArticleComposition));
    }
}
