<?php

/*
 * @author TRYDEA - 2024
 */

namespace Webapp\FileManagement\Service\XmlUtils\Normalizer\Webapp;

use Webapp\Core\Entity\AbstractVariable;
use Webapp\Core\Entity\Test;
use Webapp\Core\Enumeration\CombinationOperationEnum;
use Webapp\Core\Enumeration\ComparisonOperationEnum;
use Webapp\Core\Enumeration\TestTypeEnum;
use Webapp\FileManagement\Dto\Common\MinMaxDoubleDto;
use Webapp\FileManagement\Dto\Common\ReferenceDto;
use Webapp\FileManagement\Entity\RequestFile;
use Webapp\FileManagement\Exception\ParsingException;
use Webapp\FileManagement\Service\XmlUtils\Normalizer\AbstractXmlNormalizer;
use Webapp\FileManagement\Service\XmlUtils\Normalizer\XmlImportConfig;
use Webapp\FileManagement\Service\XmlUtils\ReaderHelperInterface;

/**
 * @template-extends AbstractXmlNormalizer<Test, TestXmlDto>
 *
 * @psalm-type TestXmlDto = array{
 *      "@condition": ?string,
 *      "@nom": ?string,
 *      "@operande1": ?AbstractVariable,
 *      "@operande2": ?AbstractVariable,
 *      "@operateur": ?string,
 *      "@variableAComparer": ?AbstractVariable,
 *      "@variableDeComparaison": ?AbstractVariable,
 *      ecart: ?MinMaxDoubleDto,
 *      limite: ?MinMaxDoubleDto,
 *      limiteObligatoire: ?MinMaxDoubleDto,
 *      limiteOptionnel: ?MinMaxDoubleDto,
 *      valeurAAffecter: string[],
 *      valeurAComparer: float[],
 * }
 */
class TestNormalizer extends AbstractXmlNormalizer
{
    private const OPERATION_MAP = [
        CombinationOperationEnum::SUBSTRACTION => 'soustraction',
        CombinationOperationEnum::MULTIPLICATION => 'multiplication',
        CombinationOperationEnum::DIVISION => 'division',
    ];

    private const CONDITIONS = [
        ComparisonOperationEnum::EQUAL,
        ComparisonOperationEnum::INF_EQUAL,
        ComparisonOperationEnum::SUP_EQUAL,
        ComparisonOperationEnum::INF,
        ComparisonOperationEnum::SUP,
    ];

    protected const ATTR_OPERANDE_1 = '@operande1';
    protected const ATTR_OPERANDE_2 = '@operande2';
    protected const ATTR_NOM = '@nom';
    protected const ATTR_ACTIVE = '@active';
    protected const ATTR_OPERATEUR = '@operateur';
    protected const ATTR_VARIABLE_DE_COMPARAISON = '@variableDeComparaison';
    protected const TAG_LIMITE = 'limite';
    protected const ATTR_MIN_DOUBLE = '@minDouble';
    protected const ATTR_MAX_DOUBLE = '@maxDouble';
    protected const TAG_ECART = 'ecart';
    protected const ATTR_CONDITION = '@condition';
    protected const ATTR_VARIABLE_A_AFFECTER = '@variableAAffecter';
    protected const TAG_VALEUR_A_AFFECTER = 'valeurAAffecter';
    protected const ATTR_VARIABLE = '@variable';
    protected const ATTR_VALEUR = '@valeur';
    protected const ATTR_VARIABLE_A_COMPARER = '@variableAComparer';
    protected const TAG_VALEUR_A_COMPARER = 'valeurAComparer';
    protected const TAG_LIMITE_OPTIONNEL = 'limiteOptionnel';
    protected const TAG_LIMITE_OBLIGATOIRE = 'limiteObligatoire';
    protected const NOM_TEST_SUR_INTERVALLE = 'Test sur intervalle';
    protected const NOM_TEST_D_ACCROISSEMENT = "Test d'accroissement";
    protected const NOM_TEST_SUR_COMBINAISON_ENTRE_VARIABLES = 'Test sur combinaison entre variables';
    protected const NOM_PRECALCUL_CONDITIONNEL = 'Précalcul conditionnel';

    protected function getClass(): string
    {
        return Test::class;
    }

    protected function getImportDataConfig(array $context): array
    {
        return [
            self::ATTR_NOM => 'string',
            self::ATTR_CONDITION => 'string',
            self::ATTR_OPERATEUR => 'string',
            self::ATTR_OPERANDE_1 => XmlImportConfig::reference(AbstractVariable::class),
            self::ATTR_OPERANDE_2 => XmlImportConfig::reference(AbstractVariable::class),
            self::TAG_VALEUR_A_AFFECTER => 'string[]',
            self::TAG_VALEUR_A_COMPARER => 'float[]',
            self::ATTR_VARIABLE_DE_COMPARAISON => XmlImportConfig::reference(AbstractVariable::class),
            self::ATTR_VARIABLE_A_COMPARER => XmlImportConfig::reference(AbstractVariable::class),
            self::TAG_LIMITE => XmlImportConfig::value(MinMaxDoubleDto::class),
            self::TAG_LIMITE_OPTIONNEL => XmlImportConfig::value(MinMaxDoubleDto::class),
            self::TAG_LIMITE_OBLIGATOIRE => XmlImportConfig::value(MinMaxDoubleDto::class),
            self::TAG_ECART => XmlImportConfig::value(MinMaxDoubleDto::class),
        ];
    }

