<?php

namespace Webapp\Core\Normalizer;

use ApiPlatform\Core\Api\IriConverterInterface;
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\SerializerAwareInterface;
use Symfony\Component\Serializer\SerializerAwareTrait;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
use Webapp\Core\Entity\Block;
use Webapp\Core\Entity\Experiment;
use Webapp\Core\Entity\Factor;
use Webapp\Core\Entity\Individual;
use Webapp\Core\Entity\Modality;
use Webapp\Core\Entity\OutExperimentationZone;
use Webapp\Core\Entity\Platform;
use Webapp\Core\Entity\Protocol;
use Webapp\Core\Entity\SubBlock;
use Webapp\Core\Entity\SurfacicUnitPlot;
use Webapp\Core\Entity\Treatment;
use Webapp\Core\Entity\UnitPlot;

class BusinessObjectFullNormalizer implements ContextAwareNormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface
{
    private IriConverterInterface $iriConverter;
    private CacheInterface $cache;
    private bool $exportNotes;

    use SerializerAwareTrait;

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

    /**
     * @param Platform | Experiment $object
     * @param string|null $format
     * @param array $context
     * @return array|\ArrayObject|bool|float|int|string|void|null
     */
    public function normalize($object, string $format = null, array $context = [])
    {
        $this->exportNotes = in_array("note_view", $context['groups']);

        $res = $object instanceof SurfacicUnitPlot ?
            $this->normalizeSurfacicUnitPlot($object, $format, $context) :
            (($object instanceof Individual) ?
                $this->normalizeIndividual($object, $format, $context) :
                (($object instanceof OutExperimentationZone) ?
                    $this->normalizeOEZ($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->exportNotes ?
                                        $this->normalizeExperiment($object, $format, $context) :
                                        $this->cache->get($object->getId(), function (ItemInterface $item) use ($context, $format, $object) {
                                            return $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 Protocol) ?
                                                        $this->normalizeProtocol($object, $format, $context) :
                                                        null)))))))))));
        $res['@id'] = $this->iriConverter->getIriFromItem($object);
        return $res;
    }

    public function supportsNormalization($data, string $format = null, array $context = [])
    {
        return ($data instanceof Platform ||
                $data instanceof Experiment ||
                $data instanceof Block ||
                $data instanceof SubBlock ||
                $data instanceof UnitPlot ||
                $data instanceof SurfacicUnitPlot ||
                $data instanceof Individual ||
                $data instanceof Protocol ||
                $data instanceof Factor ||
                $data instanceof Modality ||
                $data instanceof Treatment ||
                $data instanceof OutExperimentationZone
            ) &&
            isset($context['groups']) && in_array("platform_full_view", $context['groups']);
    }

    private function normalizePlatform(Platform $platform, string $format = null, array $context = []): array
    {
        return [
            'name' => $platform->getName(),
            'siteName' => $platform->getSiteName(),
            'placeName' => $platform->getPlaceName(),
            'comment' => $platform->getComment(),
            'created' => $this->serializer->normalize($platform->getCreated(), $format, $context),
            'experiments' => array_map(fn($experiment) => $this->serializer->normalize($experiment, $format, $context), $platform->getExperiments()->toArray()),
            'owner' => $this->iriConverter->getIriFromItem($platform->getOwner()),
            'color' => $platform->getColor(),
            'xMesh' => $platform->getXMesh(),
            'yMesh' => $platform->getYMesh(),
            'origin' => $platform->getOrigin(),
            'northIndicator' => $this->serializer->normalize($platform->getNorthIndicator(), $format, $context),
            'graphicalTextZones' => array_map(fn($graphicalTextZone) => $this->serializer->normalize($graphicalTextZone, $format, $context), $platform->getGraphicalTextZones()->toArray()),
        ];
    }

    private function normalizeExperiment(Experiment $experiment, string $format = null, array $context = []): array
    {
        $res = [
            'name' => $experiment->getName(),
            'individualUP' => $experiment->isIndividualUP(),
            'comment' => $experiment->getComment(),
            'created' => $this->serializer->normalize($experiment->getCreated(), $format, $context),
            'protocol' => $this->serializer->normalize($experiment->getProtocol(), $format, $context),
            'owner' => $this->iriConverter->getIriFromItem($experiment->getOwner()),
            'blocks' => array_map(fn($oez) => $this->serializer->normalize($oez, $format, $context), $experiment->getBlocks()->toArray()),
            'outExperimentationZones' => array_map(fn($oez) => $this->serializer->normalize($oez, $format, $context), $experiment->getOutExperimentationZones()->toArray()),
            'color' => $experiment->getColor(),
            'state' => $experiment->getState(),
        ];
        if ($this->exportNotes) {
            $res['notes'] = array_map(fn($note) => $this->iriConverter->getIriFromItem($note), $experiment->getNotes()->toArray());
        }
        return $res;
    }

    private function normalizeBlock(Block $block, string $format = null, array $context = []): array
    {
        $res = [
            'number' => $block->getNumber(),
            'subBlocks' => array_map(fn($oez) => $this->serializer->normalize($oez, $format, $context), $block->getSubBlocks()->toArray()),
            'unitPlots' => array_map(fn($oez) => $this->serializer->normalize($oez, $format, $context), $block->getUnitPlots()->toArray()),
            'surfacicUnitPlots' => array_map(fn($oez) => $this->serializer->normalize($oez, $format, $context), $block->getSurfacicUnitPlots()->toArray()),
            'outExperimentationZones' => array_map(fn($oez) => $this->serializer->normalize($oez, $format, $context), $block->getOutExperimentationZones()->toArray()),
            'color' => $block->getColor(),
            'comment' => $block->getComment(),
        ];
        if ($this->exportNotes) {
            $res['notes'] = array_map(fn($note) => $this->iriConverter->getIriFromItem($note), $block->getNotes()->toArray());
        }
        return $res;
    }

    private function normalizeSubBlock(SubBlock $subBlock, string $format = null, array $context = []): array
    {
        $res = [
            'number' => $subBlock->getNumber(),
            'unitPlots' => array_map(fn($oez) => $this->serializer->normalize($oez, $format, $context), $subBlock->getUnitPlots()->toArray()),
            'surfacicUnitPlots' => array_map(fn($oez) => $this->serializer->normalize($oez, $format, $context), $subBlock->getSurfacicUnitPlots()->toArray()),
            'outExperimentationZones' => array_map(fn($oez) => $this->serializer->normalize($oez, $format, $context), $subBlock->getOutExperimentationZones()->toArray()),
            'color' => $subBlock->getColor(),
            'comment' => $subBlock->getComment(),
        ];
        if ($this->exportNotes) {
            $res['notes'] = array_map(fn($note) => $this->iriConverter->getIriFromItem($note), $subBlock->getNotes()->toArray());
        }
        return $res;
    }

    private function normalizeUnitPlot(UnitPlot $unitPlot, string $format = null, array $context = []): array
    {
        $res = [
            'number' => $unitPlot->getNumber(),
            'treatment' => $this->iriConverter->getIriFromItem($unitPlot->getTreatment()),
            'individuals' => array_map(fn($oez) => $this->serializer->normalize($oez, $format, $context), $unitPlot->getIndividuals()->toArray()),
            'outExperimentationZones' => array_map(fn($oez) => $this->serializer->normalize($oez, $format, $context), $unitPlot->getOutExperimentationZones()->toArray()),
            'color' => $unitPlot->getColor(),
            'comment' => $unitPlot->getComment(),
        ];
        if ($this->exportNotes) {
            $res['notes'] = array_map(fn($note) => $this->iriConverter->getIriFromItem($note), $unitPlot->getNotes()->toArray());
        }
        return $res;
    }

    private function normalizeSurfacicUnitPlot(SurfacicUnitPlot $surfacicUnitPlot, string $format = null, array $context = []): array
    {
        $res = [
            '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(),
            'latitude' => $surfacicUnitPlot->getLatitude(),
            'longitude' => $surfacicUnitPlot->getLongitude(),
            'height' => $surfacicUnitPlot->getHeight(),
            'treatment' => $this->iriConverter->getIriFromItem($surfacicUnitPlot->getTreatment()),
            'color' => $surfacicUnitPlot->getColor(),
            'comment' => $surfacicUnitPlot->getComment(),
        ];
        if ($this->exportNotes) {
            $res['notes'] = array_map(fn($note) => $this->iriConverter->getIriFromItem($note), $surfacicUnitPlot->getNotes()->toArray());
        }
        return $res;
    }

    private function normalizeIndividual(Individual $individual, string $format = null, array $context = []): array
    {
        $res = [
            '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(),
            'latitude' => $individual->getLatitude(),
            'longitude' => $individual->getLongitude(),
            'height' => $individual->getHeight(),
            'color' => $individual->getColor(),
            'comment' => $individual->getComment(),
        ];
        if ($this->exportNotes) {
            $res['notes'] = array_map(fn($note) => $this->iriConverter->getIriFromItem($note), $individual->getNotes()->toArray());
        }
        return $res;
    }

    private function normalizeOEZ(OutExperimentationZone $outExperimentationZone, string $format = null, array $context = []): array
    {
        return [
            'number' => $outExperimentationZone->getNumber(),
            'x' => $outExperimentationZone->getX(),
            'y' => $outExperimentationZone->getY(),
            'comment' => $outExperimentationZone->getComment(),
            'color' => $outExperimentationZone->getColor(),
            'nature' => [
                'nature' => $outExperimentationZone->getNature()->getNature(),
                'color' => $outExperimentationZone->getNature()->getColor(),
                'texture' => $outExperimentationZone->getNature()->getTexture(),
            ],
        ];
    }

    private function normalizeProtocol(Protocol $protocol, string $format = null, array $context = []): array
    {
        return [
            'name' => $protocol->getName(),
            'aim' => $protocol->getAim(),
            'comment' => $protocol->getComment(),
            'created' => $this->serializer->normalize($protocol->getCreated(), $format, $context),
            'factors' => array_map(fn($factor) => $this->serializer->normalize($factor, $format, $context), $protocol->getFactors()->toArray()),
            'treatments' => array_map(fn($treatment) => $this->serializer->normalize($treatment, $format, $context), $protocol->getTreatments()->toArray()),
            'owner' => $this->iriConverter->getIriFromItem($protocol->getOwner()),
        ];
    }

    private function normalizeFactor(Factor $factor, string $format = null, array $context = []): array
    {
        return [
            'name' => $factor->getName(),
            'modalities' => array_map(fn($modality) => $this->serializer->normalize($modality, $format, $context), $factor->getModalities()->toArray()),
            'order' => $factor->getOrder(),
        ];
    }

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

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

    public function hasCacheableSupportsMethod(): bool
    {
        return true;
    }
}
