<?php
/**
 * @author      Aurélien BERNARD - TRYDEA - 2020
 */

declare(strict_types=1);

namespace Mobile\Project\ApiTransformer;

use ApiPlatform\Core\Api\IriConverterInterface;
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use Doctrine\Common\Collections\Collection;
use Mobile\Device\Dto\DeviceInput;
use Mobile\Device\Entity\Device;
use Mobile\Measure\Dto\Test\CombinationTestInput;
use Mobile\Measure\Dto\Test\GrowthTestInput;
use Mobile\Measure\Dto\Test\PreconditionedCalculationInput;
use Mobile\Measure\Dto\Test\RangeTestInput;
use Mobile\Measure\Dto\Test\TestInput;
use Mobile\Measure\Dto\Variable\DriverInput;
use Mobile\Measure\Dto\Variable\GeneratorVariableInput;
use Mobile\Measure\Dto\Variable\MarkInput;
use Mobile\Measure\Dto\Variable\MarkListInput;
use Mobile\Measure\Dto\Variable\MaterialInput;
use Mobile\Measure\Dto\Variable\PreviousValueInput;
use Mobile\Measure\Dto\Variable\RequiredAnnotationInput;
use Mobile\Measure\Dto\Variable\StateCodeInput;
use Mobile\Measure\Dto\Variable\UniqueVariableInput;
use Mobile\Measure\Dto\Variable\ValueHintInput;
use Mobile\Measure\Dto\Variable\ValueHintListInput;
use Mobile\Measure\Dto\Variable\VariableInput;
use Mobile\Measure\Entity\Test\Base\Test;
use Mobile\Measure\Entity\Test\CombinationTest;
use Mobile\Measure\Entity\Test\GrowthTest;
use Mobile\Measure\Entity\Test\PreconditionedCalculation;
use Mobile\Measure\Entity\Test\RangeTest;
use Mobile\Measure\Entity\Variable\Base\Variable;
use Mobile\Measure\Entity\Variable\Driver;
use Mobile\Measure\Entity\Variable\GeneratorVariable;
use Mobile\Measure\Entity\Variable\Mark;
use Mobile\Measure\Entity\Variable\Material;
use Mobile\Measure\Entity\Variable\PreviousValue;
use Mobile\Measure\Entity\Variable\RequiredAnnotation;
use Mobile\Measure\Entity\Variable\Scale;
use Mobile\Measure\Entity\Variable\StateCode;
use Mobile\Measure\Entity\Variable\UniqueVariable;
use Mobile\Measure\Entity\Variable\ValueHint;
use Mobile\Measure\Entity\Variable\ValueHintList;
use Mobile\Project\Dto\DataEntryProjectInput;
use Mobile\Project\Dto\DesktopUserInput;
use Mobile\Project\Dto\GraphicalStructureInput;
use Mobile\Project\Dto\NatureZHEInput;
use Mobile\Project\Dto\PlatformInput;
use Mobile\Project\Dto\WorkpathInput;
use Mobile\Project\Entity\DataEntryProject;
use Mobile\Project\Entity\DesktopUser;
use Mobile\Project\Entity\GraphicalStructure;
use Mobile\Project\Entity\NatureZHE;
use Mobile\Project\Entity\Platform;
use Mobile\Project\Entity\Workpath;
use Mobile\Shared\ApiTransformer\DeviceOutputDataTransformer;
use Mobile\Shared\ApiTransformer\NatureZheOutputDataTransformer;
use Shared\Authentication\Entity\User;
use Symfony\Component\Security\Core\Security;

/**
 * Class DataEntryProjectOutputDataTransformer.
 */
class DataEntryProjectOutputDataTransformer implements DataTransformerInterface
{
    /**
     * @var DeviceOutputDataTransformer
     */
    private $deviceDto;

    /**
     * @var NatureZheOutputDataTransformer
     */
    private $natureZheDto;

    /**
     * @var Security
     */
    private $security;

    private IriConverterInterface $iriConverter;

