<?php

namespace Webapp\FileManagement\Service;

use Doctrine\ORM\EntityManagerInterface;
use DOMDocument;
use Shared\Authentication\Entity\User;
use Symfony\Component\Security\Core\Security;
use Vich\UploaderBundle\Storage\StorageInterface;
use Webapp\Core\Entity\Experiment;
use Webapp\Core\Entity\OezNature;
use Webapp\Core\Entity\Platform;
use Webapp\Core\Entity\Project;
use Webapp\Core\Entity\ProjectData;

/**
 * Class WebappWriterService.
 */
class WebappWriterService extends AbstractWriterService
{

    private WebappWriterHelper $writerHelper;

    private Security $security;

    private string $desktopVersion;

    public function __construct(EntityManagerInterface $entityManager,
                                DirectoryNamer         $directoryNameManager,
                                StorageInterface       $storage,
                                WebappWriterHelper     $writerHelper,
                                Security               $security,
                                string                 $tmpDir,
                                string                 $desktopVersion)
    {
        parent::__construct($entityManager, $directoryNameManager, $storage, $tmpDir);
        $this->desktopVersion = $desktopVersion;
        $this->writerHelper = $writerHelper;
        $this->security = $security;
    }

    public function constructPlatformWithDataXml(Platform $platform): string
    {
        return $this->constructPlatformWithData($platform)->saveXML();
    }

    public function constructPlatformXml(Platform $platform): string
    {
        return $this->constructPlatform($platform)->saveXML();
    }

    public function constructExperimentXml(Experiment $experiment): string
    {
        return $this->constructExperiment($experiment)->saveXML();
    }

    private function constructPlatformWithData(Platform $platform, DOMDocument $dom = null, &$base = null, array &$uriMap = [], int &$count = 0): DOMDocument
    {
        if ($dom === null) {
            list($dom, $base, $count, $uriMap) = $this->initDom();
        }
        $dom = $this->constructPlatform($platform, $dom, $base, $uriMap, $count);

        foreach ($platform->getProjects() as $project) {
            $dom = $this->constructProject($project, $dom, $base, $uriMap, $count);
            foreach ($project->getProjectDatas() as $projectData) {
                $dom = $this->constructProjectData($projectData, $dom, $base, $uriMap, $count);
            }
        }

        return $dom;
    }

    private function constructProject(Project $project, DOMDocument $dom = null, &$base = null, array &$uriMap = [], int &$count = 0): DOMDocument
    {
        if ($dom === null) {
            list($dom, $base, $count, $uriMap) = $this->initDom();
        }
        $variablesTuples = [];
        $uriMap[$project->getUri()] = "/" . $count++;
        $projectXml = $this->writerHelper->constructProjectElement($dom, $project, $uriMap, $variablesTuples);
        $base->appendChild($projectXml);

        foreach ($variablesTuples as $variableTuple) {
            $this->writerHelper->completeVariableXml($dom, $uriMap, $variableTuple);
        }


        return $dom;
    }

    private function constructProjectData(ProjectData $projectData, DOMDocument $dom = null, &$base = null, array &$uriMap = [], int &$count = 0): DOMDocument
    {
        if ($dom === null) {
            list($dom, $base, $count, $uriMap) = $this->initDom();
        }
        $variablesTuples = [];
        $uriMap[$projectData->getUri()] = "/". $count++;
        $desktopUsersXml = [];
        $projectDataXml = $this->writerHelper->constructProjectDataElement($dom, $projectData, $uriMap, $variablesTuples, $count, $desktopUsersXml);
        $base->appendChild($projectDataXml);

        foreach ($desktopUsersXml as $desktopUserXml) {
            $base->appendChild($desktopUserXml);
        }

        foreach ($variablesTuples as $variableTuple) {
            $this->writerHelper->completeVariableXml($dom, $uriMap, $variableTuple);
        }

        return $dom;
    }

