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

use Doctrine\DBAL\Connection as DBALConnection;
use Enlight_Config;
use Shopware\Plugins\ViisonCommon\Classes\Util\Localization;
use Shopware\Components\Model\ModelManager;

class SnippetDbAdapter extends \Enlight_Config_Adapter
{
    /**
     * @var int[]
     */
    protected $localeIdsInFallThroughOrder;

    /**
     * @var DBALConnection
     */
    protected $dbalConnection;

    /**
     * @var ModelManager
     */
    protected $localeRepository;

    /**
     * SnippetDbAdapter constructor.
     *
     * @param DBALConnection $dbalConnection
     * @param ModelManager $entityManager
     */
    public function __construct(DBALConnection $dbalConnection, ModelManager $entityManager)
    {
        parent::__construct([]);

        $this->dbalConnection = $dbalConnection;
        $this->localeRepository = $entityManager->getRepository('Shopware\\Models\\Shop\\Locale');
    }

    /**
     * @inheritdoc
     */
    public function read(Enlight_Config $config)
    {
        /**
         * In Shopware prior to 5.2.11, getSection can return an array instead of a string.
         * The behaviour varies depending on whether the value is set in the constructor
         * or using the setter.
         */
        if (is_array($config->getSection())) {
            $shopIdAndLocaleId = $config->getSection();
        } else {
            $shopIdAndLocaleId = explode(':', $config->getSection());
        }

        $shopId = $shopIdAndLocaleId[0];
        $localeId = $shopIdAndLocaleId[1];

        $snippets = $this->dbalConnection->fetchAll(
            'SELECT
                name,
                value
            FROM s_core_snippets
            WHERE namespace = :namespace
            AND shopId = :shopId
            ORDER BY FIELD(localeID, :orderByFields)',
            [
                'namespace' => $config->getName(),
                'shopId' => $shopId,
                'orderByFields' => $this->getLocalesInFallThroughOrder($localeId),
            ],
            [
                'orderByFields' => DBALConnection::PARAM_INT_ARRAY,
            ]
        );

        $configData = [];
        foreach ($snippets as $snippetEntry) {
            $configData[$snippetEntry['name']] = $snippetEntry['value'];
        }

        $config->setData($configData);

        return $this;
    }

    /**
     * Populates the list of locales in the correct order
     * for fall-through values.
     *
     * @param int $localeId
     * @return int[]
     */
    private function getLocalesInFallThroughOrder($localeId)
    {
        if ($this->localeIdsInFallThroughOrder !== null && $this->localeIdsInFallThroughOrder[0] === $localeId) {
            return $this->localeIdsInFallThroughOrder;
        }

        $locales = $this->getLocaleIdsInFallThroughOrder($localeId);
        $locales = array_merge($locales, $this->getEnglishLocaleIdsInFallbackOrder());

        $locales = array_unique($locales);

        // Array is reversed so that the fall-through-values are first
        // in the sql result. Since the values at the beginning will
        // later be overriden in the foreach loop, the later and
        // important values will therefore be kept.
        $this->localeIdsInFallThroughOrder = array_reverse($locales);

        return $this->localeIdsInFallThroughOrder;
    }

    /**
     * Gets the list of a languages locales in order of priority.
     *
     * @param int $localeId
     * @return int[]
     */
    private function getLocaleIdsInFallThroughOrder($localeId)
    {
        $locale = $this->localeRepository->findOneBy([
            'id' => $localeId,
        ]);
        $locales = [$locale];

        $twoCharLanguageCode = mb_substr($locale->getLocale(), 0, 2);

        // Try to find the main fallback locale of the given locale's language
        $mainLocaleIdentifierOfLanguage = Localization::getPrimaryLocaleForLanguage($twoCharLanguageCode);
        if ($mainLocaleIdentifierOfLanguage && $mainLocaleIdentifierOfLanguage !== $locale->getLocale()) {
            $mainLocaleOfLanguage = $this->localeRepository->findOneBy([
                'locale' => $mainLocaleIdentifierOfLanguage,
            ]);
            if ($mainLocaleOfLanguage) {
                $locales[] = $mainLocaleOfLanguage;
            }
        }

        $builder = $this->localeRepository->createQueryBuilder('locale');
        $builder
            ->select('locale')
            ->where('locale.locale LIKE :code')
            ->orderBy('locale.id', 'ASC')
            ->setParameter('code', $twoCharLanguageCode . '_%');
        $languageLocales = $builder->getQuery()->getResult();
        $foundLocals = array_merge($locales, $languageLocales);

        return array_map(function ($locale) {
            return $locale->getId();
        }, $foundLocals);
    }

    /**
     * Get all English language locales in order of their respective priority
     *
     * @return int[]
     */
    private function getEnglishLocaleIdsInFallbackOrder()
    {
        $builder = $this->localeRepository->createQueryBuilder('locale');
        $builder
            ->select('locale')
            ->where('locale.locale LIKE :code')
            ->orderBy('locale.id', 'ASC')
            ->setParameter('code', 'en_%');
        $englishLocales = $builder->getQuery()->getResult();

        return array_map(function ($locale) {
            return $locale->getId();
        }, $englishLocales);
    }

    /**
     * @inheritdoc
     */
    public function write(Enlight_Config $config)
    {
        throw new \Exception('Snippets may not be written dynamically. Please use the installation process.');
    }
}
