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

use \Doctrine\ORM\QueryBuilder;
use Shopware\Models\Article\Article;
use Shopware\Models\Shop\Shop;
use Shopware\Models\Article\Detail;
use Shopware\Plugins\ViisonCommon\Classes\TranslationServiceFactory;
use Shopware\Plugins\ViisonCommon\Classes\Util\Util as ViisonCommonUtil;

/**
 * Contains static utlitiy methods and values which are used by several classes of the ViisonSetArticles plugin.
 */
final class Util
{

    /**
     * Returns a fully translated article name with variant additional text.
     * If no shop (and therefore no locale) is given or no translation was found,
     * use default settings.
     *
     * @param Detail $articleDetail
     * @param Shop|null $shop
     * @return string
     */
    public static function getTranslatedFullArticleName($articleDetail, $shop = null)
    {
        $mainArticle = $articleDetail->getArticle();
        $translator = TranslationServiceFactory::createTranslationService();

        // Fetch article name
        if (!$shop) {
            // Use untranslated article name
            $result = $mainArticle->getName();
        } else {
            $translation = $translator->read(
                $shop->getId(),
                'article',
                $mainArticle->getId()
            );
            // If no translation was found, still use the article name from shopware main article
            $result = (!empty($translation['name'])) ? $translation['name'] : $mainArticle->getName();
        }

        // Fetch variant additional text
        $additionalText = ViisonCommonUtil::getVariantAdditionalText(
            $articleDetail->getArticle()->getId(),
            $articleDetail->getId(),
            $articleDetail->getNumber(),
            $shop
        );
        $result .= mb_strlen($additionalText) > 0 ? ' ' . $additionalText : '';

        return $result;
    }

    /**
     * Checks if an Article (given by Article/Detail id) is a set articles
     *
     * @param int $articleDetailId
     * @return bool
     */
    public static function isSetArticleByDetailId($articleDetailId)
    {
        if (!$articleDetailId) {
            return false;
        }

        $articleDetail = Shopware()->Container()->get('models')->find(
            'Shopware\\Models\\Article\\Detail',
            $articleDetailId
        );
        if (empty($articleDetail)) {
            return false;
        } else {
            return self::isSetArticleByArticle($articleDetail->getArticle());
        }
    }

    /**
     * Checks if an Article (given by a Detail orderNumber) is a set articles.
     *
     * @param string $orderNumber
     * @return bool
     */
    public static function isSetArticleByOrdernumber($orderNumber)
    {
        $articleDetail = Shopware()->Container()->get('models')->getRepository('Shopware\\Models\\Article\\Detail')->findOneBy(
            [
                'number' => $orderNumber,
            ]
        );
        if (!$articleDetail) {
            return false;
        }

        return self::isSetArticleByArticle($articleDetail->getArticle());
    }

    /**
     * Checks if an Article (given by Article/Article id) is a set articles
     *
     * @param int $articleId
     * @return bool
     */
    public static function isSetArticleByArticleId($articleId)
    {
        if (!$articleId) {
            return false;
        }

        $article = Shopware()->Container()->get('models')->find('Shopware\\Models\\Article\\Article', $articleId);
        if (empty($article)) {
            return false;
        } else {
            return self::isSetArticleByArticle($article);
        }
    }

    /**
     * Returns the name prefix for sub article of a given set articles.
     *
     * @param string $setArticleOrderNumber Article ordernumber of set article
     * @param Shop|null $shop
     * @return string
     */
    public static function getSubArticleNamePrefix($setArticleOrderNumber, $shop = null)
    {
        if (!$shop) {
            $shop = self::fetchShop();
        }
        $prefix = ViisonCommonUtil::getSnippetFromLocale(
            'backend/viison_set_articles/main',
            'subarticlePrefix',
            $shop
        );

        return $prefix . ' ' . $setArticleOrderNumber;
    }

    /**
     * Fetches the Shop of the current context.
     *
     * @return Shop
     */
    public static function fetchShop()
    {
        $shopContext = Shopware()->Container()->get('shopware_storefront.context_service')->getShopContext()->getShop();

        return Shopware()->Container()->get('models')->find('Shopware\\Models\\Shop\\Shop', $shopContext->getId());
    }

    /**
     * Checks if an Article (given by Article/Article) is a set articles
     *
     * The flag viisonSetArticleActive defines whether an article is a set or not. Therefore
     * it is defined on the article and not on the variant level. To retrieve the value of the
     * flag we need to access it via the article's main detail link. This indirection is necessary,
     * since the shortcut getArticle()->getAttribute() is no longer available in Shopware >= 5.2
     * (the articleID attribute of s_articles_attributes is no longer used).
     *
     * @param Article $article
     * @return bool whether or not given Article is a set articles
     */
    public static function isSetArticleByArticle($article)
    {
        // Safeguard if information is missing
        if (!$article || !$article->getMainDetail() || !$article->getMainDetail()->getAttribute()) {
            return false;
        }

        return $article->getMainDetail()->getAttribute()->getViisonSetArticleActive();
    }

