<?php

namespace jrosset;

use jrosset\LastErrorException\LastErrorException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RegexIterator;
use SplFileInfo;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\SingleCommandApplication;
use Throwable;

/**
 * The "main" command
 */
class Main
{
    /**
     * Argument name for input files
     */
    private const string ARGUMENT_FILES = 'files';

    /**
     * @var SingleCommandApplication The command
     */
    private readonly SingleCommandApplication $command;

    /**
     * Initialization
     */
    public function __construct()
    {
        $this->command = (new SingleCommandApplication())
            ->setName('calibre_metadata_parser_ff')
            ->setDescription(<<<'EOF'
Calibre metadata parser for FanFiction files

  Each input file, if valid (EPUB file), is transformed to a Calibre input directory, containing the EPUB file and the metadata file (metadata.opf).
  The metadata are extracted from the "title" page of the EPUB, based on FicHub.net or FF2EBOOK.com.
EOF
            )
            ->addArgument(
                Main::ARGUMENT_FILES,
                InputArgument::REQUIRED | InputArgument::IS_ARRAY,
                'The files or directories to process'
            )
            ->setCode($this->execute(...));
    }

    /**
     * Run the command
     *
     * @return void
     *
     * @throws Throwable If an error occurs
     */
    public function run(): void
    {
        $this->command->run();
    }

    /**
     * Execute the command
     *
     * @param InputInterface $input The command line input
     * @param OutputInterface $output The command line output
     *
     * @return int The command exit status code
     *
     * @throws Throwable If an error occurs
     */
    private function execute(InputInterface $input, OutputInterface $output): int
    {
        foreach ($input->getArgument(self::ARGUMENT_FILES) as $file) {
            //region Check if file or directory exists
            $fileInfo = new SplFileInfo($file);
            if (!file_exists($file)) {
                $output->writeln('<error>Unable to find ' . ($fileInfo->isDir() ? 'directory' : 'file') . ': ' . $fileInfo->getPathname() . '</error>');
            }
            //endregion

            if ($fileInfo->isDir()) {
                $this->processDirectory($file, $output);
            } else {
                $this->processFile($file, $output);
            }
        }
        return Command::SUCCESS;
    }

    /**
     * Process a directory
     *
     * @param SplFileInfo $directory The directory
     * @param OutputInterface $output The command line output
     *
     * @return void
     *
     * @throws Throwable If an error occurs
     */
    private function processDirectory(SplFileInfo $directory, OutputInterface $output): void
    {
        //region Check directory is readable
        $output->writeln('<info>Processing directory: ' . $directory->getPathname() . '</info>');
        if (!$directory->isReadable()) {
            $output->writeln('<error>The directory is not readable</error>');
            return;
        }
        //endregion
        //region Treat each EPUB files (check recursively)
        $directoryIterator = new RecursiveDirectoryIterator($directory->getPathname());
        $directoryIterator = new RecursiveIteratorIterator($directoryIterator);
        $directoryIterator = new RegexIterator($directoryIterator, /** @lang PhpRegExp */ '/\.epub$/i', RegexIterator::MATCH);

        /** @var SplFileInfo $file */
        foreach ($directoryIterator as $file) {
            $this->processFile($file, $output);
        }
        //endregion
    }

    /**
     * Process a file
     *
     * @param SplFileInfo $file The file
     * @param OutputInterface $output The command line output
     *
     * @return void
     *
     * @throws Throwable If an error occurs
     */
    private function processFile(SplFileInfo $file, OutputInterface $output): void
    {
        //region Check file is OK for processing
        $output->writeln('<info>Processing file: ' . $file->getPathname() . '</info>');

        if (mb_strtolower($file->getExtension()) !== 'epub') {
            $output->writeln('<error>Invalid file extension, "epub" expected</error>');
            return;
        }
        if (!$file->isReadable()) {
            $output->writeln('<error>The file is not readable</error>');
            return;
        }

        $fileParentDirectory = new SplFileInfo(dirname($file->getRealPath()));
        if (!$fileParentDirectory->isReadable()) {
            $output->writeln('<error>The file parent directory is not readable</error>');
            return;
        }
        if (!$fileParentDirectory->isWritable()) {
            $output->writeln('<error>The file parent directory is not writable</error>');
            return;
        }
        //endregion
        //region Create subdirectory for the file (if necessary)
        if (count(scandir($fileParentDirectory->getPathname())) > 1) {
            $fileParentDirectory = new SplFileInfo($fileParentDirectory->getPathname() . DIRECTORY_SEPARATOR . $file->getBasename('.' . $file->getExtension()));
            if (!mkdir($fileParentDirectory->getPathname())) {
                $output->writeln('<error>Failed to create subdirectory "' . $fileParentDirectory->getPathname() . '": ' . (new LastErrorException())->getMessage() . '</error>');
                return;
            }

            $oldFile = $file;
            $file = new SplFileInfo($fileParentDirectory . DIRECTORY_SEPARATOR . $file->getFilename());
            if (rename($oldFile->getPathname(), $file->getPathname())) {
                $output->writeln('<error>Failed to move file to subdirectory "' . $oldFile->getPathname() . '" => "' . $file->getPathname() . '": ' . (new LastErrorException())->getMessage() . '</error>');
                return;
            }
        }
        //endregion
    }
}