<?php 

namespace NPS\Command;

use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Input\InputOption;
use NPS\Etc\Strings;
use NPS\Repository\FileRepository;
use NPS\Model\KnowledgeBase;
use NPS\Repository\KnowledgeBaseRepository;
use Doctrine\ORM\EntityManagerInterface;
use NPS\Exception\UnsupportedFileException;

/**
 * Synchronizes known bases with a state of a directory
 * 
 * Tree format 
 * / synced_directory 
 * / synced_directory / one_base_subdirectory
 * / synced_directory / one_base_subdirectory / base.nps|n32 will be synced
 * / synced_directory / one_base_subdirectory / a.jpg will be synced to belong to base.nps|n32
 * 
 * @author Lukas Korinek
 */
class SynchronizeBasesDirectoryCommand extends Command
{

    /** @var FileRepository $fileRepo */
    private $fileRepo;

    /** @var KnowledgeBaseRepository $baseRepo */
    private $baseRepo;

    /** @var EntityManagerInterface $em */
    private $em;

    public function __construct(FileRepository $fileRepo, KnowledgeBaseRepository $baseRepo, EntityManagerInterface $em) 
    {
        $this->fileRepo = $fileRepo;
        $this->baseRepo = $baseRepo;
        $this->em = $em;
        parent::__construct();
    }

    /**
     * @inheritdoc
     */
    protected function configure()
    {
        $this 
            ->setName('base:sync')
            ->setDescription('Synchronizes known bases with a state of a directory')
            ->addOption('status', null, InputOption::VALUE_REQUIRED, 'Status into which the bases will be created/updated' , 'STATUS_PUBLIC')
            ->addArgument('directory', InputArgument::REQUIRED, 'Directory serving as a source of bases');
    }   

    /**
     * @inheritdoc
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // Validate directory
        $dir = $input->getArgument('directory');
        if(!is_dir($dir))
        {
            $output->writeln('Please pass a valid directory as an argument.');
            return 1;
        }
        $output->writeln('Syncing directory: ' . $dir);

        // Find and synchronize all subdirectories in that directory which have been updated recently (in timescale)
        foreach(scandir($dir) as $file)
        {
            if($file == '.' || $file == '..')
            {
                continue;
            }

            // Append the directory
            $file = rtrim($dir, "\\/") . '/' . $file;

            if(is_dir($file))
            {
                $this->synchronizeDirectory($output, $file, $input->getOption('status'));
            }
        }

        $this->em->flush();
        return 0;
    }

    /**
     * Synchronizes a particular subdirectory with a knowledge base
     * @param OutputInterface $output
     * @param string $dir Directory to sync
     * @param string $status Status to sync the directory to
     */
    private function synchronizeDirectory(OutputInterface $output, $dir, $status)
    {
        // Find a knowledge base file
        $files = glob(rtrim($dir, "\\/") . '/' . '*.{nps,n32}', GLOB_BRACE);
        $file = reset($files);
        if($file == null)
        {
            $output->writeln("No knowledge base file found: " . rtrim($dir, "\\/") . '/' . '*.{nps,n32}');
            return;
        }

        // Synchronize the knowledge base
        /** @var KnowledgeBase $knowledgeBase */
        if(!($knowledgeBase = $this->synchronizeKnowledgeBase($output, $file, $status)))
        {
            return;
        }
        $tag = $knowledgeBase->getBaseDiscriminatorTag();
        
        // Synchronize media files 
        foreach(scandir($dir) as $file)
        {
            // Append the directory
            $file = rtrim($dir, "\\/") . '/' . $file;
            
            // File is a valid media file
            if(is_dir($file) || Strings::ends('.n32', $file) || Strings::ends('.nps', $file))
            {
                continue;
            }

            $this->synchronizeFile($output, $file, $tag);
        }
    }

    /**
     * Synchronizes a knowledge base file 
     * @param OutputInterface $output
     * @param string $path
     * @param string $status
     * @return KnowledgeBase|null
     */
    private function synchronizeKnowledgeBase(OutputInterface $output, $path, $status)
    {
        $output->write('Syncing base: ' . $path . ' ... ');
        $target = $path;
        $base = null;

        // Convert NPS files to their respective counterparts
        if(Strings::ends('.n32', $path))
        {
            $command = $this->getApplication()->find('base:convert');
            $input = new ArrayInput(['source' => $path, '--force' => true]);
            if($command->run($input, new NullOutput()) != 0)
            {
                $output->write('conversion failed');
                return $base;
            }

            $target = Strings::replace('.n32', '.nps', $path);
        }

        // Match version date if possible
        $ver = basename($target, '.nps');
        $matched = \DateTime::createFromFormat('Ymd', $ver);
        if($matched === false || $matched->format('Ymd') != trim($ver))
        {
            $ver = null;
        }
        
        // Try to add the base
        $in = [
            'path' => $target, 
            '--update' => true, 
            '--status' => $status
        ];

        if($ver)
        {
            $in['--ver'] = $ver;
        }
        
        $command = $this->getApplication()->find('base:add');
        $out = new BufferedOutput();
        $input = new ArrayInput($in);

        if(($result = $command->run($input, $out)) == 0)
        {
            $lines = array_filter(explode("\n", str_replace("\r", '', $out->fetch())));
            $id = intval(end($lines));
            $base = $this->baseRepo->getById($id);

            $output->writeln('ok');
        }
        else 
        {
            $output->writeln('fail');

            if($output->isVerbose())
            {
                $output->writeln($out->fetch());
            }
        }
        
        // Clear conversion garbage
        if($target != $path)
        {
            unlink($target);
        }

        return $base;
    }

    /**
     * Synchronizes a media file
     * @param OutputInterface $output
     * @param string $path Path to the file 
     * @param string $discriminatorTag Knowledge base discriminator tag 
     */
    private function synchronizeFile(OutputInterface $output, $path, $discriminatorTag)
    {
        $output->write('Syncing file: ' . $path . ' ... ');

        $filename = basename($path);
        if($file = $this->fileRepo->getByTagAndFilename($discriminatorTag, $filename))
        {
            // Update existing file
            $content = file_get_contents($path);
            $result = $file->updateContent($content);

            $output->writeln($result ? 'updated' : 'fail');
            return;
        }

        $command = $this->getApplication()->find('file:upload');
        $input = new ArrayInput(['path' => $path, '--tags' => $discriminatorTag]);

        // Unsupported files should be just ignored
        try 
        {
            $result = $command->run($input, new NullOutput());
        }
        catch(UnsupportedFileException $ex)
        {
            $result = 1;
        }
        

        $output->writeln($result == 0 ? 'ok' : 'fail');
    }


}

