<?php

namespace Webapp\Core\Normalizer;

use ApiPlatform\Core\Api\IriConverterInterface;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\SerializerAwareInterface;
use Symfony\Component\Serializer\SerializerAwareTrait;
use Symfony\Contracts\Cache\CacheInterface;
use Webapp\Core\Entity\AbstractVariable;
use Webapp\Core\Entity\Annotation;
use Webapp\Core\Entity\Block;
use Webapp\Core\Entity\Experiment;
use Webapp\Core\Entity\Factor;
use Webapp\Core\Entity\FieldGeneration;
use Webapp\Core\Entity\FieldMeasure;
use Webapp\Core\Entity\GeneratorVariable;
use Webapp\Core\Entity\Individual;
use Webapp\Core\Entity\Measure;
use Webapp\Core\Entity\Modality;
use Webapp\Core\Entity\Platform;
use Webapp\Core\Entity\Project;
use Webapp\Core\Entity\ProjectData;
use Webapp\Core\Entity\SemiAutomaticVariable;
use Webapp\Core\Entity\Session;
use Webapp\Core\Entity\SimpleVariable;
use Webapp\Core\Entity\StateCode;
use Webapp\Core\Entity\SubBlock;
use Webapp\Core\Entity\SurfacicUnitPlot;
use Webapp\Core\Entity\Treatment;
use Webapp\Core\Entity\UnitPlot;

class ProjectDataViewNormalizer implements ContextAwareNormalizerInterface, SerializerAwareInterface
{
    private IriConverterInterface $iriConverter;
    private CacheInterface $cache;
    private array $iriMap;
    use SerializerAwareTrait;

    public function __construct(IriConverterInterface $iriConverter, CacheInterface $platformFullViewCache)
    {
        $this->iriConverter = $iriConverter;
        $this->cache = $platformFullViewCache;
    }

    /**
     * @param mixed $object
     * @param string|null $format
     * @param array $context
     * @return array|null
     */
    public function normalizeObject($object, string $format, array $context): ?array
    {
        set_time_limit(1200);
        if ($object === null) {
            return null;
        }
        $res = $object instanceof SurfacicUnitPlot ?
            $this->normalizeSurfacicUnitPlot($object, $format, $context) :
            (($object instanceof Individual) ?
                $this->normalizeIndividual($object, $format, $context) :
                (($object instanceof UnitPlot) ?
                    $this->normalizeUnitPlot($object, $format, $context) :
                    (($object instanceof SubBlock) ?
                        $this->normalizeSubBlock($object, $format, $context) :
                        (($object instanceof Block) ?
                            $this->normalizeBlock($object, $format, $context) :
                            (($object instanceof Experiment) ?
                                $this->normalizeExperiment($object, $format, $context) :
                                (($object instanceof Platform) ?
                                    $this->normalizePlatform($object, $format, $context) :
                                    (($object instanceof Modality) ?
                                        $this->normalizeModality($object, $format, $context) :
                                        (($object instanceof Treatment) ?
                                            $this->normalizeTreatment($object, $format, $context) :
                                            (($object instanceof Factor) ?
                                                $this->normalizeFactor($object, $format, $context) :
                                                (($object instanceof StateCode) ?
                                                    $this->normalizeState($object, $format, $context) :
                                                    (($object instanceof Annotation) ?
                                                        $this->normalizeAnnotation($object, $format, $context) :
                                                        (($object instanceof GeneratorVariable) ?
                                                            $this->normalizeGeneratorVariable($object, $format, $context) :
                                                            (($object instanceof SemiAutomaticVariable) ?
                                                                $this->normalizeSemiAutomaticVariable($object, $format, $context) :
                                                                (($object instanceof SimpleVariable) ?
                                                                    $this->normalizeSimpleVariable($object, $format, $context) :
                                                                    (($object instanceof FieldMeasure) ?
                                                                        $this->normalizeFieldMeasures($object, $format, $context) :
                                                                        (($object instanceof Measure) ?
                                                                            $this->normalizeMeasure($object, $format, $context) :
                                                                            (($object instanceof FieldGeneration) ?
                                                                                $this->normalizeFieldGeneration($object, $format, $context) :
                                                                                (($object instanceof Session) ?
                                                                                    $this->normalizeSession($object, $format, $context) :
                                                                                    (($object instanceof Project) ?
                                                                                        $this->normalizeProject($object, $format, $context) :
                                                                                        (($object instanceof ProjectData) ?
                                                                                            $this->normalizeProjectData($object, $format, $context) :
                                                                                            null))))))))))))))))))));
        $res['@id'] = $this->iriConverter->getIriFromItem($object);
        $res['id'] = $object->getId();
        return array_filter($res, fn($item) => $item !== null);
    }

