<?php
/**
 * Copyright 2017 Adobe
 * All Rights Reserved.
 */
namespace Magento\Deploy\Strategy;

use Magento\Deploy\Console\DeployStaticOptions as Options;
use Magento\Deploy\Package\Package;
use Magento\Deploy\Package\PackagePool;
use Magento\Deploy\Process\Queue;
use function array_key_exists;

/**
 * Quick deployment strategy implementation
 */
class QuickDeploy implements StrategyInterface
{
    /**
     * @var PackagePool
     */
    private $packagePool;

    /**
     * @var Queue
     */
    private $queue;

    /**
     * @var array
     */
    private $baseLocalePackages = [];

    /**
     * QuickDeploy constructor
     *
     * @param PackagePool $packagePool
     * @param Queue $queue
     */
    public function __construct(
        PackagePool $packagePool,
        Queue $queue
    ) {
        $this->packagePool = $packagePool;
        $this->queue = $queue;
    }

    /**
     * @inheritdoc
     */
    public function deploy(array $options)
    {
        $groupedPackages = $deployPackages = [];
        $packages = $this->packagePool->getPackagesForDeployment($options);
        foreach ($packages as $package) {
            if ($package->isVirtual()) {
                // skip packages which can not be referenced directly
                continue;
            }
            $level = $this->getInheritanceLevel($package);
            $groupedPackages[$level][$package->getPath()] = $package;
        }

        ksort($groupedPackages);

        foreach ($groupedPackages as $level => $levelPackages) {
            $this->preparePackages($level, $levelPackages);
        }

        $parentCompilationRequested = $options[Options::NO_PARENT] !== true;
        $includeThemesMap = array_flip($options[Options::THEME] ?? []);
        $excludeThemesMap = array_flip($options[Options::EXCLUDE_THEME] ?? []);

        foreach ($groupedPackages as $levelPackages) {
            foreach ($levelPackages as $package) {
                if ($parentCompilationRequested
                    || $this->canDeployTheme($package->getTheme(), $includeThemesMap, $excludeThemesMap)) {
                    $this->queue->add($package);
                    $deployPackages[] = $package;
                }
            }
        }

        $this->queue->process();

        return $deployPackages;
    }

    /**
     * Prepare packages before deploying
     *
     * @param int $level
     * @param Package[] $levelPackages
     * @return void
     */
    private function preparePackages(int $level, array $levelPackages): void
    {
        foreach ($levelPackages as $package) {
            $package->aggregate();
            if ($level > 1) {
                $parentPackage = null;
                $packageId = $package->getArea() . '/' . $package->getTheme();
                // use base package if it is not the same as current
                if (isset($this->baseLocalePackages[$packageId])
                    && $package !== $this->baseLocalePackages[$packageId]
                ) {
                    $parentPackage = $this->baseLocalePackages[$packageId];
                } else {
                    $parentPackages = $package->getParentPackages();
                    foreach (array_reverse($parentPackages) as $ancestorPackage) {
                        if (!$ancestorPackage->isVirtual()) {
                            $parentPackage = $ancestorPackage;
                            break;
                        }
                        if ($parentPackage === null) {
                            $parentPackage = $ancestorPackage;
                        }
                    }
                }
                if ($parentPackage) {
                    $package->setParent($parentPackage);
                }
            }
        }
    }

    /**
     * Calculate proper inheritance level for the given package
     *
     * @param Package $package
     * @return int
     */
    private function getInheritanceLevel(Package $package): int
    {
        $level = $package->getInheritanceLevel();
        $packageId = $package->getArea() . '/' . $package->getTheme();
        if (!isset($this->baseLocalePackages[$packageId])) {
            $this->baseLocalePackages[$packageId] = $package;
        } else {
            ++$level;
        }
        return $level;
    }

    /**
     * Verify if specified theme should be deployed
     *
     * @param string $theme
     * @param array $includedThemesMap
     * @param array $excludedEntitiesMap
     * @return bool
     */
    private function canDeployTheme(string $theme, array $includedThemesMap, array $excludedEntitiesMap): bool
    {
        $includesAllThemes = array_key_exists('all', $includedThemesMap);
        $excludesNoneThemes = array_key_exists('none', $excludedEntitiesMap);

        if ($includesAllThemes && $excludesNoneThemes) {
            return true;
        } elseif (!$excludesNoneThemes) {
            return !array_key_exists($theme, $excludedEntitiesMap);
        } elseif (!$includesAllThemes) {
            return array_key_exists($theme, $includedThemesMap);
        }

        return true;
    }
}