    /**
     * Computes maximum possible purchase units (quantity) of given sub article
     *
     * @param array $subArticle
     * @return int maxpurchase (rounded down)
     */
    public static function getSubMaxPurchase($subArticle, $shopwareDefaultMaxPurchase = null)
    {
        if ($shopwareDefaultMaxPurchase === null) {
            $shopwareDefaultMaxPurchase = self::getShopwareMaxPurchase();
        }

        // Remark: Divide maxpurchase by number of sub articles needed right now (as in instock calculation)
        $articleMaxPurchase = null;
        if ($subArticle['maxpurchase']) {
            $articleMaxPurchase = floor($subArticle['maxpurchase'] / ($subArticle['quantityInSet']));
        } else {
            $articleMaxPurchase = floor($shopwareDefaultMaxPurchase / ($subArticle['quantityInSet']));
        }

        // Remark: if laststock is set, the article instock also caps the maxpurchase
        if ($subArticle['laststock']) {
            $articleMaxPurchase = min(
                $articleMaxPurchase,
                max(0, self::getSubArticleInstock($subArticle))
            );
        }

        return (int) $articleMaxPurchase;
    }

    /**
     * Fetches the current maxpurchase config value of the shopware config.
     *
     * @return int
     */
    public static function getShopwareMaxPurchase()
    {
        $maxPurchaseConfig = Shopware()->Container()->get('models')->getRepository(
            'Shopware\\Models\\Config\\Element'
        )->findOneBy(
            [
                'name' => 'maxpurchase',
            ]
        );
        $values = $maxPurchaseConfig->getValues();
        if (!$values || $values->isEmpty()) {
            return $maxPurchaseConfig->getValue();
        } else {
            return $values[0]->getValue();
        }
    }

    /**
     * Computes maximum instock of given sub article and SetArticleQuantity
     *    instock > 0: direct delivery
     *    instock <= 0: longer delivery time
     *    instock <= 0 + laststock: not available
     *
     * @param array $subArticle
     * @return int maximum instock (rounded down)
     */
    public static function getSubArticleInstock($subArticle)
    {
        return (int) floor($subArticle['instock'] / ($subArticle['quantityInSet']));
    }

    /**
     * Computes activeness of given sub article. This depends on whether or not the sub article is a variant oder
     * non-variant-article. (as in Shopware standard)
     *
     * @param array $subArticle
     * @return bool active flag
     */
    public static function getSubArticleActive($subArticle)
    {
        return self::isArticleActive(
            $subArticle['configuratorId'],
            $subArticle['mainActive'],
            $subArticle['variantActive']
        );
    }

    /**
     * Computes the activeness of an Article given by its active flags and configurator id.
     *
     * @param int|null $configuratorId
     * @param bool $mainActive
     * @param bool $detailActive
     * @return bool
     */
    public static function isArticleActive($configuratorId, $mainActive, $detailActive)
    {
        return (
            ($configuratorId && $mainActive && $detailActive)
            ||
            (!$configuratorId && $mainActive)
        );
    }

    /**
     * Computes availability of given sub article. This depends on sub articles active, laststock and instock values.
     * (as in Shopware standard)
     *
     * @param array $subArticle
     * @param int $setQuantity Number of set articles in Basket (default: 1)
     * @return bool available flag
     */
    public static function getSubArticleAvailability($subArticle, $setQuantity = 1)
    {
        return (
            self::getSubArticleActive($subArticle)
            &&
            ((self::getSubArticleInstock($subArticle) * $setQuantity > 0) || !$subArticle['laststock'])
        );
    }

    /**
     * Takes shipping time of given sub article. If the article is a variant article and a variant has shippingtime = 0
     * the shipping time of the main detail is relevant.
     *
     * @param array $subArticle
     * @param int $setQuantity Number of set articles in Basket (default: 1)
     * @return int SubArticle deliverytime
     */
    public static function getSubArticleDeliveryTime($subArticle, $setQuantity = 1)
    {
        // Make sub article available now, if it is available
        // Scenario: 2 sub articles (A, B). A low deliverytime, B high deliverytime => set articles gets high
        // deliverytime. Even if only A is out of stock, set articles will show high deliverytime. The manipulation here
        // fixes this.
        if (self::getSubArticleInstock($subArticle) * $setQuantity > 0) {
            return 0;
        }

        return $subArticle['shippingtime'];
    }

    /**
     * Returns the weight of given sub article in the set articles (single weight * quantity).
     *
     * @param array $subArticle
     * @return int sub article delivery time
     */
    public static function getSubWeight($subArticle)
    {
        return $subArticle['weight'] * $subArticle['quantityInSet'];
    }

    /**
     * Adds the lastStock selection to the given querybuilder depending on the current Shopware version.
     *
     * @param QueryBuilder $builder
     * @return QueryBuilder
     */
    public static function addLastStockSelectionToQueryBuilder(QueryBuilder $builder)
    {
        if (ViisonCommonUtil::assertMinimumShopwareVersion('5.4.0')) {
            $builder->addSelect('ad.lastStock as laststock');
        } else {
            $builder->addSelect('a.lastStock as laststock');
        }

        return $builder;
    }
}