    /**
     * @param $object
     * @param string|null $format
     * @param array $context
     * @return array
     */
    public function normalize($object, string $format = null, array $context = []): array
    {
        $this->iriMap = [];
        $res = $this->normalizeObject($object, $format, $context);
        return $res;
    }
    public function supportsNormalization($data, string $format = null, array $context = []): bool
    {
        return ($data instanceof ProjectData ||
                $data instanceof Session) &&
            isset($context['groups']) && in_array("webapp_data_view", $context['groups']);
    }

    private function normalizeProjectData(ProjectData $projectData, string $format, array $context): array
    {
        return [
            'project' => $this->normalizeObject($projectData->getProject(), $format, $context),
            'sessions' => array_map(fn($session) => $this->normalizeObject($session, $format, $context), $projectData->getSessions()->toArray()),
            'simpleVariables' => array_map(fn($simpleVariable) => $this->normalizeObject($simpleVariable, $format, $context), $projectData->getSimpleVariables()->toArray()),
            'generatorVariables' => array_map(fn($generatorVariable) => $this->normalizeObject($generatorVariable, $format, $context), $projectData->getGeneratorVariables()->toArray()),
            'semiAutomaticVariables' => array_map(fn($semiAutomaticVariable) => $this->normalizeObject($semiAutomaticVariable, $format, $context), $projectData->getSemiAutomaticVariables()->toArray()),
            'name' => $projectData->getName(),
            'start' => $this->serializer->normalize($projectData->getStart(), $format, $context),
            'end' => $this->serializer->normalize($projectData->getEnd(), $format, $context),
            'userName' => $projectData->getUserName(),
        ];
    }

    private function normalizeProject(Project $project, string $format, array $context): array
    {
        return [];
    }

    private function normalizeSession(Session $session, string $format, array $context): array
    {
        return [
            'startedAt' => $this->serializer->normalize($session->getStartedAt(), $format, $context),
            'endedAt' => $this->serializer->normalize($session->getEndedAt(), $format, $context),
            'fieldMeasures' => array_map(fn($fieldMeasure) => $this->normalizeObject($fieldMeasure, $format, $context), $session->getFieldMeasures()->toArray()),
            'annotations' => array_map(fn($annotation) => $this->normalizeObject($annotation, $format, $context), $session->getAnnotations()->toArray()),
        ];
    }

    private function normalizeFieldMeasures(FieldMeasure $fieldMeasure, string $format, array $context): array
    {
        if(!isset($this->iriMap[$this->iriConverter->getIriFromItem($fieldMeasure->getTarget())])){
            $this->iriMap[$this->iriConverter->getIriFromItem($fieldMeasure->getTarget())] = $this->normalizeObject($fieldMeasure->getTarget(), $format, $context);
        }
        $fieldGenerations = $fieldMeasure->getFieldGenerations()->toArray();
        usort($fieldGenerations, fn(FieldGeneration $a, FieldGeneration $b) => $a->getIndex() - $b->getIndex());
        return [
            'variable' => $this->iriConverter->getIriFromItem($fieldMeasure->getVariable()),
            'target' => $this->iriMap[$this->iriConverter->getIriFromItem($fieldMeasure->getTarget())],
            'targetType' => $fieldMeasure->getTargetType(),
            'measures' => array_map(fn($measure) => $this->normalizeObject($measure, $format, $context), $fieldMeasure->getMeasures()->toArray()),
            'fieldGenerations' => array_map(fn($fieldGeneration) => $this->normalizeObject($fieldGeneration, $format, $context),$fieldGenerations),
        ];
    }

