<?php

namespace Webapp\Core\ApiOperation;

use ApiPlatform\Core\Api\IriConverterInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Security\Core\Security;
use Webapp\Core\Entity\Block;
use Webapp\Core\Entity\Experiment;
use Webapp\Core\Entity\Individual;
use Webapp\Core\Entity\OutExperimentationZone;
use Webapp\Core\Entity\Platform;
use Webapp\Core\Entity\SubBlock;
use Webapp\Core\Entity\SurfacicUnitPlot;
use Webapp\Core\Entity\UnitPlot;
use Webapp\Core\Enumeration\ExperimentStateEnum;
use Webapp\Core\Enumeration\PathLevelEnum;

/**
 * Class MultipleBusinessObjectAbstractOperation.
 */
abstract class MultipleBusinessObjectAbstractOperation
{
    protected Security $security;
    protected IriConverterInterface $iriConverter;
    protected EntityManagerInterface $entityManager;

    public function __construct(Security $security, IriConverterInterface $iriConverter, EntityManagerInterface $entityManager)
    {
        $this->security = $security;
        $this->iriConverter = $iriConverter;
        $this->entityManager = $entityManager;
    }

    abstract public function __invoke(Request $request, $data);

    protected function getObjectLeafs($objectIris, &$parent): array
    {
        /** @var array<SurfacicUnitPlot | Individual | OutExperimentationZone> $objectToMove iri => leaf object */
        $objectToMove = [];
        if (isset($objectIris[PathLevelEnum::OEZ])) {
            foreach ($objectIris[PathLevelEnum::OEZ] as $iri) {
                /** @var OutExperimentationZone $oez */
                $oez = $this->iriConverter->getItemFromIri($iri);
                if (!$this->checkOezParent($oez, $parent)) {
                    throw new BadRequestHttpException();
                }
                $objectToMove[$iri] = $oez;
            }
        }
        if (isset($objectIris[PathLevelEnum::INDIVIDUAL])) {
            foreach ($objectIris[PathLevelEnum::INDIVIDUAL] as $iri) {
                /** @var Individual $individual */
                $individual = $this->iriConverter->getItemFromIri($iri);
                if (!$this->checkIndividualParent($individual, $parent)) {
                    throw new BadRequestHttpException();
                }
                $objectToMove[$iri] = $individual;
            }
        }
        if (isset($objectIris[PathLevelEnum::SURFACIC_UNIT_PLOT])) {
            foreach ($objectIris[PathLevelEnum::SURFACIC_UNIT_PLOT] as $iri) {
                /** @var SurfacicUnitPlot $surfacicUnitPlot */
                $surfacicUnitPlot = $this->iriConverter->getItemFromIri($iri);
                if (!$this->checkSurfacicUnitPlotParent($surfacicUnitPlot, $parent)) {
                    throw new BadRequestHttpException();
                }
                $objectToMove[$iri] = $surfacicUnitPlot;
            }
        }
        if (isset($objectIris[PathLevelEnum::UNIT_PLOT])) {
            foreach ($objectIris[PathLevelEnum::UNIT_PLOT] as $iri) {
                /** @var UnitPlot $unitPlot */
                $unitPlot = $this->iriConverter->getItemFromIri($iri);
                if (!$this->checkUnitPlotParent($unitPlot, $parent)) {
                    throw new BadRequestHttpException();
                }
                $this->handleUnitPlot($unitPlot, $objectToMove);
            }
        }
        if (isset($objectIris[PathLevelEnum::SUB_BLOCK])) {
            foreach ($objectIris[PathLevelEnum::SUB_BLOCK] as $iri) {
                /** @var SubBlock $subBlock */
                $subBlock = $this->iriConverter->getItemFromIri($iri);
                if (!$this->checkSubBlockParent($subBlock, $parent)) {
                    throw new BadRequestHttpException();
                }
                $this->handleSubBlock($subBlock, $objectToMove);
            }
        }
        if (isset($objectIris[PathLevelEnum::BLOCK])) {
            foreach ($objectIris[PathLevelEnum::BLOCK] as $iri) {
                /** @var Block $block */
                $block = $this->iriConverter->getItemFromIri($iri);
                if (!$this->checkBlockParent($block, $parent)) {
                    throw new BadRequestHttpException();
                }
                $this->handleBlock($block, $objectToMove);
            }
        }
        if (isset($objectIris[PathLevelEnum::EXPERIMENT])) {
            foreach ($objectIris[PathLevelEnum::EXPERIMENT] as $iri) {
                /** @var Experiment $experiment */
                $experiment = $this->iriConverter->getItemFromIri($iri);
                if (!$this->checkExperimentParent($experiment, $parent)) {
                    throw new BadRequestHttpException();
                }
                $this->handleExperiment($experiment, $objectToMove);
            }
        }
        if (isset($objectIris[PathLevelEnum::PLATFORM])) {
            foreach ($objectIris[PathLevelEnum::PLATFORM] as $iri) {
                /** @var Platform $platform */
                $platform = $this->iriConverter->getItemFromIri($iri);
                foreach ($platform->getExperiments() as $experiment) {
                    if ($experiment->getState() > ExperimentStateEnum::CREATED) {
                        throw new BadRequestHttpException();
                    }
                }
                if (!$this->checkPlatformParent($platform, $parent)) {
                    throw new BadRequestHttpException();
                }
                $this->handlePlatform($platform, $objectToMove);
            }
        }
        // TODO Faire les tests de sécurité sur le parent

        return $objectToMove;
    }