    protected function validateImportData($data, array $context): void
    {
        if (!isset($data[self::ATTR_NOM]) || !isset($data[self::ATTR_ACTIVE])) {
            throw new ParsingException('', RequestFile::ERROR_TEST_INFO_MISSING);
        }

        $readerHelper = self::getReaderHelper($context);
        switch ($data[self::ATTR_NOM]) {
            case self::NOM_TEST_SUR_INTERVALLE:
                break;

            case self::NOM_TEST_D_ACCROISSEMENT:
                $this->testLinkedVariable($data[self::ATTR_VARIABLE_DE_COMPARAISON], $readerHelper);
                break;

            case self::NOM_TEST_SUR_COMBINAISON_ENTRE_VARIABLES:
                $this->testLinkedVariable($data[self::ATTR_OPERANDE_1], $readerHelper);
                $this->testLinkedVariable($data[self::ATTR_OPERANDE_2], $readerHelper);
                break;

            case self::NOM_PRECALCUL_CONDITIONNEL:
                $this->testLinkedVariable($data[self::ATTR_VARIABLE_DE_COMPARAISON], $readerHelper);
                if (isset($data[self::ATTR_VARIABLE_A_COMPARER])) {
                    $this->testLinkedVariable($data[self::ATTR_VARIABLE_A_COMPARER], $readerHelper);
                }
                break;

            default:
                throw new ParsingException('', RequestFile::ERROR_UNKNOWN_TEST_TYPE);
        }
    }

    /**
     * @throws ParsingException
     */
    private function testLinkedVariable(string $value, ReaderHelperInterface $readerHelper): void
    {
        if (\count(explode('/', $value)) < 3) {
            if (!$readerHelper->exists($value)) {
                throw new ParsingException('', RequestFile::ERROR_UNKNOWN_TEST_LINKED_VARIABLE);
            }
        } elseif (!$readerHelper->exists($value)) {
            throw new ParsingException('', RequestFile::ERROR_UNKNOWN_TEST_LINKED_VARIABLE);
        }
    }

    protected function generateImportedObject($data, array $context): Test
    {
        return new Test();
    }

    protected function completeImportedObject($object, $data, ?string $format, array $context): Test
    {
        switch ($data[self::ATTR_NOM]) {
            case self::NOM_TEST_SUR_INTERVALLE:
                $limiteOptionnel = $data[self::TAG_LIMITE_OPTIONNEL] ?? new MinMaxDoubleDto();
                $limiteObligatoire = $data[self::TAG_LIMITE_OBLIGATOIRE] ?? new MinMaxDoubleDto();

                $object
                    ->setType(TestTypeEnum::INTERVAL)
                    ->setProbIntervalMin($limiteOptionnel->min ?? 0.)
                    ->setProbIntervalMax($limiteOptionnel->max ?? 0.)
                    ->setAuthIntervalMin($limiteObligatoire->min ?? 0.)
                    ->setAuthIntervalMax($limiteObligatoire->max ?? 0.);
                break;

            case self::NOM_TEST_D_ACCROISSEMENT:
                $ecart = $data[self::TAG_ECART] ?? new MinMaxDoubleDto();

                $object
                    ->setType(TestTypeEnum::GROWTH)
                    ->setMinGrowth($ecart->min ?? 0.)
                    ->setMaxGrowth($ecart->max ?? 0.)
                    ->setComparedVariable($data[self::ATTR_VARIABLE_DE_COMPARAISON]);
                break;

            case self::NOM_TEST_SUR_COMBINAISON_ENTRE_VARIABLES:
                $limite = $data[self::TAG_LIMITE] ?? new MinMaxDoubleDto();

                $object
                    ->setType(TestTypeEnum::COMBINATION)
                    ->setCombinedVariable1($data[self::ATTR_OPERANDE_1])
                    ->setCombinedVariable2($data[self::ATTR_OPERANDE_2])
                    ->setMinLimit($limite->min ?? 0.)
                    ->setMaxLimit($limite->max ?? 0.)
                    ->setCombinationOperation(array_search($data[self::ATTR_OPERATEUR] ?? '', self::OPERATION_MAP, true) ?: CombinationOperationEnum::ADIDITION);
                break;

            case self::NOM_PRECALCUL_CONDITIONNEL:
                $valeurAComparer = $data[self::TAG_VALEUR_A_COMPARER] ?? [];
                $valeurAffecter = $data[self::TAG_VALEUR_A_AFFECTER] ?? [];

                $object
                    ->setType(TestTypeEnum::PRECONDITIONED)
                    ->setCompareWithVariable(null !== $data[self::ATTR_VARIABLE_A_COMPARER])
                    ->setComparedVariable1($data[self::ATTR_VARIABLE_DE_COMPARAISON])
                    ->setAssignedValue($valeurAffecter[self::ATTR_VALEUR] ?? '0')
                    ->setComparisonOperation($data[self::ATTR_CONDITION]);

                if (isset($data[self::ATTR_VARIABLE_A_COMPARER])) {
                    $object->setComparedVariable2($data[self::ATTR_VARIABLE_A_COMPARER]);
                } else {
                    $object->setComparedValue($valeurAComparer[self::ATTR_VALEUR] ?? 0);
                }

                // no break
            default:
        }

        return $object;
    }