    private function normalizeVariable(AbstractVariable $variable, string $format, array $context): array
    {
        return [
            'name' => $variable->getName(),
            'shortName' => $variable->getShortName(),
            'repetitions' => $variable->getRepetitions(),
            'unit' => $variable->getUnit(),
            'pathLevel' => $variable->getPathLevel(),
            'identifier' => $variable->getIdentifier(),
            'format' => $variable->getFormat(),
        ];
    }

    private function normalizeSimpleVariable(SimpleVariable $variable, string $format, array $context): array
    {
        return array_merge($this->normalizeVariable($variable, $format, $context), [
            'type' => $variable->getType(),
            'scale' => $variable->getScale() !== null ? $this->iriConverter->getIriFromItem($variable->getScale()) : null,
        ]);
    }

    private function normalizeGeneratorVariable(GeneratorVariable $variable, string $format, array $context): array
    {
        return array_merge($this->normalizeVariable($variable, $format, $context), [
            'generatedPrefix' => $variable->getGeneratedPrefix(),
            'generatedSimpleVariables' => array_map(fn($generatedSimpleVariable) => $this->normalizeObject($generatedSimpleVariable, $format, $context), $variable->getGeneratedSimpleVariables()->toArray()),
            'generatedGeneratorVariables' => array_map(fn($generatedGeneratorVariable) => $this->normalizeObject($generatedGeneratorVariable, $format, $context), $variable->getGeneratedGeneratorVariables()->toArray()),
        ]);
    }

    private function normalizeSemiAutomaticVariable(SemiAutomaticVariable $variable, string $format, array $context): array
    {
        return array_merge($this->normalizeVariable($variable, $format, $context), [
            'type' => $variable->getType(),
        ]);
    }


    private function normalizePlatform(Platform $platform, string $format, array $context): array
    {
        return [
            'name' => $platform->getName(),
        ];
    }

    private function normalizeExperiment(Experiment $experiment, string $format, array $context): array
    {
        return [
            'name' => $experiment->getName(),
            'platform' => $this->normalizeObject($experiment->getPlatform(), $format, $context),
        ];
    }

    private function normalizeBlock(Block $block, string $format, array $context): array
    {
        return [
            'number' => $block->getNumber(),
            'experiment' => $this->normalizeObject($block->getExperiment(), $format, $context),
        ];
    }

    private function normalizeSubBlock(SubBlock $subBlock, string $format, array $context): array
    {
        return [
            'number' => $subBlock->getNumber(),
            'block' => $this->normalizeObject($subBlock->getBlock(), $format, $context),
        ];
    }

    private function normalizeUnitPlot(UnitPlot $unitPlot, string $format, array $context): array
    {
        return [
            'number' => $unitPlot->getNumber(),
            'treatment' => $this->normalizeObject($unitPlot->getTreatment(), $format, $context),
            'subBlock' => $this->normalizeObject($unitPlot->getSubBlock(), $format, $context),
            'block' => $this->normalizeObject($unitPlot->getBlock(), $format, $context),
        ];
    }

    private function normalizeSurfacicUnitPlot(SurfacicUnitPlot $surfacicUnitPlot, string $format, array $context): array
    {
        return [
            'number' => $surfacicUnitPlot->getNumber(),
            'x' => $surfacicUnitPlot->getX(),
            'y' => $surfacicUnitPlot->getY(),
            'dead' => $surfacicUnitPlot->isDead(),
            'appeared' => $this->serializer->normalize($surfacicUnitPlot->getAppeared(), $format, $context),
            'disappeared' => $this->serializer->normalize($surfacicUnitPlot->getDisappeared(), $format, $context),
            'identifier' => $surfacicUnitPlot->getIdentifier(),
            'treatment' => $this->normalizeObject($surfacicUnitPlot->getTreatment(), $format, $context),
            'subBlock' => $this->normalizeObject($surfacicUnitPlot->getSubBlock(), $format, $context),
            'block' => $this->normalizeObject($surfacicUnitPlot->getBlock(), $format, $context),
        ];
    }