    private function constructPlatform(Platform $platform, DOMDocument $dom = null, &$base = null, array &$uriMap = [], int &$count = 0): DOMDocument
    {
        if ($dom === null) {
            list($dom, $base, $count, $uriMap) = $this->initDom();
        }

        foreach ($platform->getSite()->getOezNatures() as $oezNature) {
            $dom = $this->constructOezNatureElement($oezNature, $dom, $base, $uriMap, $count);
        }

        /** @var User $user */
        $user = $this->security->getUser();
        $uriMap['connected_user'] = "/$count";
        $uriMap[$user->getUri()] = "/$count";
        $userXml = $this->writerHelper->constructConnectedUserElement($dom, $user, $uriMap);
        $base->appendChild($userXml);
        $count++;

        $uriMap[$platform->getUri()] = "/$count";
        $platformXml = $this->writerHelper->constructPlateformElement($dom, $platform, $uriMap);
        $base->appendChild($platformXml);
        $count++;

        return $dom;
    }

    private function constructExperiment(Experiment $experiment, DOMDocument $dom = null, &$base = null, array &$uriMap = [], int &$count = 0): DOMDocument
    {
        if ($dom === null) {
            list($dom, $base, $count, $uriMap) = $this->initDom();
        }

        foreach (($experiment->getSite() ?? $experiment->getPlatform()->getSite())->getOezNatures() as $oezNature) {
            $dom = $this->constructOezNatureElement($oezNature, $dom, $base, $uriMap, $count);
        }

        $uriMap['connected_user'] = "/$count";
        $userXml = $this->writerHelper->constructConnectedUserElement($dom, $this->security->getUser(), $uriMap);
        $base->appendChild($userXml);
        $count++;

        $uriMap[$experiment->getUri()] = "/$count";
        $experimentXml = $this->writerHelper->constructExperimentElement($dom, $experiment, $uriMap, true);
        $base->appendChild($experimentXml);
        $count++;

        return $dom;
    }

    private function constructOezNatureElement(OezNature $oezNature, DOMDocument $dom = null, &$base = null, array &$uriMap = [], int &$count = 0): DOMDocument
    {
        if ($dom === null) {
            list($dom, $base, $count, $uriMap) = $this->initDom();
        }

        $uriMap[$oezNature->getUri()] = "/$count";
        $oezNatureXml = $this->writerHelper->constructOezNatureElement($dom, $oezNature, $uriMap);
        $base->appendChild($oezNatureXml);
        $count++;

        return $dom;
    }

    /**
     * @return array
     * @throws \DOMException
     */
    private function initDom(): array
    {
        $dom = new DOMDocument("1.0", "UTF-8");
        $dom->preserveWhiteSpace = false;
        //Needed to make the XML human readable
        $dom->formatOutput = true;

        $base = $dom->createElementNS(self::NS_XMI, 'xmi:XMI');
        $base->setAttributeNS(self::NS_XMI, "xmi:version", "2.0");
        $base->setAttribute("xmlns:xsi", self::NS_XSI);
        $base->setAttribute("xmlns:adonis.modeleMetier.plateforme", self::NS_PLATEFORME);
        $base->setAttribute("xmlns:adonis.modeleMetier.projetDeSaisie", self::NS_PROJET_SAISIE);
        $base->setAttribute("xmlns:adonis.modeleMetier.projetDeSaisie.cheminement", self::NS_CHEMINEMENT);
        $base->setAttribute("xmlns:adonis.modeleMetier.projetDeSaisie.variables", self::NS_VARIABLES);
        $base->setAttribute("xmlns:adonis.modeleMetier.saisieTerrain", self::NS_SAISIE_TERRAIN);
        $base->setAttribute("xmlns:adonis.modeleMetier.site", self::NS_SITE);
        $base->setAttribute("xmlns:adonis.modeleMetier.utilisateur", self::NS_UTILISATEUR);
        $base->setAttribute("xmlns:adonis.modeleMetier.conceptsDeBase", self::NS_CONCEPT_DE_BASE);
        $dom->appendChild($base);

        $version = $dom->createElement("adonis.modeleMetier.site:AdonisVersion");
        $version->setAttribute("version", $this->desktopVersion);
        $base->appendChild($version);

        return array($dom, $base, 1, []);
    }

}
