<?php

/*
 * @author TRYDEA - 2024
 */

namespace Webapp\FileManagement\Service;

use ApiPlatform\Core\Api\IriConverterInterface;
use Brick\Geo\Exception\GeometryIOException;
use Doctrine\ORM\EntityManagerInterface;
use Shared\FileManagement\Entity\UserLinkedJob;
use Vich\UploaderBundle\Storage\StorageInterface;
use Webapp\Core\Entity\AbstractVariable;
use Webapp\Core\Entity\Block;
use Webapp\Core\Entity\BusinessObject;
use Webapp\Core\Entity\Device;
use Webapp\Core\Entity\Experiment;
use Webapp\Core\Entity\Factor;
use Webapp\Core\Entity\FieldMeasure;
use Webapp\Core\Entity\Individual;
use Webapp\Core\Entity\Measure;
use Webapp\Core\Entity\Modality;
use Webapp\Core\Entity\OezNature;
use Webapp\Core\Entity\OutExperimentationZone;
use Webapp\Core\Entity\PathBase;
use Webapp\Core\Entity\Project;
use Webapp\Core\Entity\ProjectData;
use Webapp\Core\Entity\Protocol;
use Webapp\Core\Entity\SemiAutomaticVariable;
use Webapp\Core\Entity\Session;
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;
use Webapp\Core\Enumeration\ExperimentStateEnum;
use Webapp\Core\Enumeration\PathLevelEnum;
use Webapp\Core\Service\CloneService;
use Webapp\FileManagement\Entity\ParsingJob;
use Webapp\FileManagement\Entity\RequestFile;
use Webapp\FileManagement\Exception\MultipleParsingException;
use Webapp\FileManagement\Exception\ParsingException;

class WebappReaderService extends AbstractReaderService
{
    private IriConverterInterface $iriConverter;

    private CloneService $cloneService;

    private WebappXmlReaderService $webappXmlReaderService;

    public function __construct(EntityManagerInterface $entityManager,
        IriConverterInterface $iriConverter,
        DirectoryNamer $directoryNameManager,
        StorageInterface $storage,
        CloneService $cloneService,
        string $tmpDir,
        WebappXmlReaderService $webappXmlReaderService
    ) {
        parent::__construct($entityManager, $directoryNameManager, $storage, $tmpDir);
        $this->iriConverter = $iriConverter;
        $this->cloneService = $cloneService;
        $this->webappXmlReaderService = $webappXmlReaderService;
    }

    /**
     * @throws ParsingException
     */
    public function readPlatform(int $jobId): void
    {
        [$parsingJob, $xml] = $this->initParsing($jobId);
        $platformDto = $this->webappXmlReaderService->readPlatform($xml, $parsingJob->getSite(), $parsingJob->getUser(), $this->getTmpDir($parsingJob));

        foreach ($platformDto->oezNatures as $oezNature) {
            $this->entityManager->persist($oezNature);
        }
        $this->entityManager->persist($platformDto->platform);

        foreach ($platformDto->platform->getProjects() as $project) {
            foreach ($project->getProjectDatas() as $projectData) {
                if ($projectData->getSessions()->filter(fn (Session $session) => null !== $session->getComment())->count() > 0) {
                    $parsingJob->setErrors([[RequestFile::WARNING_UNKNOWN_SESSION_USER]]);
                    break 2;
                }
            }
        }

        $this->cleanTmpDir($parsingJob);

        $parsingJob->setObjectIri($this->iriConverter->getIriFromItem($platformDto->platform));
        $parsingJob->setStatus(UserLinkedJob::STATUS_SUCCESS);
        $this->entityManager->flush();
    }

    /**
     * @throws ParsingException
     */
    public function readExperiment(int $jobId): void
    {
        [$parsingJob, $xml] = $this->initParsing($jobId);
        $experimentDto = $this->webappXmlReaderService->readExperiment($xml, $parsingJob->getSite(), $parsingJob->getUser(), $this->getTmpDir($parsingJob));

        $experiment = $experimentDto->experiment;
        if (null === $experiment) {
            throw new ParsingException('No experiments found in the xml file');
        }
        if ($parsingJob->getSite()->getExperiments()->exists(fn ($key, Experiment $existingExperiment) => $experiment->getName() === $existingExperiment->getName())) {
            throw new ParsingException('The name is already in use', RequestFile::ERROR_NAME_ALREADY_IN_USE);
        }

        $experiment->setState(ExperimentStateEnum::CREATED);

        foreach ($experimentDto->oezNatures as $oezNature) {
            $this->entityManager->persist($oezNature);
        }
        $this->entityManager->persist($experiment);

        $this->cleanTmpDir($parsingJob);

        $parsingJob->setObjectIri($this->iriConverter->getIriFromItem($experiment));
        $parsingJob->setStatus(UserLinkedJob::STATUS_SUCCESS);
        $this->entityManager->flush();
    }