    private function normalizeIndividual(Individual $individual, string $format, array $context): array
    {
        return [
            'number' => $individual->getNumber(),
            'x' => $individual->getX(),
            'y' => $individual->getY(),
            'dead' => $individual->isDead(),
            'appeared' => $this->serializer->normalize($individual->getAppeared(), $format, $context),
            'disappeared' => $this->serializer->normalize($individual->getDisappeared(), $format, $context),
            'identifier' => $individual->getIdentifier(),
            'unitPlot' => $this->normalizeObject($individual->getUnitPlot(), $format, $context),
        ];
    }

    private function normalizeFactor(Factor $factor, string $format, array $context): array
    {
        return [
            'name' => $factor->getName(),
            'order' => $factor->getOrder(),
        ];
    }

    private function normalizeTreatment(Treatment $treatment, string $format, array $context): array
    {
        return [
            'name' => $treatment->getName(),
            'shortName' => $treatment->getShortName(),
            'modalities' => array_map(fn($modality) => $this->normalizeObject($modality, $format, $context), $treatment->getModalities()->toArray()),
        ];
    }

    private function normalizeModality(Modality $modality, string $format, array $context): array
    {
        return [
            'value' => $modality->getValue(),
            'shortName' => $modality->getShortName(),
            'factor' => $this->normalizeObject($modality->getFactor(), $format, $context),
        ];
    }

    private function normalizeMeasure(Measure $measure, string $format, array $context): array
    {
        return [
            'value' => $measure->getValue(),
            'repetition' => $measure->getRepetition(),
            'timestamp' => $this->serializer->normalize($measure->getTimestamp(), $format, $context),
            'state' => $this->normalizeObject($measure->getState(), $format, $context),
        ];
    }

    private function normalizeState(StateCode $state, string $format, array $context): array
    {
        return [
            'code' => $state->getCode(),
        ];
    }

    private function normalizeFieldGeneration(FieldGeneration $modality, string $format, array $context): array
    {
        return [
            'index' => $modality->getIndex(),
            'prefix' => $modality->getPrefix(),
            'numeralIncrement' => $modality->isNumeralIncrement(),
            'children' => array_map(fn($child) => $this->normalizeObject($child, $format, $context), $modality->getChildren()->toArray()),
        ];
    }

    private function normalizeVariableAnnotation(Annotation $annotation, string $format, array $context): array
    {
        return [
            'target' => $this->iriConverter->getIriFromItem($annotation->getTarget()),
        ];
    }

    private function normalizeObjectAnnotation(Annotation $annotation, string $format, array $context): array
    {
        if(!isset($this->iriMap[$this->iriConverter->getIriFromItem($annotation->getTarget())])){
            $this->iriMap[$this->iriConverter->getIriFromItem($annotation->getTarget())] = $this->normalizeObject($annotation->getTarget(), $format, $context);
        }
        return [
            'target' => $this->iriMap[$this->iriConverter->getIriFromItem($annotation->getTarget())],
        ];
    }

    private function normalizeAnnotation(Annotation $annotation, string $format, array $context): array
    {
        return array_merge( ($annotation->getTargetType() === null ?
            $this->normalizeVariableAnnotation($annotation, $format, $context) :
            $this->normalizeObjectAnnotation($annotation, $format, $context)), [
            'name' => $annotation->getName(),
            'type' => $annotation->getType(),
            'value' => $annotation->getValue(),
            'image' => $annotation->getImage(),
            'timestamp' => $this->serializer->normalize($annotation->getTimestamp(), $format, $context),
            'categories' => $annotation->getCategories(),
            'keywords' => $annotation->getKeywords(),
            'targetType' => $annotation->getTargetType(),
        ]);
    }
}