    /**
     * DataEntryProjectOutputDataTransformer constructor.
     */
    public function __construct(IriConverterInterface $iriConverter,
        DeviceOutputDataTransformer $deviceOutputDataTransformer,
        NatureZheOutputDataTransformer $natureZheOutputDataTransformer,
        Security $security)
    {
        $this->deviceDto = $deviceOutputDataTransformer;
        $this->natureZheDto = $natureZheOutputDataTransformer;
        $this->security = $security;
        $this->iriConverter = $iriConverter;
    }

    public function supportsTransformation($data, string $to, array $context = []): bool
    {
        return $data instanceof DataEntryProject && DataEntryProjectInput::class === $to;
    }

    public function transform($object, string $to, array $context = []): object
    {
        /** @var $object DataEntryProject */
        /** @var $user User */
        $user = $this->security->getUser();

        $creatorLogin = !is_null($object->getCreator()) ? $object->getCreator()->getLogin() : null;

        $output = new DataEntryProjectInput();
        $output
            ->setConnectedUniqueVariables($this->transform2uniqueVariablesArray($object->getConnectedUniqueVariables()))
            ->setConnectedGeneratorVariables($this->transform2generatorVariablesArray($object->getConnectedGeneratorVariables()))
            ->setRequiredAnnotations($this->transform2RequiredAnnotationArray($object->getRequiredAnnotations()))
            ->setWorkpaths($this->transform2workpathsArray($object->getWorkpaths()))
            ->setImprovised(false);

        $output
            ->setId($object->getId())
            ->setOwnerIri($this->iriConverter->getIriFromItem($user))
            ->setName($object->getName())
            ->setPlatform($this->transform2platform($object->getPlatform()))
            ->setCreatorLogin($creatorLogin)
            ->setDesktopUsers($this->transform2desktopUsersArray($object->getDesktopUsers()))
            ->setUniqueVariables($this->transform2uniqueVariablesArray($object->getUniqueVariables()))
            ->setGeneratorVariables($this->transform2generatorVariablesArray($object->getGeneratorVariables()))
            ->setStateCodes($this->transform2stateCodesArray($object->getStateCodes()))
            ->setCreationDate($object->getCreationDate())
            ->setMaterials($this->transform2materialArray($object->getMaterials()))
            ->setNaturesZHE($this->transform2natureZHEArray($object->getNaturesZHE()))
            ->setGraphicalStructure($this->transform2graphicalStructure($object->getGraphicalStructure()));

        return $output;
    }

    private function transform2uniqueVariablesArray(Collection $variables): array
    {
        return array_map(
            function (UniqueVariable $variable) {
                $output = new UniqueVariableInput();
                $this->completeWithVariableData($variable, $output);
                $output
                    ->setValueHintList($this->transform2valueHintList($variable->getValueHintList()));

                return $output;
            },
            $variables->toArray()
        );
    }

    private function completeWithVariableData(Variable $variable, VariableInput $output): void
    {
        $output
            ->setName($variable->getName())
            ->setShortName($variable->getShortName())
            ->setRepetitions($variable->getRepetitions())
            ->setUnit($variable->getUnit())
            ->setAskTimestamp($variable->isAskTimestamp())
            ->setPathLevel($variable->getPathLevel())
            ->setComment($variable->getComment())
            ->setOrder($variable->getOrder())
            ->setActive($variable->isActive())
            ->setFormat($variable->getFormat())
            ->setDefaultValue($variable->getDefaultValue())
            ->setType($variable->getType())
            ->setMandatory($variable->isMandatory())
            ->setGrowthTests($this->transform2GrowthTestsArray($variable->getGrowthTests()))
            ->setRangeTests($this->transform2RangeTestsArray($variable->getRangeTests()))
            ->setCombinationTests($this->transform2CombinationTestsArray($variable->getCombinationTests()))
            ->setPreconditionedCalculations($this->transform2preconditionedCalculationsArray($variable->getPreconditionedCalculations()))
            ->setScale($this->transform2markList($variable->getScale()))
            ->setPreviousValues($this->transform2previousValuesArray($variable->getPreviousValues()))
            ->setCreationDate($variable->getCreationDate())
            ->setModificationDate($variable->getModificationDate())
            ->setUri($variable->getUri())
            ->setFrameStartPosition($variable->getFrameStartPosition())
            ->setFrameEndPosition($variable->getFrameEndPosition())
            ->setMaterialUid(null === $variable->getMaterial() ? null : $variable->getMaterial()->getUri());
    }