    /**
     * @throws ParsingException
     */
    public function readVariableCollection(int $jobId): void
    {
        [$parsingJob, $xml] = $this->initParsing($jobId);
        $variableCollectionDto = $this->webappXmlReaderService->readVariableCollection($xml, $parsingJob->getSite(), $parsingJob->getUser(), $this->getTmpDir($parsingJob));

        foreach ($variableCollectionDto->materiels as $materiel) {
            if ($parsingJob->getSite()->getDevices()->exists(fn (int $key, Device $existingDevice) => $existingDevice->getName() === $materiel->getName())) {
                throw new ParsingException('', RequestFile::ERROR_NAME_ALREADY_IN_USE);
            }
        }
        foreach ($variableCollectionDto->variables as $variable) {
            if (!$variable instanceof SemiAutomaticVariable && 0 !== \count(array_filter($parsingJob->getSite()->getVariables(), fn (AbstractVariable $existingVariable) => $existingVariable->getName() === $variable->getName()))) {
                throw new ParsingException('', RequestFile::ERROR_NAME_ALREADY_IN_USE);
            }
        }

        foreach ($variableCollectionDto->materiels as $materiel) {
            $parsingJob->getSite()->addDevice($materiel);
        }
        foreach ($variableCollectionDto->variables as $variable) {
            if (!$variable instanceof SemiAutomaticVariable) {
                // Semi automatic variables are added with device.
                $parsingJob->getSite()->addVariable($variable);
            }
        }

        $this->cleanTmpDir($parsingJob);

        $parsingJob->setStatus(UserLinkedJob::STATUS_SUCCESS);
        $this->entityManager->flush();
    }

    /**
     * @return array{ParsingJob, string}
     *
     * @throws ParsingException
     */
    private function initParsing(int $id): array
    {
        $parsingJob = $this->entityManager->getRepository(ParsingJob::class)->find($id);
        $parsingJob->setStatus(UserLinkedJob::STATUS_RUNNING);
        $this->entityManager->flush();

        $this->copyFileInTmpDir($parsingJob);

        $xml = $this->getSimpleXMLElement($parsingJob);

        return [$parsingJob, $xml->asXML()];
    }