    function handleOEZ(OutExperimentationZone $outExperimentationZone, &$map, $stroreWithIri = true, &$maxNumber = null)
    {
        $map[$stroreWithIri ? $this->iriConverter->getIriFromItem($outExperimentationZone) : $outExperimentationZone->getX() . "," . $outExperimentationZone->getY()] = $outExperimentationZone;
        if ($maxNumber !== null) {
            $maxNumber[PathLevelEnum::OEZ] = max($maxNumber[PathLevelEnum::OEZ], intval($outExperimentationZone->getNumber()));
        }
    }

    function handleIndividual(Individual $individual, &$map, $stroreWithIri = true, &$maxNumber = null)
    {
        $map[$stroreWithIri ? $this->iriConverter->getIriFromItem($individual) : $individual->getX() . "," . $individual->getY()] = $individual;
        if ($maxNumber !== null) {
            $maxNumber[PathLevelEnum::INDIVIDUAL] = max($maxNumber[PathLevelEnum::INDIVIDUAL], intval($individual->getNumber()));
        }
    }

    function handleSurfacicUnitPlot(SurfacicUnitPlot $surfacicUnitPlot, &$map, $stroreWithIri = true, &$maxNumber = null)
    {
        $map[$stroreWithIri ? $this->iriConverter->getIriFromItem($surfacicUnitPlot) : $surfacicUnitPlot->getX() . "," . $surfacicUnitPlot->getY()] = $surfacicUnitPlot;
        if ($maxNumber !== null) {
            $maxNumber[PathLevelEnum::SURFACIC_UNIT_PLOT] = max($maxNumber[PathLevelEnum::SURFACIC_UNIT_PLOT], intval($surfacicUnitPlot->getNumber()));
        }
    }

    function handleUnitPlot(UnitPlot $unitPlot, &$map, $stroreWithIri = true, &$maxNumber = null)
    {
        foreach ($unitPlot->getIndividuals() as $individual) {
            $this->handleIndividual($individual, $map, $stroreWithIri, $maxNumber);
        }
        foreach ($unitPlot->getOutExperimentationZones() as $outExperimentationZone) {
            $this->handleOEZ($outExperimentationZone, $map, $stroreWithIri, $maxNumber);
        }
        if ($maxNumber !== null) {
            $maxNumber[PathLevelEnum::UNIT_PLOT] = max($maxNumber[PathLevelEnum::UNIT_PLOT], intval($unitPlot->getNumber()));
        }
    }

    function handleSubBlock(SubBlock $subBlock, &$map, $stroreWithIri = true, &$maxNumber = null)
    {
        foreach ($subBlock->getSurfacicUnitPlots() as $surfacicUnitPlot) {
            $this->handleSurfacicUnitPlot($surfacicUnitPlot, $map, $stroreWithIri, $maxNumber);
        }
        foreach ($subBlock->getUnitPlots() as $unitPlot) {
            $this->handleUnitPlot($unitPlot, $map, $stroreWithIri, $maxNumber);
        }
        foreach ($subBlock->getOutExperimentationZones() as $outExperimentationZone) {
            $this->handleOEZ($outExperimentationZone, $map, $stroreWithIri, $maxNumber);
        }
        if ($maxNumber !== null) {
            $maxNumber[PathLevelEnum::SUB_BLOCK] = max($maxNumber[PathLevelEnum::SUB_BLOCK], intval($subBlock->getNumber()));
        }
    }

