<?php
declare(strict_types=1);

namespace App\Service\Constraint;

/**
 * Classe Utils qui fournit de méthodes pour calculer les contraintes de temps
 */
abstract class AbstractTimeConstraintUtils
{
    public const NULL = 0;
    public const INF = 1;
    public const EQUAL = 3;
    public const SUP = 5;
    public const CANCEL_OPERATION = 9;
    public const START_KEY = 'start';
    public const END_KEY = 'end';
    public const NULL_VALUE = 'NULL';

    /**
     * Retourne true si l'utilisateur veux une période précise
     * @param array<bool|string> $historical
     * @return bool
     * @see DateTimeConstraintTest::testHasCustomPeriods()
     */
    protected function hasCustomPeriods(array $historical): bool
    {
        return array_key_exists('dateStart', $historical) && $historical['dateStart'] && array_key_exists('dateEnd', $historical) && $historical['dateEnd'];
    }

    /**
     * Fonction permettant d'ajouter une nouvelle contrainte de date
     * @param array<string, array<string, list<int>>> $contraints
     * @param array<string, list<int>> $newContraint
     * @return array<string, array<string, int[]>>
     * @see DateTimeConstraintTest::testAddConstraint()
     */
    protected function addConstraint(array $contraints, array $newContraint): array
    {
        $contraints = $this->mergeConstraint($contraints,$newContraint,self::START_KEY);
        return $this->mergeConstraint($contraints,$newContraint,self::END_KEY);
    }

    /**
     * Construit le tableau de contraintes pour une condition (start, end)
     * @param array<string, array<string, list<int>>> $contraints
     * @param array<string, list<int>> $newContraint
     * @param string $key
     * @return array<string, array<string, int[]>>
     */
    protected function mergeConstraint(array $contraints, array $newContraint, string $key): array
    {
        if(array_key_exists($key, $newContraint)){
            foreach ($newContraint[$key] as $dateKey => $arithmeticValue){
                //Si la date à déjà des conditions
                if(array_key_exists($dateKey, $contraints[$key])){
                    //Si la conditions (<, >, =, ...) n'est pas encore appliquée à la date
                    if(!in_array($arithmeticValue, $contraints[$key][$dateKey]))
                        $contraints[$key][$dateKey][] = $arithmeticValue;
                }else{
                    $contraints[$key][$dateKey] = [$arithmeticValue];
                }
            }
        }

        return $contraints;
    }

    /**
     * Nettoyage des contraintes (toutes celles supérieur à la condition cancel et les valeurs null isolées)
     * @param array<string, array<string, list<int>>> $constraints
     * @return array<string, array<string, list<int>>>
     * @see DateTimeConstraintTest::testCleanConstraints()
     */
    protected function cleanConstraints(array $constraints): array
    {
        $constraints[self::START_KEY] = $this->filterConstraint($constraints, self::START_KEY);
        $constraints[self::START_KEY] = $this->clearNull($constraints, self::START_KEY);

        $constraints[self::END_KEY] = $this->filterConstraint($constraints, self::END_KEY);
        $constraints[self::END_KEY] = $this->clearNull($constraints, self::END_KEY);
        return $constraints;
    }

    /**
     * Pour chaque contraintes appliquées à une date on vérifie qu'on ne dépasse pas la valeur cancel : c'est à dire
     * la condition qui démontre que toutes les valeurs arithmétiques ont été choisies
     * @param array<string, array<string, list<int>>> $constraints
     * @param string $key
     * @return array<string, list<int>>
     * @see DateTimeConstraintTest::testFilterConstraint()
     */
    protected function filterConstraint(array $constraints, string $key): array
    {
        return array_filter($constraints[$key], function($constraint){
            return array_sum($constraint) < self::CANCEL_OPERATION ;
        });
    }

    /**
     * On ne conserve pas les contraintes sur des conditions start et end si ces dernieres ne possède qu'une valeur null :
     * une valeur null doit obligatoirement s'appliquer avec un OR
     * @param array<string, array<string, list<int>>> $constraints
     * @param string $key
     * @return array<string, list<int>>
     * @see DateTimeConstraintTest::testClearNull()
     */
    protected function clearNull(array $constraints, string $key): array
    {
        if(count($constraints[$key]) === 1 && array_key_exists(self::NULL_VALUE, $constraints[$key]))
            $constraints[$key] = [];
        return $constraints[$key];
    }
}
