<?php
/**
 * @category Mageants SimpleConfigurable
 * @package Mageants_SimpleConfigurable
 * @copyright Copyright (c) 2017 Mageants
 * @author Mageants Team <info@mageants.com>
 */

namespace Mageants\SimpleConfigurable\Block\Product\Renderer\Listing;

use Mageants\SimpleConfigurable\Helper\Data as SimpleConfigurableHelper;
use Magento\Catalog\Block\Product\Context;
use Magento\Catalog\Helper\Product as CatalogProduct;
use Magento\Catalog\Model\Product;
use Magento\ConfigurableProduct\Helper\Data;
use Magento\ConfigurableProduct\Model\ConfigurableAttributeData;
use Magento\Customer\Helper\Session\CurrentCustomer;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Json\EncoderInterface;
use Magento\Framework\Pricing\PriceCurrencyInterface;
use Magento\Framework\Stdlib\ArrayUtils;
use Magento\Swatches\Helper\Data as SwatchData;
use Magento\Swatches\Helper\Media;
use Magento\Swatches\Model\SwatchAttributesProvider;

/**
 * Swatch renderer block in Category page
 *
 * @api
 * @since 100.0.2
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 */
class Configurable extends \Magento\Swatches\Block\Product\Renderer\Configurable
{
    /**
     * @var SimpleConfigurableHelper
     */
    private $simpleConfigurableHelper;

    /**
     * @var \Magento\Framework\Locale\Format
     */
    private $localeFormat;

    /**
     * @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Variations\Prices
     */
    private $variationPrices;

    /**
     * @var \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
     */
    protected $scopeConfig;

    /**
     * @var \Magento\Catalog\Model\Product
     */
    protected $selectedProduct;

    /**
     * @param Context $context
     * @param ArrayUtils $arrayUtils
     * @param EncoderInterface $jsonEncoder
     * @param Data $helper
     * @param \Magento\Catalog\Model\Product $selectedProduct
     * @param CatalogProduct $catalogProduct
     * @param CurrentCustomer $currentCustomer
     * @param PriceCurrencyInterface $priceCurrency
     * @param ConfigurableAttributeData $configurableAttributeData
     * @param SwatchData $swatchHelper
     * @param Media $swatchMediaHelper
     * @param SimpleConfigurableHelper $simpleConfigurableHelper
     * @param array $data other data
     * @param SwatchAttributesProvider $swatchAttributesProvider
     * @param \Magento\Framework\Locale\Format|null $localeFormat
     * @param \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Variations\Prices|null $variationPrices
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
     */
    public function __construct(
        Context $context,
        ArrayUtils $arrayUtils,
        EncoderInterface $jsonEncoder,
        Data $helper,
        \Magento\Catalog\Model\Product $selectedProduct,
        CatalogProduct $catalogProduct,
        CurrentCustomer $currentCustomer,
        PriceCurrencyInterface $priceCurrency,
        ConfigurableAttributeData $configurableAttributeData,
        SwatchData $swatchHelper,
        Media $swatchMediaHelper,
        SimpleConfigurableHelper $simpleConfigurableHelper,
        array $data = [],
        ?SwatchAttributesProvider $swatchAttributesProvider = null,
        ?\Magento\Framework\Locale\Format $localeFormat = null,
        ?\Magento\ConfigurableProduct\Model\Product\Type\Configurable\Variations\Prices $variationPrices = null
    ) {
        parent::__construct(
            $context,
            $arrayUtils,
            $jsonEncoder,
            $helper,
            $catalogProduct,
            $currentCustomer,
            $priceCurrency,
            $configurableAttributeData,
            $swatchHelper,
            $swatchMediaHelper,
            $data,
            $swatchAttributesProvider
        );
        $this->simpleConfigurableHelper = $simpleConfigurableHelper;
        $this->scopeConfig = $context->getScopeConfig();
        $this->localeFormat = $localeFormat ?: ObjectManager::getInstance()->get(
            \Magento\Framework\Locale\Format::class
        );
        $this->variationPrices = $variationPrices ?: ObjectManager::getInstance()->get(
            \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Variations\Prices::class
        );
        $this->selectedProduct = $selectedProduct;
    }

