<?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.

use Doctrine\ORM\Tools\Pagination\Paginator;
use Doctrine\ORM\Query;
use Shopware\Plugins\ViisonCommon\Classes\Util\Util as ViisonCommonUtil;
use Shopware\Plugins\ViisonSetArticles\Util;

class Shopware_Controllers_Backend_ViisonSetArticlesArticle extends Shopware_Controllers_Backend_ExtJs
{
    private $defaultPriceGroup = 'EK';

    /**
     * Return a list of Articles.
     * The list is filtered by a given filter-array (set article IDs that are excluded) and filter-word
     * (filter edit field from the backend)
     */
    public function getArticleListAction()
    {
        $start = $this->Request()->getParam('start', 0);
        $limit = $this->Request()->getParam('limit', 30);
        $filterString = $this->Request()->getParam('filterword', '');
        $filterIds = json_decode($this->Request()->getParam('filterarr'));

        $builder = $this->getArticleDetailsQueryBuilder();

        // Apply filter to name and ordernumber
        if (!empty($filterString)) {
            $builder
                ->andWhere('a.name LIKE :filterString OR ad.number LIKE :filterString')
                ->setParameter('filterString', '%' . $filterString . '%');
        }
        // Apply filter to ArticleDetailId
        if ($filterIds[0] != '') {
            $builder
                ->andWhere('ad.id NOT IN (:filterIds)')
                ->setParameter('filterIds', $filterIds);
        }

        $builder->setFirstResult($start);
        $builder->setMaxResults($limit);
        $query = $builder->getQuery();
        $query->setHydrationMode(Query::HYDRATE_ARRAY);
        $paginator = new Paginator($query);
        $data = $paginator->getIterator()->getArrayCopy();

        $data = $this->fixResultFields($data);

        $this->View()->assign([
            'success' => true,
            'data' => $data,
            'total' => $paginator->count(),
        ]);
    }

    /**
     * Returns a querybuilder for some basic article detail information.
     *
     * @return \Doctrine\ORM\QueryBuilder
     */
    private function getArticleDetailsQueryBuilder()
    {
        $builder = $this->get('models')->createQueryBuilder();
        $builder
            ->select(
                'ad',
                'ad.id as id',
                'a.name as name',
                'ad.id as articleDetailId',
                'a.id as articleId',
                'ad.number as ordernumber',
                'ad.additionalText as additional',
                't.tax as tax',
                'p.price',
                'attr.viisonSetarticleActive',
                'ad.inStock as instock',
                'ad.active as detailActive',
                'a.active as mainActive',
                'a.configuratorSetId',
                'ad.shippingTime as deliverytime',
                'ad.maxPurchase as maxpurchase',
                'ad.weight'
            )
            ->from('Shopware\\Models\\Article\\Detail', 'ad')
            ->leftJoin('ad.prices', 'p')
            ->leftJoin('ad.article', 'a')
            ->leftJoin('ad.attribute', 'attr')
            ->leftJoin('a.tax', 't')
            ->andWhere('attr.viisonSetarticleActive = 0 OR attr.viisonSetarticleActive IS NULL')
            ->andWhere('p.from = 1')
            ->andWhere('p.customerGroupKey = (:priceGroup)')
            ->setParameter('priceGroup', $this->defaultPriceGroup);

        // Add lastStock selection
        $builder = Util::addLastStockSelectionToQueryBuilder($builder);

        return $builder;
    }

    /**
     * Return some basic information of all ArticleDetails to given Article (by articleID).
     * This action is used to fill the variant-selector-dropdown with all necessary information.
     */
    public function getArticleDetailListAction()
    {
        $builder = $this->get('models')->createQueryBuilder();
        $filterID = $this->Request()->getParam('articleId');

        $builder
            ->select(
                'ad',
                'ad.id as articleDetailId',
                'ad.articleId',
                'adp.price',
                't.tax as tax',
                'ad.number as ordernumber',
                'a.name as name',
                'ad.additionalText as additional'
            )
            ->from('Shopware\\Models\\Article\\Detail', 'ad')
            ->leftJoin('ad.article', 'a')
            ->leftJoin('ad.prices', 'adp')
            ->leftJoin('a.tax', 't')
            ->andWhere('adp.from = 1')// only fetch one "single" price, not multiple quantity-scaled prices
            ->andWhere('adp.customerGroupKey = (:pricegroup)')// Only use price for default customer group.
            ->setParameter('pricegroup', $this->defaultPriceGroup)
            ->andWhere('ad.articleId = (:filterID)')
            ->setParameter('filterID', $filterID);

        $query = $builder->getQuery();
        $query->setHydrationMode(Query::HYDRATE_ARRAY);
        $paginator = new Paginator($query);
        $data = $paginator->getIterator()->getArrayCopy();
        $data = $this->fixVariantAdditionalText($data);
        $totalResult = count($data);

        // Send response
        $this->View()->success = true;
        $this->View()->data = $data;
        $this->View()->total = $totalResult;
    }

