<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

namespace Magento\Deploy\Model;

use Magento\Framework\App\View\Deployment\Version\StorageInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Magento\Deploy\Console\Command\DeployStaticOptionsInterface as Options;
use Magento\Deploy\Model\Deploy\TemplateMinifier;
use Magento\Framework\App\State;

class DeployManager
{
    /**
     * @var array
     */
    private $packages = [];

    /**
     * @var OutputInterface
     */
    private $output;

    /**
     * @var array
     */
    private $options;

    /**
     * @var StorageInterface
     */
    private $versionStorage;

    /**
     * @var DeployStrategyProviderFactory
     */
    private $deployStrategyProviderFactory;

    /**
     * @var ProcessQueueManagerFactory
     */
    private $processQueueManagerFactory;

    /**
     * @var TemplateMinifier
     */
    private $templateMinifier;

    /**
     * @var bool
     */
    private $idDryRun;

    /**
     * @var State
     */
    private $state;

    /**
     * @param OutputInterface $output
     * @param StorageInterface $versionStorage
     * @param DeployStrategyProviderFactory $deployStrategyProviderFactory
     * @param ProcessQueueManagerFactory $processQueueManagerFactory
     * @param TemplateMinifier $templateMinifier
     * @param State $state
     * @param array $options
     */
    public function __construct(
        OutputInterface $output,
        StorageInterface $versionStorage,
        DeployStrategyProviderFactory $deployStrategyProviderFactory,
        ProcessQueueManagerFactory $processQueueManagerFactory,
        TemplateMinifier $templateMinifier,
        State $state,
        array $options
    ) {
        $this->output = $output;
        $this->options = $options;
        $this->versionStorage = $versionStorage;
        $this->deployStrategyProviderFactory = $deployStrategyProviderFactory;
        $this->processQueueManagerFactory = $processQueueManagerFactory;
        $this->templateMinifier = $templateMinifier;
        $this->state = $state;
        $this->idDryRun = !empty($this->options[Options::DRY_RUN]);
    }

    /**
     * Add package tie to area and theme
     *
     * @param string $area
     * @param string $themePath
     * @param string $locale
     * @return void
     */
    public function addPack($area, $themePath, $locale)
    {
        $this->packages[$area . '-' . $themePath][$locale] = [$area, $themePath];
    }

    /**
     * Deploy local packages with chosen deploy strategy
     * @return int
     */
    public function deploy()
    {
        if ($this->idDryRun) {
            $this->output->writeln('Dry run. Nothing will be recorded to the target directory.');
        } else {
            $version = (new \DateTime())->getTimestamp();
            $this->versionStorage->save($version);
        }

        /** @var DeployStrategyProvider $strategyProvider */
        $strategyProvider = $this->deployStrategyProviderFactory->create(
            ['output' => $this->output, 'options' => $this->options]
        );

        if ($this->isCanBeParalleled()) {
            $result = $this->runInParallel($strategyProvider);
        } else {
            $result = 0;
            foreach ($this->packages as $package) {
                $locales = array_keys($package);
                list($area, $themePath) = current($package);
                foreach ($strategyProvider->getDeployStrategies($area, $themePath, $locales) as $locale => $strategy) {
                    $result |= $this->state->emulateAreaCode(
                        $area,
                        [$strategy, 'deploy'],
                        [$area, $themePath, $locale]
                    );
                }
            }
        }

        $this->minifyTemplates();
        if (!$this->idDryRun) {
            $this->output->writeln("New version of deployed files: {$version}");
        }

        return $result;
    }

    /**
     * @return void
     */
    private function minifyTemplates()
    {
        $noHtmlMinify = isset($this->options[Options::NO_HTML_MINIFY]) ? $this->options[Options::NO_HTML_MINIFY] : null;
        if (!$noHtmlMinify && !$this->idDryRun) {
            $this->output->writeln('=== Minify templates ===');
            $minified = $this->templateMinifier->minifyTemplates();
            $this->output->writeln("\nSuccessful: {$minified} files modified\n---\n");
        }
    }

    /**
     * @param DeployStrategyProvider $strategyProvider
     * @return int
     */
    private function runInParallel($strategyProvider)
    {
        $processQueueManager = $this->processQueueManagerFactory->create(
            ['maxProcesses' => $this->getProcessesAmount()]
        );
        foreach ($this->packages as $package) {
            $locales = array_keys($package);
            list($area, $themePath) = current($package);
            $baseStrategy = null;
            $dependentStrategy = [];
            foreach ($strategyProvider->getDeployStrategies($area, $themePath, $locales) as $locale => $strategy) {
                $deploymentFunc = function () use ($area, $themePath, $locale, $strategy) {
                    return $this->state->emulateAreaCode($area, [$strategy, 'deploy'], [$area, $themePath, $locale]);
                };
                if (null === $baseStrategy) {
                    $baseStrategy = $deploymentFunc;
                } else {
                    $dependentStrategy[] = $deploymentFunc;
                }

            }
            $processQueueManager->addTaskToQueue($baseStrategy, $dependentStrategy);
        }

        return $processQueueManager->process();
    }

    /**
     * @return bool
     */
    private function isCanBeParalleled()
    {
        return function_exists('pcntl_fork') && $this->getProcessesAmount() > 1;
    }

    /**
     * @return int
     */
    private function getProcessesAmount()
    {
        return isset($this->options[Options::JOBS_AMOUNT]) ? (int)$this->options[Options::JOBS_AMOUNT] : 0;
    }
}