    protected function extractData($object, array $context): array
    {
        switch ($object->getType()) {
            case TestTypeEnum::COMBINATION:
                $data = $this->extractCombinationTest($object, $context);
                break;

            case TestTypeEnum::GROWTH:
                $data = $this->extractGrowthTest($object, $context);
                break;

            case TestTypeEnum::INTERVAL:
                $data = $this->extractRangeTest($object, $context);
                break;

            case TestTypeEnum::PRECONDITIONED:
                $data = $this->extractPreconditionedCalculation($object, $context);
                break;

            default:
                $data = [];
        }

        return $data;
    }

    private function extractCombinationTest(Test $object, array $context = []): array
    {
        return [
            self::ATTR_XSI_TYPE => 'adonis.modeleMetier.projetDeSaisie.variables:TestCombinaisonEntreVariables',
            self::ATTR_OPERANDE_1 => new ReferenceDto($object->getCombinedVariable1()),
            self::ATTR_OPERANDE_2 => new ReferenceDto($object->getCombinedVariable2()),
            self::ATTR_NOM => self::NOM_TEST_SUR_COMBINAISON_ENTRE_VARIABLES,
            self::ATTR_ACTIVE => true,
            self::ATTR_OPERATEUR => self::OPERATION_MAP[$object->getCombinationOperation() ?? ''] ?? null,
            self::TAG_LIMITE => new MinMaxDoubleDto($object->getMinLimit(), $object->getMaxLimit()),
        ];
    }

    private function extractGrowthTest(Test $object, array $context = []): array
    {
        return [
            self::ATTR_XSI_TYPE => 'adonis.modeleMetier.projetDeSaisie.variables:TestsDeComparaison',
            self::ATTR_VARIABLE_DE_COMPARAISON => new ReferenceDto($object->getComparedVariable()),
            self::ATTR_ACTIVE => true,
            self::ATTR_NOM => 'Test d\'accroissement',
            self::TAG_ECART => new MinMaxDoubleDto($object->getMinGrowth(), $object->getMaxGrowth()),
        ];
    }

    private function extractRangeTest(Test $object, array $context = []): array
    {
        return [
            self::ATTR_XSI_TYPE => 'adonis.modeleMetier.projetDeSaisie.variables:TestConditionnelEntreVariables',
            self::ATTR_ACTIVE => true,
            self::ATTR_NOM => self::NOM_PRECALCUL_CONDITIONNEL,
            self::ATTR_CONDITION => $object->getComparisonOperation(),
            self::ATTR_VARIABLE_A_AFFECTER => new ReferenceDto($object->getVariable()),
            self::ATTR_VARIABLE_DE_COMPARAISON => new ReferenceDto($object->getComparedVariable1()),
            self::TAG_VALEUR_A_AFFECTER => [
                self::ATTR_XSI_TYPE => 'adonis.modeleMetier.saisieTerrain:MesureVariableAlphanumerique',
                self::ATTR_VARIABLE => new ReferenceDto($object->getVariable()),
                self::ATTR_VALEUR => $object->getAssignedValue(),
            ],
            self::ATTR_VARIABLE_A_COMPARER => $object->getCompareWithVariable() ? new ReferenceDto($object->getComparedVariable2()) : null,
            self::TAG_VALEUR_A_COMPARER => !$object->getCompareWithVariable() ? [
                self::ATTR_XSI_TYPE => 'adonis.modeleMetier.saisieTerrain:MesureVariableAlphanumerique',
                self::ATTR_VARIABLE => new ReferenceDto($object->getVariable()),
                self::ATTR_VALEUR => $object->getComparedValue(),
            ] : null,
        ];
    }

    private function extractPreconditionedCalculation(Test $object, array $context = []): array
    {
        return [
            self::ATTR_XSI_TYPE => 'adonis.modeleMetier.projetDeSaisie.variables:TestSurIntervalles',
            self::ATTR_ACTIVE => true,
            self::ATTR_NOM => self::NOM_TEST_SUR_INTERVALLE,
            self::TAG_LIMITE_OPTIONNEL => new MinMaxDoubleDto($object->getProbIntervalMin(), $object->getProbIntervalMax()),
            self::TAG_LIMITE_OBLIGATOIRE => new MinMaxDoubleDto($object->getAuthIntervalMin(), $object->getAuthIntervalMax()),
        ];
    }
}
