<?php
declare(strict_types=1);
/*
 * This file is part of "irstea/ng-model-generator-bundle".
 *
 * "irstea/ng-model-generator-bundle" generates Typescript interfaces for Angular using api-platform metadata.
 * Copyright (C) 2018-2021 IRSTEA
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License and the GNU
 * Lesser General Public License along with this program. If not, see
 * <https://www.gnu.org/licenses/>.
 */

namespace Irstea\NgModelGeneratorBundle\Command;

use ApiPlatform\Core\Documentation\Documentation;
use Assert\Assertion;
use Irstea\NgModelGeneratorBundle\ModelGenerator;
use Irstea\NgModelGeneratorBundle\Writers\ConsoleWriter;
use Irstea\NgModelGeneratorBundle\Writers\DirectoryWriter;
use Irstea\NgModelGeneratorBundle\Writers\FilteringFileWriter;
use Irstea\NgModelGeneratorBundle\Writers\MultiFileWriter;
use Irstea\NgModelGeneratorBundle\Writers\PhonyFileWriter;
use Irstea\NgModelGeneratorBundle\Writers\ZipWriter;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Class NgModelGenerateCommand.
 */
/* final */ class NgModelGenerateCommand extends Command
{
    /**
     * @var ModelGenerator
     */
    private $generator;

    /**
     * @var Documentation
     */
    private $documentation;

    /**
     * NgModelGenerateCommand constructor.
     */
    public function __construct(
        ModelGenerator $generator,
        Documentation $documentation
    ) {
        parent::__construct();

        $this->generator = $generator;
        $this->documentation = $documentation;
    }

    /**
     * {@inheritdoc}
     */
    protected function configure(): void
    {
        $this
            ->setName('ng-model:generate')
            ->setDescription('Dump Typescript representations')
            ->addOption('zip', 'z', InputOption::VALUE_REQUIRED, 'Archive ZIP à créer')
            ->addOption('output', 'o', InputOption::VALUE_REQUIRED, 'Dossier de destination')
            ->addOption('force', 'f', InputOption::VALUE_NONE, 'Ecrase la cible')
            ->addOption('restrict', 'r', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Limite la sortie aux modèles de nom de fichier');
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output): void
    {
        $writer = new PhonyFileWriter(
            $this->openWriter($input, $output),
            $output
        );

        if ($input->getOption('restrict')) {
            $globs = $input->getOption('restrict');
            Assertion::isArray($globs);
            Assertion::allString($globs);

            $writer = new FilteringFileWriter($writer, function (string $path) use ($globs): bool {
                foreach ($globs as $glob) {
                    if (fnmatch($glob, $path, \FNM_PATHNAME)) {
                        return true;
                    }
                }

                return false;
            });
        }

        try {
            $this->generator->generate($this->documentation, $writer);
        } finally {
            $writer->close();
        }
    }

    private function openWriter(InputInterface $input, OutputInterface $output): MultiFileWriter
    {
        $zipPath = $input->getOption('zip');
        Assertion::nullOrString($zipPath);
        if ($zipPath) {
            $force = (bool) $input->getOption('force');
            $archive = $this->openZipArchive($zipPath, $force);
            $output->write("Writing to ZIP archive: <info>$zipPath</info>\n");

            return new ZipWriter($archive);
        }

        $dirPath = $input->getOption('output');
        Assertion::nullOrString($dirPath);
        if ($dirPath) {
            $output->write("Writing to directory: <info>$dirPath</info>\n");

            return new DirectoryWriter($dirPath);
        }

        return new ConsoleWriter($output);
    }

    private function openZipArchive(string $path, bool $force): \ZipArchive
    {
        $archive = new \ZipArchive();

        $flags = \ZipArchive::CREATE;
        if ($force) {
            $flags |= \ZipArchive::OVERWRITE;
        }

        $res = $archive->open($path, $flags);
        if ($res !== true) {
            throw new \RuntimeException("Could not open $path, error code #$res");
        }

        return $archive;
    }
}