    /**
     * @throws ParsingException
     * @throws MultipleParsingException
     */
    public function readExperimentCsv(int $jobId)
    {
        /** @var $parsingJob ParsingJob */
        $parsingJob = $this->entityManager->getRepository(ParsingJob::class)->find($jobId);
        $this->copyFileInTmpDir($parsingJob);
        $filePath = $this->getTmpDir($parsingJob).$parsingJob->getFilePath();
        if (!file_exists($filePath)) {
            throw new ParsingException('File not found');
        }

        $bindings = json_decode($parsingJob->getCsvBindings());

        // Open the CSV file
        $handle = fopen($filePath, 'r');

        // Get bindings from the 1st line
        $data = fgetcsv($handle, 0, $bindings->separator);

        $openSilexInstance = (bool) $bindings->openSilexInstanceIri ? $this->iriConverter->getItemFromIri($bindings->openSilexInstanceIri) : null;

        $factorNames = [];
        // Iterate on each binding parameter
        foreach ($bindings as $key => $value) {
            if ('' !== $value) {
                switch ($key) {
                    case 'factors':
                        foreach ($value as $factorNumber => $factor) {
                            foreach ($factor as $factorKey => $value) {
                                $bindings->$key[$factorNumber]->$factorKey = array_search($value, $data, true);
                                $factorNames[$bindings->$key[$factorNumber]->$factorKey] = $value;
                            }
                        }
                        break;
                    case 'individualUp' :
                    case 'separator' :
                        break;
                    default:
                        $bindings->$key = array_search($value, $data, true);
                }
            } else {
                $bindings->$key = false;
            }
        }
        if (0 === \count($factorNames)) {
            throw new ParsingException('Missing factor');
        }

        $experiments = [];
        $OezNatures = [];
        $lineNumber = 1;
        $errors = [];
        $positionMap = [];
        $outExperimentationZoneCount = 0;
        $leafIdentifiers = [];
        // Iterate on each line of the CSV
        while (($data = fgetcsv($handle, 0, $bindings->separator)) !== false) {
            // retrait des caractères non compatibles UTF8
            $data = array_map(fn (string $value) => iconv('UTF-8', 'UTF-8//IGNORE', $value), $data);

            ++$lineNumber;
            $errors[$lineNumber] = [];
            if (false === $bindings->outExperimentationZone || '' === $data[$bindings->outExperimentationZone]) {
                // Manage mandatory params. for normacases
                foreach ($bindings as $key => $value) {
                    switch ($key) {
                        case 'factors':
                            foreach ($value as $factor) {
                                if (!isset($data[$factor->name]) || '' === $data[$factor->name]) {
                                    $errors[$lineNumber][] = RequestFile::ERROR_CSV_MISSING_FACTOR_NAME;
                                }
                            }
                            break;
                        case 'x':
                            $switchError = RequestFile::ERROR_CSV_MISSING_X;
                            break;
                        case 'y':
                            $switchError = RequestFile::ERROR_CSV_MISSING_Y;
                            break;
                        case 'block':
                            $switchError = RequestFile::ERROR_CSV_MISSING_BLOCK_NUMBER;
                            break;
                        case 'unitPlot':
                            $switchError = RequestFile::ERROR_CSV_MISSING_UNIT_PARCEL_NAME;
                            break;
                        case 'experiment':
                            $switchError = RequestFile::ERROR_CSV_MISSING_DEVICE_NAME;
                            break;
                        case 'treatment':
                            $switchError = RequestFile::ERROR_CSV_MISSING_TREATMENT;
                            break;
                    }
                    if (isset($switchError) && (!isset($data[$bindings->$key]) || '' === $data[$bindings->$key])) {
                        $errors[$lineNumber][] = $switchError;
                    }
                    unset($switchError);
                }
            } else {
                // Manage mandatory params. for ZHE cases
                foreach ($bindings as $key => $value) {
                    switch ($key) {
                        case 'x':
                            $switchError = RequestFile::ERROR_CSV_MISSING_X;
                            break;
                        case 'y':
                            $switchError = RequestFile::ERROR_CSV_MISSING_Y;
                            break;
                        case 'experiment':
                            $switchError = RequestFile::ERROR_CSV_MISSING_DEVICE_NAME;
                            break;
                    }
                    if (isset($switchError) && (!isset($data[$bindings->$key]) || '' === $data[$bindings->$key])) {
                        $errors[$lineNumber][] = $switchError;
                    }
                    unset($switchError);
                }
            }

            if (!empty($errors[$lineNumber])) {
                continue;
            }
            if (!is_numeric($data[$bindings->x]) || !is_numeric($data[$bindings->y])) {
                $errors[$lineNumber][] = RequestFile::ERROR_CSV_NON_NUMERIC_POSITION;
                continue;
            }

            // Manage if the given position is already used
            if (isset($positionMap[$data[$bindings->x]])) {
                if (isset($positionMap[$data[$bindings->x]][$data[$bindings->y]])) {
                    $errors[$lineNumber][] = RequestFile::ERROR_CSV_ALREADY_USED_POSITION;
                } else {
                    $positionMap[$data[$bindings->x]][$data[$bindings->y]] = true;
                }
            } else {
                $positionMap[$data[$bindings->x]] = [$data[$bindings->y] => true];
            }

            if (isset($experiments[$data[$bindings->experiment]])) {
                $experiment = $experiments[$data[$bindings->experiment]];
            } else {
                $protocol = (new Protocol())
                    ->setName(\sprintf('%s%s', Protocol::DEFAULT_PREFIX_FROM_CSV, $data[$bindings->experiment]))
                    ->setAlgorithm(null)
                    ->setOwner($parsingJob->getUser())
                    ->setAim(Protocol::DEFAULT_AIM_FROM_CSV);

                try {
                    $experiment = (new Experiment())
                        ->setOwner($parsingJob->getUser())
                        ->setSite($parsingJob->getSite())
                        ->setName($data[$bindings->experiment])
                        ->setGeometry(isset($bindings->experimentGeometry) && false !== $bindings->experimentGeometry ? $data[$bindings->experimentGeometry] : null)
                        ->setIndividualUP($bindings->individualUp)
                        ->setProtocol($protocol);
                } catch (GeometryIOException $e) {
                    $errors[$lineNumber][] = RequestFile::ERROR_CSV_EXPERIMENT_WKT_MALFORMED;
                    continue;
                }

                $this->entityManager->persist($experiment);
                $experiments[$data[$bindings->experiment]] = $experiment;
            }
            if (false !== $bindings->outExperimentationZone && '' !== $data[$bindings->outExperimentationZone]) {
                if (isset($OezNatures[$data[$bindings->outExperimentationZone]])) {
                    $oezNature = $OezNatures[$data[$bindings->outExperimentationZone]];
                } else {
                    $existingOEZ = array_values(array_filter(
                        $parsingJob->getSite()->getOezNatures()->toArray(),
                        fn (OezNature $existing) => $existing->getNature() === $data[$bindings->outExperimentationZone]
                    ));
                    $oezNature = $existingOEZ[0] ?? (new OezNature())
                        ->setColor(190000)
                        ->setSite($parsingJob->getSite())
                        ->setNature($data[$bindings->outExperimentationZone]);
                    $OezNatures[$data[$bindings->outExperimentationZone]] = $oezNature;
                    $this->entityManager->persist($oezNature);
                }
                try {
                    $outExperimentationZone = (new OutExperimentationZone())
                        ->setNumber($outExperimentationZoneCount)
                        ->setX($data[$bindings->x])
                        ->setY($data[$bindings->y])
                        ->setGeometry(isset($bindings->outExperimentationZoneGeometry) && false !== $bindings->outExperimentationZoneGeometry ? $data[$bindings->outExperimentationZoneGeometry] : null)
                        ->setNature($oezNature);
                } catch (GeometryIOException $e) {
                    $errors[$lineNumber][] = RequestFile::ERROR_CSV_OEZ_WKT_MALFORMED;
                    continue;
                }

                // TODO Allow to put OEZ in blocks, up ...
                $experiment->addOutExperimentationZone($outExperimentationZone);
                ++$outExperimentationZoneCount;
                continue;
            }

            $protocol = $experiment->getProtocol();

            $modalities = [];
            $factorCount = 1;
            foreach ($bindings->factors as $factorBinding) {
                // foreach factor, we find the corresponding factor if exist.
                $factorName = $factorNames[$factorBinding->name] ?? $factorCount;
                $factor = $protocol->getFactors()->filter(fn (Factor $factor) => $factor->getName() === "$factorName")->first();
                if (!$factor) {
                    $factor = (new Factor())
                        ->setName($factorName)
                        ->setOrder($factorCount - 1)
                        // TODO changer quand on pourra importer des facteurs de openSilex
                        ->setGermplasm(null !== $openSilexInstance && false !== $factorBinding->openSilexUri);
                    $protocol->addFactors($factor);
                }

                // the factor name in the bindings corresponds to the modality value used for the line for the current factor.
                $modality = $factor->getModalities()
                    ->filter(fn (Modality $modality) => $modality->getValue() === $data[$factorBinding->name])
                    ->first();
                if (!$modality) {
                    if (false !== $factorBinding->openSilexUri && '' === $data[$factorBinding->openSilexUri]) {
                        $errors[$lineNumber][] = RequestFile::ERROR_CSV_MISSING_OPENSILEX_URI;
                    }
                    $modality = (new Modality())
                        ->setValue($data[$factorBinding->name])
                        ->setShortName(false !== $factorBinding->shortName ? $data[$factorBinding->shortName] : null)
                        ->setOpenSilexInstance($openSilexInstance)
                        ->setOpenSilexUri(false !== $factorBinding->openSilexUri ? $data[$factorBinding->openSilexUri] : null)
                        ->setIdentifier(false !== $factorBinding->id ? $data[$factorBinding->id] : null);
                    $factor->addModality($modality);
                } elseif (false !== $factorBinding->openSilexUri && $modality->getOpenSilexUri() !== $data[$factorBinding->openSilexUri]) {
                    $errors[$lineNumber][] = RequestFile::ERROR_CSV_DIFFERENT_URI_FOR_SAME_MODALITY;
                }
                $modalities[] = $modality;
                ++$factorCount;
            }

            $treatment = $protocol->getTreatments()
                ->filter(fn (Treatment $treatment) => $treatment->getName() === $data[$bindings->treatment])
                ->first();

            if (!$treatment) {
                $treatment = (new Treatment())
                    ->setName($data[$bindings->treatment])
                    ->setShortName(false !== $bindings->shortTreatment ? $data[$bindings->shortTreatment] : $protocol->getTreatments()->count() + 1)
                    ->setRepetitions(1);

                foreach ($modalities as $modality) {
                    $treatment->addModalities($modality);
                }

                $treatmentWithSameModalities = $protocol->getTreatments()->exists(function (int $key, Treatment $otherTreatment) use ($modalities) {
                    $modalityDiff = fn (Modality $m1, Modality $m2): int => $m1 === $m2 ? 0 : 1;

                    return 0 === \count(array_udiff($modalities, $otherTreatment->getModalities()->getValues(), $modalityDiff))
                        && 0 === \count(array_udiff($otherTreatment->getModalities()->getValues(), $modalities, $modalityDiff));
                });
                if ($treatmentWithSameModalities) {
                    $errors[$lineNumber][] = RequestFile::ERROR_CSV_DIFFERENTS_TREATMENTS_WITH_SAME_MODALITIES;
                }
                $protocol->addTreatments($treatment);
            } elseif ($treatment->getModalities()->count() !== \count($modalities)
                || 0 !== \count(array_udiff($treatment->getModalities()->toArray(), $modalities, fn (Modality $m1, Modality $m2) => $m1 === $m2 ? 0 : 1))) {
                $errors[$lineNumber][] = RequestFile::ERROR_CSV_TREATMENT_MULTIPLE_FACTOR_COMBINATIONS;
            }

            $block = $experiment->getBlocks()->filter(fn (Block $b) => $b->getNumber() === $data[$bindings->block])->first();
            if (!$block) {
                try {
                    $block = (new Block())
                        ->setGeometry(isset($bindings->blockGeometry) && false !== $bindings->blockGeometry ? $data[$bindings->blockGeometry] : null)
                        ->setNumber($data[$bindings->block]);
                } catch (GeometryIOException $e) {
                    $errors[$lineNumber][] = RequestFile::ERROR_CSV_BLOCK_WKT_MALFORMED;
                    continue;
                }
                $experiment->addBlocks($block);
            }

            $unitPlotSource = $block;
            $subBlock = null;
            if (false !== $bindings->subBlock && '' !== $data[$bindings->subBlock]) {
                $subBlock = $block->getSubBlocks()->filter(fn (SubBlock $sb) => $sb->getNumber() === $data[$bindings->subBlock])->first();
                if (!$subBlock) {
                    try {
                        $subBlock = (new SubBlock())
                            ->setGeometry(isset($bindings->blockGeometry) && false !== $bindings->blockGeometry ? $data[$bindings->blockGeometry] : null)
                            ->setNumber($data[$bindings->subBlock]);
                    } catch (GeometryIOException $e) {
                        $errors[$lineNumber][] = RequestFile::ERROR_CSV_SUBBLOCK_WKT_MALFORMED;
                        continue;
                    }
                    $block->addSubBlocks($subBlock);
                }
                $unitPlotSource = $subBlock;
            }

            if (false !== $bindings->id) {
                if (\in_array($data[$bindings->id], $leafIdentifiers, true)) {
                    $errors[$lineNumber][] = RequestFile::ERROR_CSV_INDIVIDUAL_ID_EXISTS;
                }
                $leafIdentifiers[] = $data[$bindings->id];
            }
            if ($bindings->individualUp) {
                /** @var UnitPlot $unitPlot */
                $unitPlot = $unitPlotSource->getUnitPlots()
                    ->filter(fn (UnitPlot $up) => $up->getNumber() === $data[$bindings->unitPlot])
                    ->first();
                if (!$unitPlot) {
                    try {
                        $unitPlot = (new UnitPlot())
                            ->setGeometry(isset($bindings->unitPlotGeometry) && false !== $bindings->unitPlotGeometry ? $data[$bindings->unitPlotGeometry] : null)
                            ->setNumber($data[$bindings->unitPlot])
                            ->setTreatment($treatment);
                    } catch (GeometryIOException $e) {
                        $errors[$lineNumber][] = RequestFile::ERROR_CSV_UP_WKT_MALFORMED;
                        continue;
                    }
                    if (false !== $bindings->subBlock && '' !== $data[$bindings->subBlock]) {
                        $subBlock->addUnitPlots($unitPlot);
                    } else {
                        $block->addUnitPlots($unitPlot);
                    }
                } elseif ($unitPlot->getTreatment() !== $treatment) {
                    $errors[$lineNumber][] = RequestFile::ERROR_CSV_UNIT_PARCEL_MULTIPLE_TREATMENT;
                }
                try {
                    $individual = (new Individual())
                        ->setGeometry(isset($bindings->individualGeometry) && false !== $bindings->individualGeometry ? $data[$bindings->individualGeometry] : null)
                        ->setAppeared(new \DateTime())
                        ->setX($data[$bindings->x])
                        ->setY($data[$bindings->y])
                        ->setNumber(($lineNumber - 1).'')
                        ->setIdentifier(false === $bindings->id ? null : $data[$bindings->id]);
                } catch (GeometryIOException $e) {
                    $errors[$lineNumber][] = RequestFile::ERROR_CSV_INDIVIDUAL_WKT_MALFORMED;
                    continue;
                }
                $unitPlot->addIndividual($individual);
            } else {
                $unitPlot = $unitPlotSource->getSurfacicUnitPlots()
                    ->filter(fn (SurfacicUnitPlot $up) => $up->getNumber() === $data[$bindings->unitPlot])
                    ->first();
                if (!$unitPlot) {
                    try {
                        $unitPlot = (new SurfacicUnitPlot())
                            ->setGeometry((isset($bindings->unitPlotGeometry) && false !== $bindings->unitPlotGeometry) ? $data[$bindings->unitPlotGeometry] : null)
                            ->setX($data[$bindings->x])
                            ->setY($data[$bindings->y])
                            ->setNumber($data[$bindings->unitPlot])
                            ->setTreatment($treatment)
                            ->setIdentifier(false === $bindings->id ? null : $data[$bindings->id]);
                    } catch (GeometryIOException $e) {
                        $errors[$lineNumber][] = RequestFile::ERROR_CSV_UP_WKT_MALFORMED;
                        continue;
                    }
                    if (false !== $bindings->subBlock && '' !== $data[$bindings->subBlock]) {
                        $subBlock->addSurfacicUnitPlots($unitPlot);
                    } else {
                        $block->addSurfacicUnitPlots($unitPlot);
                    }
                } elseif ($unitPlot->getTreatment() !== $treatment) {
                    $errors[$lineNumber][] = RequestFile::ERROR_CSV_UNIT_PARCEL_MULTIPLE_TREATMENT;
                }
            }

            $hasGermplasm = false;
            foreach ($experiment->getProtocol()->getFactors() as $factor) {
                if ($factor->isGermplasm()) {
                    if ($hasGermplasm) {
                        $errors[$lineNumber][] = RequestFile::ERROR_CSV_ONLY_ONE_GERMPLASM;
                    }
                    $hasGermplasm = true;
                }
            }
        }

        foreach ($errors as $errorOnLine) {
            if (!empty($errorOnLine)) {
                throw new MultipleParsingException($errors);
            }
        }

        $this->cleanTmpDir($parsingJob);
        $parsingJob->setObjectIri($this->iriConverter->getIriFromItem($experiment));
        $parsingJob->setStatus(UserLinkedJob::STATUS_SUCCESS);
        $this->entityManager->flush();
    }