    /**
     * Returns a refreshed (that means current information) list of article detail information for the given list
     * of article detail ids.
     */
    public function getRefreshedSubArticleDetailListAction()
    {
        $builder = $this->getArticleDetailsQueryBuilder();
        $subArticleDetailIds = json_decode($this->Request()->getParam('jsonSubArticleDetailIds'));
        $builder
            ->andWhere('ad.id IN (:filterIds)')
            ->setParameter('filterIds', $subArticleDetailIds);

        $query = $builder->getQuery();
        $query->setHydrationMode(Query::HYDRATE_ARRAY);
        $paginator = new Paginator($query);
        $data = $paginator->getIterator()->getArrayCopy();

        $data = $this->fixResultFields($data);

        $this->View()->assign([
            'success' => true,
            'data' => $data,
            'total' => count($data),
        ]);
    }

    /**
     * Return all sub articles for one set article.
     * Remark: we cannot use the getArticleListAction since we need additional set article information
     */
    public function getSetArticleListAction()
    {
        $filterSetId = $this->Request()->getParam('setid');
        $builder = $this->get('models')->createQueryBuilder();

        $builder
            ->select(
                'sets',
                'ad.articleId as articleId',
                'ad.number as ordernumber',
                'ad.additionalText as additional',
                'ad.inStock as instock',
                'ad.active as detailActive',
                'ad.maxPurchase as maxpurchase',
                'ad.shippingTime as deliverytime',
                'ad.weight',
                'a.name',
                'a.active as mainActive',
                'a.configuratorSetId',
                'p.price',
                't.tax',
                'sets.id',
                'sets.setId',
                'sets.articleDetailId',
                'sets.quantity'
            )
            ->from('Shopware\\CustomModels\\ViisonSetArticles\\SetArticle', 'sets')
            ->leftJoin('sets.articleDetail', 'ad')
            ->leftJoin('ad.article', 'a')
            ->leftJoin('ad.prices', 'p')
            ->leftJoin('a.tax', 't')
            ->andWhere('p.from = 1')
            ->andWhere('p.customerGroupKey = (:pricegroup)')
            ->setParameter('pricegroup', $this->defaultPriceGroup)
            ->andWhere('sets.setId = (:filterID)')
            ->setParameter('filterID', $filterSetId);

        // Add lastStock selection
        $builder = Util::addLastStockSelectionToQueryBuilder($builder);

        $query = $builder->getQuery();
        $query->setHydrationMode(Query::HYDRATE_ARRAY);
        $paginator = new Paginator($query);
        $data = $paginator->getIterator()->getArrayCopy();

        $data = $this->fixResultFields($data);

        $this->View()->assign([
            'success' => true,
            'data' => $data,
            'total' => count($data),
        ]);
    }

    /**
     * Check if given article (articleID) is a sub article. Return ordernumbers of all found set articles.
     */
    public function checkIfSubArticleAction()
    {
        $articleId = $this->Request()->getParam('articleid');

        $builder = $this->get('models')->createQueryBuilder();
        $builder
            ->select(
                'ad',
                'ad.number'
            )
            ->from('Shopware\\Models\\Article\\Detail', 'ad')
            ->join('Shopware\\CustomModels\\ViisonSetArticles\\SetArticle', 'sets', 'WITH', 'ad.id = sets.setId')
            ->join('Shopware\\Models\\Article\\Detail', 'ad2', 'WITH', 'sets.articleDetailId = ad2.id')
            ->andWhere('ad2.articleId = (:subArticleId)')
            ->setParameter('subArticleId', $articleId);

        $query = $builder->getQuery();
        $query->setHydrationMode(Query::HYDRATE_ARRAY);
        $paginator = new Paginator($query);
        $result = $paginator->getIterator()->getArrayCopy();
        $ordernumbers = array_column($result, 'number');

        $this->View()->assign([
            'success' => (count($ordernumbers) > 0),
            'total' => count($ordernumbers),
            'data' => $ordernumbers,
        ]);
    }