    private function transform2GrowthTestsArray(Collection $growthTests): array
    {
        return array_map(
            function (GrowthTest $test) {
                $output = new GrowthTestInput();
                $this->completeWithTestData($test, $output);
                $output
                    ->setComparisonVariableUid($test->getComparisonVariable()->getUri())
                    ->setMinDiff($test->getMinDiff())
                    ->setMaxDiff($test->getMaxDiff());

                return $output;
            },
            $growthTests->toArray()
        );
    }

    private function completeWithTestData(Test $test, TestInput $output)
    {
        $output
            ->setName($test->getName())
            ->setActive($test->isActive());
    }

    private function transform2RangeTestsArray(Collection $rangeTests): array
    {
        return array_map(
            function (RangeTest $test) {
                $output = new RangeTestInput();
                $this->completeWithTestData($test, $output);
                $output
                    ->setMandatoryMinValue($test->getMandatoryMinValue())
                    ->setMandatoryMaxValue($test->getMandatoryMaxValue())
                    ->setOptionalMinValue($test->getOptionalMinValue())
                    ->setOptionalMaxValue($test->getOptionalMaxValue());

                return $output;
            },
            $rangeTests->toArray()
        );
    }

    private function transform2CombinationTestsArray(Collection $combinationTests): array
    {
        return array_map(
            function (CombinationTest $test) {
                $output = new CombinationTestInput();
                $this->completeWithTestData($test, $output);
                $output
                    ->setFirstOperandUid($test->getFirstOperand()->getUri())
                    ->setSecondOperandUid($test->getSecondOperand()->getUri())
                    ->setOperator($test->getOperator())
                    ->setLowLimit($test->getLowLimit())
                    ->setHighLimit($test->getHighLimit());

                return $output;
            },
            $combinationTests->toArray()
        );
    }

    private function transform2preconditionedCalculationsArray(Collection $preconditionedCalculations): array
    {
        return array_map(
            function (PreconditionedCalculation $test) {
                $output = new PreconditionedCalculationInput();
                $this->completeWithTestData($test, $output);
                $comparedVariableUid = $test->getComparedVariable() ? $test->getComparedVariable()->getUri() : null;
                $output
                    ->setComparisonVariableUid($test->getComparisonVariable()->getUri())
                    ->setComparedVariableUid($comparedVariableUid)
                    ->setComparedValue($test->getComparedValue())
                    ->setCondition($test->getCondition())
                    ->setAffectationValue($test->getAffectationValue());

                return $output;
            },
            $preconditionedCalculations->toArray()
        );
    }

    private function transform2markList(?Scale $scale): ?MarkListInput
    {
        if (is_null($scale)) {
            return null;
        }
        $output = new MarkListInput();
        $marks = array_map(
            function (Mark $mark) {
                $output = new MarkInput();
                $output
                    ->setText($mark->getText())
                    ->setValue($mark->getValue())
                    ->setImageName($mark->getImageName())
                    ->setImageData($mark->getImageData());

                return $output;
            },
            $scale->getMarks()->toArray()
        );
        $output
            ->setName($scale->getName())
            ->setExclusive($scale->isExclusive())
            ->setMarks($marks)
            ->setMinValue($scale->getMinValue())
            ->setMaxValue($scale->getMaxValue());

        return $output;
    }

    private function transform2previousValuesArray(Collection $previousValues): array
    {
        return array_map(
            function (PreviousValue $previousValue) {
                $output = new PreviousValueInput();
                $output
                    ->setValue($previousValue->getValue())
                    ->setDate($previousValue->getDate())
                    ->setObjectUri($previousValue->getObjectUri())
                    ->setOriginVariableUid($previousValue->getOriginVariable()->getUri());

                if (!is_null($previousValue->getState())) {
                    $output->setStateCode($previousValue->getState()->getCode());
                }

                return $output;
            },
            $previousValues->toArray()
        );
    }