    /**
     * @throws ParsingException
     * @throws MultipleParsingException
     */
    public function readProtocolCsv(int $jobId)
    {
        /** @var $parsingJob ParsingJob */
        $parsingJob = $this->entityManager->getRepository(ParsingJob::class)->find($jobId);
        $this->copyFileInTmpDir($parsingJob);
        $filePath = $this->getTmpDir($parsingJob).$parsingJob->getFilePath();
        if (!file_exists($filePath)) {
            throw new ParsingException('File not found');
        }

        $bindings = json_decode($parsingJob->getCsvBindings());

        // Open the CSV file
        $handle = fopen($filePath, 'r');

        // Get bindings from the 1st line
        $data = fgetcsv($handle, 0, $bindings->separator);

        $factorNames = [];
        // Iterate on each binding parameter
        foreach ($bindings as $key => $value) {
            switch ($key) {
                case 'factors':
                    foreach ($value as $factorNumber => $factor) {
                        foreach ($factor as $factorKey => $value) {
                            $bindings->$key[$factorNumber]->$factorKey = array_search($value, $data, true);
                            if ('name' === $factorKey) {
                                $factorNames[$bindings->$key[$factorNumber]->$factorKey] = $value;
                            }
                        }
                    }
                    break;
                case 'separator' :
                case 'protocolName' :
                case 'aim' :
                case 'comment' :
                case 'algorithm' :
                    break;
                default:
                    $bindings->$key = array_search($value, $data, true);
            }
        }

        $protocol = (new Protocol())
            ->setOwner($parsingJob->getUser())
            ->setSite($parsingJob->getSite())
            ->setName($bindings->protocolName)
            ->setAim($bindings->aim)
            ->setComment($bindings->comment)
            ->setAlgorithm(null === $bindings->algorithm ? null : $this->iriConverter->getItemFromIri($bindings->algorithm));

        $this->entityManager->persist($protocol);

        $lineNumber = 0;
        $errors = [];
        // Iterate on each line of the CSV
        while (($data = fgetcsv($handle, 0, $bindings->separator)) !== false) {
            // retrait des caractères non compatibles UTF8
            $utf8conv = function ($value) {
                return iconv('UTF-8', 'UTF-8//IGNORE', $value);
            };
            $data = array_map($utf8conv, $data);
            ++$lineNumber;
            $errors[$lineNumber] = [];

            // Manage mandatory params. for normacases
            foreach ($bindings as $key => $value) {
                switch ($key) {
                    case 'factors':
                        foreach ($value as $factor) {
                            if (!isset($data[$factor->name]) || '' === $data[$factor->name]) {
                                $errors[$lineNumber][] = RequestFile::ERROR_CSV_MISSING_FACTOR_NAME;
                            }
                        }
                        break;
                    case 'treatment':
                        $switchError = RequestFile::ERROR_CSV_MISSING_TREATMENT;
                        break;
                }
                if (isset($switchError) && (!isset($data[$bindings->$key]) || '' === $data[$bindings->$key])) {
                    $errors[$lineNumber][] = $switchError;
                }
                unset($switchError);
            }

            if (!empty($errors[$lineNumber])) {
                continue;
            }

            $modalities = [];
            $factorCount = 0;
            foreach ($bindings->factors as $factorBinding) {
                // foreach factor, we find the corresponding factor if exist.
                $factorName = $factorNames[$factorBinding->name];
                $factor = $protocol->getFactors()->filter(function (Factor $factor) use ($factorName) {
                    return $factor->getName() === "$factorName";
                })->first();
                if (!$factor) {
                    $factor = (new Factor())->setName($factorName)->setOrder($factorCount);
                    $protocol->addFactors($factor);
                }

                // the factor name in the bindings corresponds to the modality value used for the line for the current factor.
                $modality = $factor->getModalities()->filter(function (Modality $modality) use ($data, $factorBinding) {
                    return $modality->getValue() === $data[$factorBinding->name];
                })->first();
                if (!$modality) {
                    $modality = (new Modality())
                        ->setValue($data[$factorBinding->name])
                        ->setShortName(false !== $factorBinding->shortName ? $data[$factorBinding->shortName] : null)
                        ->setIdentifier(false !== $factorBinding->id ? $data[$factorBinding->id] : null);
                    $factor->addModality($modality);
                }
                $modalities[] = $modality;
                ++$factorCount;
            }

            $treatment = $protocol->getTreatments()->filter(function (Treatment $treatment) use ($bindings, $data) {
                return $treatment->getName() === $data[$bindings->treatment];
            })->first();

            if (!$treatment) {
                $treatment = (new Treatment())
                    ->setName($data[$bindings->treatment])
                    ->setShortName(false !== $bindings->shortTreatment ? $data[$bindings->shortTreatment] : $protocol->getTreatments()->count() + 1)
                    ->setRepetitions($data[$bindings->repetitions] ?? 1);

                foreach ($modalities as $modality) {
                    $treatment->addModalities($modality);
                }
                $protocol->addTreatments($treatment);
            } elseif ($treatment->getModalities()->count() !== \count($modalities) || 0 !== \count(array_udiff($treatment->getModalities()->toArray(), $modalities, function (Modality $m1, Modality $m2) {
                return $m1 === $m2 ? 0 : 1;
            }))) {
                $errors[$lineNumber][] = RequestFile::ERROR_CSV_TREATMENT_MULTIPLE_FACTOR_COMBINATIONS;
            }
        }

        foreach ($errors as $errorOnLine) {
            if (!empty($errorOnLine)) {
                throw new MultipleParsingException($errors);
            }
        }

        $this->cleanTmpDir($parsingJob);
        $parsingJob->setObjectIri($this->iriConverter->getIriFromItem($protocol));
        $parsingJob->setStatus(UserLinkedJob::STATUS_SUCCESS);
        $this->entityManager->flush();
    }