    function handleBlock(Block $block, &$map, $stroreWithIri = true, &$maxNumber = null)
    {
        foreach ($block->getSurfacicUnitPlots() as $surfacicUnitPlot) {
            $this->handleSurfacicUnitPlot($surfacicUnitPlot, $map, $stroreWithIri, $maxNumber);
        }
        foreach ($block->getUnitPlots() as $unitPlot) {
            $this->handleUnitPlot($unitPlot, $map, $stroreWithIri, $maxNumber);
        }
        foreach ($block->getSubBlocks() as $subBlock) {
            $this->handleSubBlock($subBlock, $map, $stroreWithIri, $maxNumber);
        }
        foreach ($block->getOutExperimentationZones() as $outExperimentationZone) {
            $this->handleOEZ($outExperimentationZone, $map, $stroreWithIri, $maxNumber);
        }
        if ($maxNumber !== null) {
            $maxNumber[PathLevelEnum::BLOCK] = max($maxNumber[PathLevelEnum::BLOCK], intval($block->getNumber()));
        }
    }

    function handleExperiment(Experiment $experiment, &$map, $stroreWithIri = true, &$maxNumber = null, &$individualUp = null)
    {
        foreach ($experiment->getBlocks() as $block) {
            $this->handleBlock($block, $map, $stroreWithIri, $maxNumber);
        }
        foreach ($experiment->getOutExperimentationZones() as $outExperimentationZone) {
            $this->handleOEZ($outExperimentationZone, $map, $stroreWithIri, $maxNumber);
        }
        $individualUp = $experiment->isIndividualUP();
    }

    function handlePlatform(Platform $platform, &$map, $stroreWithIri = true, &$maxNumber = null, &$individualUp = null)
    {
        foreach ($platform->getExperiments() as $experiment) {
            $this->handleExperiment($experiment, $map, $stroreWithIri, $maxNumber, $individualUp);
        }
    }

    function checkOezParent(OutExperimentationZone $oez, &$parent): bool
    {
        if ($oez->getExperiment() !== null) {
            return $this->checkExperimentParent($oez->getExperiment(), $parent);
        } else if ($oez->getBlock() !== null) {
            return $this->checkBlockParent($oez->getBlock(), $parent);
        } else if ($oez->getSubBlock() !== null) {
            return $this->checkSubBlockParent($oez->getSubBlock(), $parent);
        } else if ($oez->getUnitPlot() !== null) {
            return $this->checkUnitPlotParent($oez->getUnitPlot(), $parent);
        }
        return false;
    }

    function checkIndividualParent(Individual $individual, &$parent): bool
    {
        return $this->checkUnitPlotParent($individual->getUnitPlot(), $parent);
    }

    function checkSurfacicUnitPlotParent(SurfacicUnitPlot $surfacicUnitPlot, &$parent): bool
    {
        return $surfacicUnitPlot->getSubBlock() !== null ?
            $this->checkSubBlockParent($surfacicUnitPlot->getSubBlock(), $parent) :
            $this->checkBlockParent($surfacicUnitPlot->getBlock(), $parent);

    }

    function checkUnitPlotParent(UnitPlot $unitPlot, &$parent): bool
    {
        return $unitPlot->getSubBlock() !== null ?
            $this->checkSubBlockParent($unitPlot->getSubBlock(), $parent) :
            $this->checkBlockParent($unitPlot->getBlock(), $parent);
    }

    function checkSubBlockParent(SubBlock $subBlock, &$parent): bool
    {
        return $this->checkBlockParent($subBlock->getBlock(), $parent);
    }

    function checkBlockParent(Block $block, &$parent): bool
    {
        return $this->checkExperimentParent($block->getExperiment(), $parent);
    }

    function checkExperimentParent(Experiment $experiment, &$parent): bool
    {
        if ($experiment->getState() > ExperimentStateEnum::CREATED) {
            throw new BadRequestHttpException("Locked experiment");
        }
        if ($experiment->getPlatform() === null) {
            if ($parent === null) {
                $parent = $experiment;
            }
            return $parent === $experiment;
        } else {
            return $this->checkPlatformParent($experiment->getPlatform(), $parent);
        }
    }

    function checkPlatformParent(Platform $platform, &$parent): bool
    {
        if ($parent === null) {
            $parent = $platform;
        }
        return $parent === $platform;
    }
}