    private function transform2valueHintList(?ValueHintList $valueHintList): ?ValueHintListInput
    {
        if (is_null($valueHintList)) {
            return null;
        }
        $output = new ValueHintListInput();
        $valueHints = array_map(
            function (ValueHint $valueHint) {
                $output = new ValueHintInput();
                $output
                    ->setValue($valueHint->getValue());

                return $output;
            },
            $valueHintList->getValueHints()->toArray()
        );
        $output
            ->setName($valueHintList->getName())
            ->setValueHints($valueHints);

        return $output;
    }

    private function transform2generatorVariablesArray(Collection $variables): array
    {
        return array_map(
            function (GeneratorVariable $variable) {
                $output = new GeneratorVariableInput();
                $this->completeWithVariableData($variable, $output);
                $output
                    ->setGeneratedPrefix($variable->getGeneratedPrefix())
                    ->setNumeralIncrement($variable->isNumeralIncrement())
                    ->setUniqueVariables($this->transform2uniqueVariablesArray($variable->getUniqueVariables()))
                    ->setGeneratorVariables($this->transform2generatorVariablesArray($variable->getGeneratorVariables()));

                return $output;
            },
            $variables->toArray()
        );
    }

    private function transform2RequiredAnnotationArray(Collection $requiredAnnotations): array
    {
        return array_map(
            function (RequiredAnnotation $requiredAnnotation) {
                $output = new RequiredAnnotationInput();
                $output->setBuisnessObjects($requiredAnnotation->getBuisnessObjects())
                    ->setPathLevel($requiredAnnotation->getPathLevel())
                    ->setType($requiredAnnotation->getType())
                    ->setComment($requiredAnnotation->getComment())
                    ->setAskWhenEntering($requiredAnnotation->isAskWhenEntering())
                    ->setActive($requiredAnnotation->isActive())
                    ->setKeywords($requiredAnnotation->getKeywords())
                    ->setCategory($requiredAnnotation->getCategory());

                return $output;
            },
            $requiredAnnotations->toArray()
        );
    }

    private function transform2workpathsArray(Collection $workpaths): array
    {
        return array_map(
            function (Workpath $workpath) {
                $output = new WorkpathInput();
                $output
                    ->setUsername($workpath->getUsername())
                    ->setPath($workpath->getPath())
                    ->setStartEnd($workpath->isStartEnd());

                return $output;
            },
            $workpaths->toArray()
        );
    }

    private function transform2platform(Platform $platform): PlatformInput
    {
        $output = new PlatformInput();
        $output
            ->setName($platform->getName())
            ->setDevices($this->transform2devicesArray($platform->getDevices()))
            ->setCreationDate($platform->getCreationDate())
            ->setPlace($platform->getPlace())
            ->setSite($platform->getSite());

        return $output;
    }

    private function transform2devicesArray(Collection $devices): array
    {
        return array_map(
            function (Device $device) {
                return $this->deviceDto->transform($device, DeviceInput::class);
            },
            $devices->toArray()
        );
    }

    /**
     * @param Collection<int,  DesktopUser> $desktopUser
     *
     * @return DesktopUserInput[]
     */
    private function transform2desktopUsersArray(Collection $desktopUser): array
    {
        return array_map(
            function (DesktopUser $desktopUser) {
                $output = new DesktopUserInput();
                $output
                    ->setId($desktopUser->getId())
                    ->setName($desktopUser->getName())
                    ->setFirstname($desktopUser->getFirstname())
                    ->setEmail($desktopUser->getEmail())
                    ->setLogin($desktopUser->getLogin())
                    ->setPassword($desktopUser->getPassword());

                return $output;
            },
            $desktopUser->toArray()
        );
    }

    private function transform2stateCodesArray(Collection $stateCodes): array
    {
        return array_map(
            function (StateCode $stateCode) {
                return $this->transform2stateCode($stateCode);
            },
            $stateCodes->toArray()
        );
    }