    /**
     * @throws ParsingException
     * @throws MultipleParsingException
     */
    public function readDataEntryCsv(int $jobId)
    {
        /** @var $parsingJob ParsingJob */
        $parsingJob = $this->entityManager->getRepository(ParsingJob::class)->find($jobId);
        $this->copyFileInTmpDir($parsingJob);
        $filePath = $this->getTmpDir($parsingJob).$parsingJob->getFilePath();
        if (!file_exists($filePath)) {
            throw new ParsingException('File not found');
        }

        $bindings = json_decode($parsingJob->getCsvBindings());

        // Open the CSV file
        $handle = fopen($filePath, 'r');

        // Get bindings from the 1st line
        $data = fgetcsv($handle, 0, $bindings->separator);

        $variables = [];

        /** @var Project $project */
        $project = $this->iriConverter->getItemFromIri($bindings->projectIri);
        $experiments = [];
        foreach ($project->getExperiments() as $experiment) {
            $experiments[$experiment->getName()] = $experiment;
        }
        $projectData = (new ProjectData())
            ->setComment('Saisie importée')
            ->setUser($parsingJob->getUser())
            ->setName($project->getName());
        $session = (new Session())
            ->setUser($parsingJob->getUser())
            ->setStartedAt(new \DateTime())
            ->setEndedAt(new \DateTime());
        $projectData->addSession($session);
        $missingDataStateCode = current(array_filter($project->getStateCodes()->toArray(),
            fn (StateCode $stateCode) => $stateCode->isPermanent() && 'Donnée Manquante' === $stateCode->getTitle()));
        $deadStateCode = current(array_filter($project->getStateCodes()->toArray(),
            fn (StateCode $stateCode) => $stateCode->isPermanent() && 'Mort' === $stateCode->getTitle()));

        // Iterate on each binding parameter
        foreach ($bindings as $key => $value) {
            if ('' !== $value) {
                switch ($key) {
                    case 'variables':
                        foreach ($value as $variableIndex => $variable) {
                            $variableObject = $this->iriConverter->getItemFromIri($variable->iri);
                            $variables[$variable->iri] = $this->cloneService->cloneSimpleVariable($variableObject);
                            $projectData->addSimpleVariable($variables[$variable->iri]);
                            $bindings->$key[$variableIndex]->value = array_search($variable->value, $data, true);
                            $bindings->$key[$variableIndex]->timestamp = array_search($variable->timestamp, $data, true);
                        }
                        break;
                    case 'separator' :
                    case 'replaceMissingValue' :
                    case 'integrateDeadAndReplanted' :
                    case 'projectIri' :
                        break;
                    default:
                        $bindings->$key = array_search($value, $data, true);
                }
            } else {
                $bindings->$key = false;
            }
        }

        $lineNumber = 1;
        $errors = [];
        // Iterate on each line of the CSV
        while (($data = fgetcsv($handle, 0, $bindings->separator)) !== false) {
            // retrait des caractères non compatibles UTF8
            $utf8conv = function ($value) {
                return iconv('UTF-8', 'UTF-8//IGNORE', $value);
            };
            $data = array_map($utf8conv, $data);

            ++$lineNumber;
            $errors[$lineNumber] = [];
            // Manage mandatory params. for normacases
            foreach ($bindings as $key => $value) {
                switch ($key) {
                    case 'variables':
                        foreach ($value as $variable) {
                            if (!isset($data[$variable->value]) || '' === $data[$variable->value] && !$bindings->replaceMissingValue) {
                                $errors[$lineNumber][] = RequestFile::ERROR_CSV_EMPTY_VARIABLE_VALUE;
                            }
                        }
                        break;
                }
                /*if (isset($switchError) && (!isset($data[$bindings->$key]) || $data[$bindings->$key] === "")) {
                    $errors[$lineNumber][] = $switchError;
                }
                unset($switchError);*/
            }

            if (!empty($errors[$lineNumber])) {
                continue;
            }

            $experiment = $experiments[$data[$bindings->experiment]];
            /** @var Block $block */
            $block = (bool) $experiment ?
                current(array_filter($experiment->getBlocks()->toArray(), fn (Block $block) => $block->getNumber() === $data[$bindings->block])) :
                false;
            /** @var SubBlock $subBlock */
            $subBlock = (bool) $block ?
                current(array_filter($block->getSubBlocks()->toArray(), fn (SubBlock $subBlock) => $subBlock->getNumber() === $data[$bindings->subBlock])) :
                false;
            /** @var UnitPlot $unitPlot */
            $unitPlot = (bool) $subBlock ?
                current(array_filter($subBlock->getUnitPlots()->toArray(), fn (UnitPlot $unitPlot) => $unitPlot->getNumber() === $data[$bindings->unitPlot])) :
                ((bool) $block ?
                    current(array_filter($block->getUnitPlots()->toArray(), fn (UnitPlot $unitPlot) => $unitPlot->getNumber() === $data[$bindings->unitPlot])) :
                    false);
            /** @var SurfacicUnitPlot $surfacicUnitPlot */
            $surfacicUnitPlot = (bool) $subBlock ?
                current(array_filter($subBlock->getSurfacicUnitPlots()->toArray(),
                    fn (SurfacicUnitPlot $surfacicUnitPlot) => $surfacicUnitPlot->getX() === (int) $data[$bindings->x] && $surfacicUnitPlot->getY() === (int) $data[$bindings->y]
                )) :
                ((bool) $block ?
                    current(array_filter($block->getSurfacicUnitPlots()->toArray(),
                        fn (SurfacicUnitPlot $surfacicUnitPlot) => $surfacicUnitPlot->getX() === (int) $data[$bindings->x] && $surfacicUnitPlot->getY() === (int) $data[$bindings->y]
                    )) :
                    false);
            $individual = (bool) $unitPlot ?
                current(array_filter($unitPlot->getIndividuals()->toArray(),
                    fn (Individual $individual) => $individual->getX() === (int) $data[$bindings->x] && $individual->getY() === (int) $data[$bindings->y]
                )) :
                false;

            foreach ($bindings->variables as $variableBinding) {
                /** @var AbstractVariable $variable */
                $variable = $variables[$variableBinding->iri];
                $target = false;
                switch ($variable->getPathLevel()) {
                    case PathLevelEnum::EXPERIMENT:
                        $target = $experiment;
                        break;
                    case PathLevelEnum::BLOCK:
                        $target = $block;
                        break;
                    case PathLevelEnum::SUB_BLOCK:
                        $target = $subBlock;
                        break;
                    case PathLevelEnum::UNIT_PLOT:
                        $target = $unitPlot ?: $surfacicUnitPlot;
                        break;
                    case PathLevelEnum::INDIVIDUAL:
                        $target = $individual;
                        break;
                }
                if ($target) {
                    $fieldMeasure = (new FieldMeasure())
                        ->setTarget($target)
                        ->setVariable($variable);
                    $measure = (new Measure())
                        ->setTimestamp(false !== $variableBinding->timestamp ? $data[$variableBinding->timestamp] : new \DateTime())
                        ->setRepetition(1);
                    if (isset($data[$variableBinding->value]) && '' !== $data[$variableBinding->value]) {
                        $stateCode = current(array_filter($project->getStateCodes()->toArray(),
                            fn (StateCode $stateCode) => $stateCode->getCode() === (int) $data[$variableBinding->value]));

                        if ($stateCode) {
                            $measure->setState($stateCode);
                        } else {
                            $measure->setValue($data[$variableBinding->value]);
                        }
                        if ($bindings->integrateDeadAndReplanted && ($individual || $surfacicUnitPlot) && (($individual ?: $surfacicUnitPlot)->isDead() !== ($stateCode === $deadStateCode))) {
                            if ($stateCode !== $deadStateCode) {
                                ($individual ?: $surfacicUnitPlot)->setAppeared(new \DateTime());
                                ($individual ?: $surfacicUnitPlot)->setDead(false);
                            } else {
                                ($individual ?: $surfacicUnitPlot)->setDisappeared(new \DateTime());
                                ($individual ?: $surfacicUnitPlot)->setDead(true);
                            }
                        }
                    } else {
                        $measure->setState($missingDataStateCode);
                    }
                    $fieldMeasure->addMeasure($measure);
                    $session->addFieldMeasure($fieldMeasure);
                } else {
                    $errors[$lineNumber][] = RequestFile::ERROR_CSV_DATA_ENTRY_OBJECT_NOT_FOUND;
                }
            }
        }

        foreach ($errors as $errorOnLine) {
            if (!empty($errorOnLine)) {
                throw new MultipleParsingException($errors);
            }
        }
        $this->entityManager->persist($projectData);
        $project->addProjectData($projectData);
        $this->cleanTmpDir($parsingJob);
        $parsingJob->setObjectIri($this->iriConverter->getIriFromItem($projectData));
        $parsingJob->setStatus(UserLinkedJob::STATUS_SUCCESS);
        $this->entityManager->flush();
    }