    /**
     * Updates multiple set articles. Update information is received from three arrays, with corresponding SetId,
     * ArticleDetailId and Quantity. Each set article is deleted from the database, then saved anew with given
     * composition.
     */
    public function updateSetArticleStackAction()
    {
        $newSetIds = json_decode($this->Request()->getParam('setIdArray'));
        $newArticleDetailIds = json_decode($this->Request()->getParam('articleDetailIdArray'));
        $newQuantities = json_decode($this->Request()->getParam('quantityArray'));

        /**
         * First, remove old set article composition from db
         * Remark: array_unique does not change the array keys, therefore use foreach to iterate throug
         * (keys 0, 1, 2, 3, 4.. -> keys 2, 5,..)
         */
        $deleteSetIds = array_unique($newSetIds);
        foreach ($deleteSetIds as $seleteId) {
            $this->resetOneSetarticle($seleteId);
        }

        // Finally, save new composition
        for ($n = 0; $n < count($newSetIds); $n++) {
            $this->saveNewSetArticleCombination(
                $newSetIds[$n],
                $newArticleDetailIds[$n],
                $newQuantities[$n]
            );
        }

        // Send response
        $this->View()->success = true;
    }

    /**
     * Delete multiple set articles from database.
     * This is needed during the save article step, if set article stores were emptied
     */
    public function resetSetArticleStackAction()
    {
        $deleteSetIds = json_decode($this->Request()->getParam('setIdArray'));
        foreach ($deleteSetIds as $deleteId) {
            $this->resetOneSetarticle($deleteId);
        }

        // Send response
        $this->View()->success = true;
        $this->View()->data = ['setIds' => $deleteSetIds];
    }

    /**
     * Deletes one (full) set article from database. This method fetches all set article (combination) where the set
     * article id matches the given setId and removes these entities.
     *
     * @param $setId
     */
    public function resetOneSetarticle($setId)
    {
        if ($setId === null) {
            return;
        }

        $setArticles = $this->get('models')->getRepository('Shopware\\CustomModels\\ViisonSetArticles\\SetArticle')
            ->findBy([
                'setId' => $setId,
            ]);
        foreach ($setArticles as $setArticle) {
            $this->get('models')->remove($setArticle);
        }
        $this->get('models')->flush();
    }

    /**
     * Saves a new set article entry via Shopware\CustomModels\ViisonSetArticles\SetArticle model. An entry with given
     * ids ($setid, $articledetailid) must not exist before.
     *
     * @param int $setId SetArticleId
     * @param int $articleDetailId ArticleDetailId of sub article
     * @param int $quantity Quantity of sub article
     */
    public function saveNewSetArticleCombination($setId, $articleDetailId, $quantity)
    {
        $builder = $this->get('models');
        $setArticle = new \Shopware\CustomModels\ViisonSetArticles\SetArticle();
        $setArticle->setSetId($setId);
        $setArticle->setArticleDetailId($articleDetailId);
        $setArticle->setQuantity($quantity);
        $builder->persist($setArticle);
        $builder->flush($setArticle);

        // Send response
        $this->View()->success = true;
    }

    /**
     * Fixes some fields in the result array: maxpurchase, additional (variant) text, active
     *
     * @param array $data
     * @return array
     */
    private function fixResultFields($data)
    {
        $data = $this->fixMaxPurchase($data);
        $data = $this->fixVariantAdditionalText($data);
        $data = $this->fixActive($data);

        return $data;
    }

    /**
     * Helper function to fetch Article maxpurchase information. Iterates a given array of article data and adds
     * 'maxpurchase'. Use default value if no information is provided.
     *
     * @param $data
     * @return mixed
     */
    private function fixMaxPurchase($data)
    {
        $shopwareDefaultMaxPurchase = Util::getShopwareMaxPurchase();

        foreach ($data as &$article) {
            if (!$article['maxpurchase']) {
                $article['maxpurchase'] = $shopwareDefaultMaxPurchase;
            }
        }

        return $data;
    }

    /**
     * Helper function to fetch Article additional information. Iterates a given array of article data and adds
     * 'additional' name
     *
     * @param array $data
     * @return array
     */
    private function fixVariantAdditionalText($data)
    {
        // Fetch additional texts batchwise for all articles at once
        $articleDetailIds = array_map(function ($article) {
            return $article['articleDetailId'];
        }, $data);
        $articleDetailAdditionalTexts = ViisonCommonUtil::getVariantAdditionalTexts($articleDetailIds);

        // Add additional texts to article data
        foreach ($data as &$article) {
            if (isset($articleDetailAdditionalTexts[$article['articleDetailId']])) {
                $article['additional'] = $articleDetailAdditionalTexts[$article['articleDetailId']];
            } else {
                $article['additional'] = '';
            }
        }

        return $data;
    }

    /**
     * Fixed the 'active' flag of each article depending on if it is active, a main article or variant.
     *
     * @param array $data
     * @return array $data
     */
    private function fixActive($data)
    {
        foreach ($data as &$article) {
            $article['active'] = Util::isArticleActive(
                $article['configuratorSetId'],
                $article['mainActive'],
                $article['detailActive']
            );
        }

        return $data;
    }
}