    /**
     * Check is enable
     *
     * @return boolean
     */
    public function isEnable()
    {
        return $this->simpleConfigurableHelper->isEnable();
    }

    /**
     * Get preselect source
     *
     * @return string
     */
    public function getPreselectSource()
    {
        return $this->simpleConfigurableHelper->getPreselectSource();
    }

    /**
     * Get preselect option id
     *
     * @return string
     */
    public function getPreselectOptionId()
    {
        $children = $this->getProduct()->getTypeInstance()->getUsedProducts($this->getProduct());
        $optionDatas = $this->getProduct()->getTypeInstance()->getConfigurableOptions($this->getProduct());

        $optionCode = [];
        $preoptionCode = [];
        if ($this->getPreselectSource() == 0) {
            if (count($optionDatas) > 0) {
                foreach ($optionDatas as $opt => $optionData) {
                    foreach ($optionData as $optionVal) {
                        $optionCode[$opt] = $optionVal['attribute_code'];
                        $preoptionCode[$optionVal['attribute_code']] = $optionVal['value_index'];
                        break;
                    }
                }
            }
            return $this->jsonEncoder->encode($preoptionCode);
        } elseif ($this->getPreselectSource() == 1) {
            if (!$this->getProduct()->getIsDefaultSelected()) {
                $preoptionCode = [];
                return $this->jsonEncoder->encode($preoptionCode);
            } else {
                foreach ($optionDatas as $opt => $optionData) {
                    foreach ($optionData as $optionVal) {
                        $optionCode[$opt] = $optionVal['attribute_code'];
                        break;
                    }
                }

                $selectedOpt = [];
                $defaultSelected = $this->selectedProduct->load($this->getProduct()->getIsDefaultSelected());
                foreach ($optionCode as $k => $optCode) {
                    $selectedOpt[$optCode] = $defaultSelected->getData($optCode);
                }

                return $this->jsonEncoder->encode($selectedOpt);
            }
        } elseif ($this->getPreselectSource() == 2) {
            if (count($optionDatas) > 0) {
                foreach ($optionDatas as $opt => $optionData) {
                    foreach ($optionData as $optionVal) {
                        $optionCode[$opt] = $optionVal['attribute_code'];
                        $preoptionCode[$optionVal['attribute_code']] = $optionVal['value_index'];
                        break;
                    }
                }
            }

            $maxPrice = 0;
            $maxPriceProductId = 0;
            $productPriceArray = [];

            foreach ($children as $child) {
                if ($maxPrice < $child->getPrice()) {
                    $maxPrice = $child->getPrice();
                    $maxPriceProductId = $child->getId();
                }

                $productPriceArray[] = $child->getPrice();
            }

            if (!empty($productPriceArray) && count(array_unique($productPriceArray)) === 1) {
                return $this->jsonEncoder->encode($preoptionCode);
            } else {
                $selectedOpt = [];
                $maxPriceProduct = $this->selectedProduct->load($maxPriceProductId);
                foreach ($optionCode as $k => $optCode) {
                    $selectedOpt[$optCode] = $maxPriceProduct->getData($optCode);
                }

                return $this->jsonEncoder->encode($selectedOpt);
            }
        } elseif ($this->getPreselectSource() == 3) {
            if (count($optionDatas) > 0) {
                foreach ($optionDatas as $opt => $optionData) {
                    foreach ($optionData as $optionVal) {
                        $optionCode[$opt] = $optionVal['attribute_code'];
                        $preoptionCode[$optionVal['attribute_code']] = $optionVal['value_index'];
                        break;
                    }
                }
            }

            $lowPrice = 0;
            $lowPriceProductId = 0;
            $productPriceArray = [];

            foreach ($children as $child) {
                if ($lowPrice == 0) {
                    $lowPrice = $child->getPrice();
                    $lowPriceProductId = $child->getId();
                } else {
                    if ($lowPrice > $child->getPrice()) {
                        $lowPrice = $child->getPrice();
                        $lowPriceProductId = $child->getId();
                    }
                }

                $productPriceArray[] = $child->getPrice();
            }

            if (!empty($productPriceArray) && count(array_unique($productPriceArray)) === 1) {
                return $this->jsonEncoder->encode($preoptionCode);
            } else {
                $selectedOpt = [];
                $lowPriceProduct = $this->selectedProduct->load($lowPriceProductId);

                foreach ($optionCode as $k => $optCode) {
                    $selectedOpt[$optCode] = $lowPriceProduct->getData($optCode);
                }

                return $this->jsonEncoder->encode($selectedOpt);
            }
        } else {
            $selectedOpt = [];
            return $this->jsonEncoder->encode($selectedOpt);
        }
    }