    /**
     * @throws ParsingException
     * @throws MultipleParsingException
     */
    public function readPathCsv(int $jobId)
    {
        /** @var $parsingJob ParsingJob */
        $parsingJob = $this->entityManager->getRepository(ParsingJob::class)->find($jobId);
        $this->copyFileInTmpDir($parsingJob);
        $filePath = $this->getTmpDir($parsingJob).$parsingJob->getFilePath();
        if (!file_exists($filePath)) {
            throw new ParsingException('File not found');
        }

        $bindings = json_decode($parsingJob->getCsvBindings());

        // Open the CSV file
        $handle = fopen($filePath, 'r');

        // Get bindings from the 1st line
        $data = fgetcsv($handle, 0, $bindings->separator);

        $factorNames = [];
        // Iterate on each binding parameter
        foreach ($bindings as $key => $value) {
            switch ($key) {
                case 'name' :
                case 'project' :
                case 'askWhenEntering' :
                case 'identifyWithPosition' :
                case 'separator' :
                    break;
                default:
                    $bindings->$key = array_search($value, $data, true);
            }
        }

        /** @var Project $project */
        $project = $this->iriConverter->getItemFromIri($bindings->project);
        if (!$project) {
            throw new ParsingException('Project not found');
        }

        $pathBase = (new PathBase())
            ->setName($bindings->name)
            ->setProject($project)
            ->setAskWhenEntering($bindings->askWhenEntering);

        $this->entityManager->persist($pathBase);

        $positionMap = [];
        $identifierMap = [];
        $recursive = function (BusinessObject $object) use (&$positionMap, &$identifierMap, &$recursive) {
            if ($object instanceof SurfacicUnitPlot || $object instanceof Individual) {
                $positionMap[$object->getX().'_'.$object->getY()] = $this->iriConverter->getIriFromItem($object);
                $identifierMap[$object->getIdentifier()] = $this->iriConverter->getIriFromItem($object);
            }
            foreach ($object->children() as $child) {
                $recursive($child);
            }
        };
        foreach ($project->getExperiments() as $experiment) {
            $recursive($experiment);
        }

        $lineNumber = 0;
        $errors = [];
        $orderedIris = [];
        // Iterate on each line of the CSV
        while (($data = fgetcsv($handle, 0, $bindings->separator)) !== false) {
            // retrait des caractères non compatibles UTF8
            $utf8conv = function ($value) {
                return iconv('UTF-8', 'UTF-8//IGNORE', $value);
            };
            $data = array_map($utf8conv, $data);
            ++$lineNumber;
            $errors[$lineNumber] = [];

            // Manage mandatory params. for normacases
            foreach ($bindings as $key => $value) {
                switch ($key) {
                    case 'x':
                        $switchError = $bindings->identifyWithPosition ? RequestFile::ERROR_CSV_MISSING_X : null;
                        break;
                    case 'y':
                        $switchError = $bindings->identifyWithPosition ? RequestFile::ERROR_CSV_MISSING_Y : null;
                        break;
                    case 'identifier':
                        $switchError = $bindings->identifyWithPosition ? null : RequestFile::ERROR_CSV_MISSING_IDENTIFIER;
                        break;
                    case 'order':
                        $switchError = RequestFile::ERROR_CSV_MISSING_ORDER;
                        break;
                }
                if (isset($switchError) && (!isset($data[$bindings->$key]) || '' === $data[$bindings->$key])) {
                    $errors[$lineNumber][] = $switchError;
                }
                unset($switchError);
            }

            if (!empty($errors[$lineNumber])) {
                continue;
            }

            if ($bindings->identifyWithPosition) {
                if (!isset($positionMap[$data[$bindings->x].'_'.$data[$bindings->y]])) {
                    $errors[$lineNumber][] = RequestFile::ERROR_CSV_OBJECT_NOT_FOUND;
                    continue;
                }
            } else {
                if (!isset($identifierMap[$data[$bindings->identifier]])) {
                    $errors[$lineNumber][] = RequestFile::ERROR_CSV_OBJECT_NOT_FOUND;
                    continue;
                }
            }

            if (!is_numeric($data[$bindings->order])) {
                $errors[$lineNumber][] = RequestFile::ERROR_CSV_MISSING_ORDER;
                continue;
            }

            $orderedIris[(int) $data[$bindings->order]] = $bindings->identifyWithPosition ?
                $positionMap[$data[$bindings->x].'_'.$data[$bindings->y]] :
                $identifierMap[$data[$bindings->identifier]];
        }

        foreach ($errors as $errorOnLine) {
            if (!empty($errorOnLine)) {
                throw new MultipleParsingException($errors);
            }
        }
        ksort($orderedIris);
        $pathBase->setOrderedIris(array_values($orderedIris))
            ->setSelectedIris(array_values($orderedIris));

        $this->cleanTmpDir($parsingJob);
        $parsingJob->setObjectIri($this->iriConverter->getIriFromItem($pathBase));
        $parsingJob->setStatus(UserLinkedJob::STATUS_SUCCESS);
        $this->entityManager->flush();
    }
}