    private function transform2stateCode(StateCode $stateCode): StateCodeInput
    {
        $output = new StateCodeInput();
        $output
            ->setType($stateCode->getType())
            ->setCode($stateCode->getCode())
            ->setColor($stateCode->getColor())
            ->setDescription($stateCode->getDescription())
            ->setLabel($stateCode->getLabel())
            ->setPropagation($stateCode->getPropagation());

        return $output;
    }

    private function transform2materialArray(Collection $getMaterials)
    {
        return array_map(
            function (Material $material) {
                $output = new MaterialInput();
                $output
                    ->setDriver($this->transform2Driver($material->getDriver()))
                    ->setType($material->getType())
                    ->setPhysicalDevice($material->getPhysicalDevice())
                    ->setManufacturer($material->getManufacturer())
                    ->setName($material->getName())
                    ->setUid($material->getUri());

                return $output;
            },
            $getMaterials->toArray()
        );
    }

    private function transform2Driver(Driver $driver): DriverInput
    {
        $driverInput = new DriverInput();
        $driverInput
            ->setType($driver->getType())
            ->setPort($driver->getPort())
            ->setPush($driver->getPush())
            ->setFlowControl($driver->getFlowControl())
            ->setRequest($driver->getRequest())
            ->setParity($driver->getParity())
            ->setStopbit($driver->getStopbit())
            ->setBaudrate($driver->getBaudrate())
            ->setCsvSeparator($driver->getCsvSeparator())
            ->setFrameEnd($driver->getFrameEnd())
            ->setFrameStart($driver->getFrameStart())
            ->setFrameLength($driver->getFrameLength())
            ->setTimeout($driver->getTimeout())
            ->setDatabitsFormat($driver->getDatabitsFormat());

        return $driverInput;
    }

    private function transform2natureZHEArray(Collection $getNaturesZHE)
    {
        return array_map(
            function (NatureZHE $natureZHE) {
                return $this->natureZheDto->transform($natureZHE, NatureZHEInput::class);
            },
            $getNaturesZHE->toArray()
        );
    }

    private function transform2graphicalStructure(?GraphicalStructure $graphicalStructure): ?GraphicalStructureInput
    {
        $graphicalStructureInput = null;
        if (!is_null($graphicalStructure)) {
            $graphicalStructureInput = new GraphicalStructureInput();
            $graphicalStructureInput
                ->setplatformColor($graphicalStructure->getPlatformColor())
                ->setDeviceColor($graphicalStructure->getDeviceColor())
                ->setBlockColor($graphicalStructure->getBlockColor())
                ->setSubBlockColor($graphicalStructure->getSubBlockColor())
                ->setIndividualUnitParcelColor($graphicalStructure->getIndividualUnitParcelColor())
                ->setSurfaceUnitParcelColor($graphicalStructure->getSurfaceUnitParcelColor())
                ->setAbnormalUnitParcelColor($graphicalStructure->getAbnormalUnitParcelColor())
                ->setIndividualColor($graphicalStructure->getIndividualColor())
                ->setAbnormalIndividualColor($graphicalStructure->getAbnormalIndividualColor())
                ->setEmptyColor($graphicalStructure->getEmptyColor())
                ->setTooltipActive($graphicalStructure->isTooltipActive())
                ->setDeviceLabel($graphicalStructure->getDeviceLabel())
                ->setBlockLabel($graphicalStructure->getBlockLabel())
                ->setSubBlockLabel($graphicalStructure->getSubBlockLabel())
                ->setIndividualUnitParcelLabel($graphicalStructure->getIndividualUnitParcelLabel())
                ->setSurfaceUnitParcelLabel($graphicalStructure->getSurfaceUnitParcelLabel())
                ->setIndividualLabel($graphicalStructure->getIndividualLabel())
                ->setOrigin($graphicalStructure->getOrigin())
                ->setBaseWidth($graphicalStructure->getBaseWidth())
                ->setBaseHeight($graphicalStructure->getBaseHeight());
        }

        return $graphicalStructureInput;
    }
}