    /**
     * Get renderer template
     *
     * @return string
     */
    protected function getRendererTemplate()
    {
        $this->_template = 'Mageants_SimpleConfigurable::product/listing/renderer.phtml';
        return $this->_template;
    }

    /**
     * Render block hook
     *
     * Produce and return block's html output
     *
     * @return string
     * @since 100.1.5
     */
    protected function _toHtml()
    {
        $output = '';
        if ($this->isProductHasSwatchAttribute()) {
            $output = parent::_toHtml();
        }

        return $output;
    }

    /**
     * Get swatch attributes data
     *
     * @return array
     */
    protected function getSwatchAttributesData()
    {
        $result = [];
        $swatchAttributeData = parent::getSwatchAttributesData();
        foreach ($swatchAttributeData as $attributeId => $item) {
            if (!empty($item['used_in_product_listing'])) {
                $result[$attributeId] = $item;
            }
        }
        return $result;
    }

    /**
     * Composes configuration for js
     *
     * @return string
     */
    public function getJsonConfig()
    {
        $this->unsetData('allow_products');
        return parent::getJsonConfig();
    }

    /**
     * Composes configuration for js price format
     *
     * @return string
     */
    public function getPriceFormatJson()
    {
        return $this->jsonEncoder->encode($this->localeFormat->getPriceFormat());
    }

    /**
     * Composes configuration for js price
     *
     * @return string
     */
    public function getPricesJson()
    {
        return $this->jsonEncoder->encode(
            $this->variationPrices->getFormattedPrices($this->getProduct()->getPriceInfo())
        );
    }

    /**
     * Do not load images for Configurable product with swatches due to its loaded by request
     *
     * @return array
     * @since 100.2.0
     */
    protected function getOptionImages()
    {
        return [];
    }

    /**
     * Add images to result json config in case of Layered Navigation is used
     *
     * @return array
     * @since 100.2.0
     */
    protected function _getAdditionalConfig()
    {
        $config = parent::_getAdditionalConfig();
        if (!empty($this->getRequest()->getQuery()->toArray())) {
            $config['preSelectedGallery'] = $this->getProductVariationWithMedia(
                $this->getProduct(),
                $this->getRequest()->getQuery()->toArray()
            );
        }

        return $config;
    }

    /**
     * Get product images for chosen variation based on selected product attributes
     *
     * @param Product $configurableProduct
     * @param array $additionalAttributes
     * @return array
     */
    private function getProductVariationWithMedia(
        Product $configurableProduct,
        array $additionalAttributes = []
    ) {
        $configurableAttributes = $this->getLayeredAttributesIfExists($configurableProduct, $additionalAttributes);
        if (!$configurableAttributes) {
            return [];
        }

        $product = $this->swatchHelper->loadVariationByFallback($configurableProduct, $configurableAttributes);

        return $product ? $this->swatchHelper->getProductMediaGallery($product) : [];
    }

    /**
     * Get product attributes which uses in layered navigation and present for given configurable product
     *
     * @param Product $configurableProduct
     * @param array $additionalAttributes
     * @return array
     */
    private function getLayeredAttributesIfExists(Product $configurableProduct, array $additionalAttributes)
    {
        $configurableAttributes = $this->swatchHelper->getAttributesFromConfigurable($configurableProduct);

        $layeredAttributes = [];

        $configurableAttributes = array_map(function ($attribute) {
            return $attribute->getAttributeCode();
        }, $configurableAttributes);

        $commonAttributeCodes = array_intersect(
            $configurableAttributes,
            array_keys($additionalAttributes)
        );

        foreach ($commonAttributeCodes as $attributeCode) {
            $layeredAttributes[$attributeCode] = $additionalAttributes[$attributeCode];
        }

        return $layeredAttributes;
    }
}
