<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle 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
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

namespace Moodle\BehatExtension\ServiceContainer;

use Behat\Behat\Definition\ServiceContainer\DefinitionExtension;
use Behat\Behat\EventDispatcher\ServiceContainer\EventDispatcherExtension;
use Behat\Behat\Gherkin\ServiceContainer\GherkinExtension;
use Behat\Behat\Tester\ServiceContainer\TesterExtension;
use Behat\Testwork\Cli\ServiceContainer\CliExtension;
use Behat\Testwork\Output\ServiceContainer\OutputExtension;
use Behat\Testwork\ServiceContainer\Extension as ExtensionInterface;
use Behat\Testwork\ServiceContainer\ExtensionManager;
use Behat\Testwork\ServiceContainer\ServiceProcessor;
use Behat\Testwork\Specification\ServiceContainer\SpecificationExtension;
use Behat\Testwork\Suite\ServiceContainer\SuiteExtension;
use Moodle\BehatExtension\Driver\WebDriverFactory;
use Moodle\BehatExtension\Output\Formatter\MoodleProgressFormatterFactory;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Reference;

// phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod

/**
 * Behat extension for moodle
 *
 * Provides multiple features directory loading (Gherkin\Loader\MoodleFeaturesSuiteLoader
 *
 * @package core
 * @copyright 2016 Rajesh Taneja <rajesh@moodle.com>
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class BehatExtension implements ExtensionInterface {
    /** @var string Extension configuration ID */
    const MOODLE_ID = 'moodle';

    /** @var string Gherkin ID */
    const GHERKIN_ID = 'gherkin';

    /** @var ServiceProcessor */
    private $processor;

    /**
     * Initializes compiler pass.
     *
     * @param null|ServiceProcessor $processor
     */
    public function __construct(ServiceProcessor $processor = null) {
        $this->processor = $processor ? : new ServiceProcessor();
    }

    /**
     * Loads moodle specific configuration.
     *
     * @param ContainerBuilder $container ContainerBuilder instance
     * @param array            $config    Extension configuration hash (from behat.yml)
     */
    public function load(ContainerBuilder $container, array $config) {
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/services'));
        $loader->load('core.xml');

        // Getting the extension parameters.
        $container->setParameter('behat.moodle.parameters', $config);

        // Load moodle progress formatter.
        $moodleprogressformatter = new MoodleProgressFormatterFactory();
        $moodleprogressformatter->buildFormatter($container);

        // Load custom step tester event dispatcher.
        $this->loadEventDispatchingStepTester($container);

        // Load chained step tester.
        $this->loadChainedStepTester($container);

        // Load step count formatter.
        $this->loadMoodleListFormatter($container);

        // Load step count formatter.
        $this->loadMoodleStepcountFormatter($container);

        // Load screenshot formatter.
        $this->loadMoodleScreenshotFormatter($container);

        // Load namespace alias.
        $this->alias_old_namespaces();

        // Load skip passed controller and list locator.
        $this->loadSkipPassedController($container, $config['passed_cache']);
        $this->loadFilesystemSkipPassedScenariosListLocator($container);
    }

    /**
     * Loads moodle List formatter.
     *
     * @param ContainerBuilder $container
     */
    protected function loadMoodleListFormatter(ContainerBuilder $container) {
        $definition = new Definition('Moodle\BehatExtension\Output\Formatter\MoodleListFormatter', [
            'moodle_list',
            'List all scenarios. Use with --dry-run',
            ['stepcount' => false],
            $this->createOutputPrinterDefinition()
        ]);
        $definition->addTag(OutputExtension::FORMATTER_TAG, ['priority' => 101]);
        $container->setDefinition(OutputExtension::FORMATTER_TAG . '.moodle_list', $definition);
    }

    /**
     * Loads moodle Step count formatter.
     *
     * @param ContainerBuilder $container
     */
    protected function loadMoodleStepcountFormatter(ContainerBuilder $container) {
        $definition = new Definition('Moodle\BehatExtension\Output\Formatter\MoodleStepcountFormatter', [
            'moodle_stepcount',
            'Count steps in feature files. Use with --dry-run',
            ['stepcount' => false],
            $this->createOutputPrinterDefinition()
        ]);
        $definition->addTag(OutputExtension::FORMATTER_TAG, ['priority' => 101]);
        $container->setDefinition(OutputExtension::FORMATTER_TAG . '.moodle_stepcount', $definition);
    }

    /**
     * Loads moodle screenshot formatter.
     *
     * @param ContainerBuilder $container
     */
    protected function loadMoodleScreenshotFormatter(ContainerBuilder $container) {
        $definition = new Definition('Moodle\BehatExtension\Output\Formatter\MoodleScreenshotFormatter', [
            'moodle_screenshot',
            // phpcs:ignore Generic.Files.LineLength.TooLong
            'Take screenshot of all steps. Use --format-settings \'{"formats": "html,image"}\' to get specific o/p type',
            ['formats' => 'html,image'],
            $this->createOutputPrinterDefinition()
        ]);
        $definition->addTag(OutputExtension::FORMATTER_TAG, ['priority' => 102]);
        $container->setDefinition(OutputExtension::FORMATTER_TAG . '.moodle_screenshot', $definition);
    }

    /**
     * Creates output printer definition.
     *
     * @return Definition
     */
    protected function createOutputPrinterDefinition() {
        return new Definition('Behat\Testwork\Output\Printer\StreamOutputPrinter', [
            new Definition('Behat\Behat\Output\Printer\ConsoleOutputFactory'),
        ]);
    }

    /**
     * Loads skip passed controller.
     *
     * @param ContainerBuilder $container
     * @param null|string      $cachepath
     */
    protected function loadSkipPassedController(ContainerBuilder $container, $cachepath) {
        $definition = new Definition('Moodle\BehatExtension\Tester\Cli\SkipPassedController', [
            new Reference(EventDispatcherExtension::DISPATCHER_ID),
            $cachepath,
            $container->getParameter('paths.base')
        ]);
        $definition->addTag(CliExtension::CONTROLLER_TAG, ['priority' => 200]);
        $container->setDefinition(CliExtension::CONTROLLER_TAG . '.passed', $definition);
    }

    /**
     * Loads filesystem passed scenarios list locator.
     *
     * @param ContainerBuilder $container
     */
    private function loadFilesystemSkipPassedScenariosListLocator(ContainerBuilder $container) {
        $definition = new Definition('Moodle\BehatExtension\Locator\FilesystemSkipPassedListLocator', [
            new Reference(self::GHERKIN_ID)
        ]);
        $definition->addTag(SpecificationExtension::LOCATOR_TAG, ['priority' => 50]);
        $container->setDefinition(
            SpecificationExtension::LOCATOR_TAG . '.filesystem_skip_passed_scenarios_list',
            $definition
        );
    }

    /**
     * Loads definition printers.
     *
     * @param ContainerBuilder $container
     */
    private function loadDefinitionPrinters(ContainerBuilder $container) {
        $definition = new Definition('Moodle\BehatExtension\Definition\Printer\ConsoleDefinitionInformationPrinter', [
            new Reference(CliExtension::OUTPUT_ID),
            new Reference(DefinitionExtension::PATTERN_TRANSFORMER_ID),
            new Reference(DefinitionExtension::DEFINITION_TRANSLATOR_ID),
            new Reference(GherkinExtension::KEYWORDS_ID)
        ]);
        $container->removeDefinition('definition.information_printer');
        $container->setDefinition('definition.information_printer', $definition);
    }

    /**
     * Loads definition controller.
     *
     * @param ContainerBuilder $container
     */
    private function loadController(ContainerBuilder $container) {
        $definition = new Definition('Moodle\BehatExtension\Definition\Cli\AvailableDefinitionsController', [
            new Reference(SuiteExtension::REGISTRY_ID),
            new Reference(DefinitionExtension::WRITER_ID),
            new Reference('definition.list_printer'),
            new Reference('definition.information_printer')
        ]);
        $container->removeDefinition(CliExtension::CONTROLLER_TAG . '.available_definitions');
        $container->setDefinition(CliExtension::CONTROLLER_TAG . '.available_definitions', $definition);
    }

    /**
     * Loads chained step tester.
     *
     * @param ContainerBuilder $container
     */
    protected function loadChainedStepTester(ContainerBuilder $container) {
        // Chained steps.
        $definition = new Definition('Moodle\BehatExtension\EventDispatcher\Tester\ChainedStepTester', [
            new Reference(TesterExtension::STEP_TESTER_ID),
        ]);
        $definition->addTag(TesterExtension::STEP_TESTER_WRAPPER_TAG, ['priority' => 100]);
        $container->setDefinition(TesterExtension::STEP_TESTER_WRAPPER_TAG . '.substep', $definition);
    }

    /**
     * Loads event-dispatching step tester.
     *
     * @param ContainerBuilder $container
     */
    protected function loadEventDispatchingStepTester(ContainerBuilder $container) {
        $definition = new Definition('Moodle\BehatExtension\EventDispatcher\Tester\MoodleEventDispatchingStepTester', [
            new Reference(TesterExtension::STEP_TESTER_ID),
            new Reference(EventDispatcherExtension::DISPATCHER_ID)
        ]);
        $definition->addTag(TesterExtension::STEP_TESTER_WRAPPER_TAG, ['priority' => -9999]);
        $container->setDefinition(TesterExtension::STEP_TESTER_WRAPPER_TAG . '.event_dispatching', $definition);
    }

    /**
     * Setups configuration for current extension.
     *
     * @param ArrayNodeDefinition $builder
     */
    public function configure(ArrayNodeDefinition $builder) {
        // phpcs:disable PEAR.WhiteSpace.ObjectOperatorIndent.Incorrect
        $builder->children()
            ->arrayNode('capabilities')
                ->useAttributeAsKey('key')
                ->prototype('variable')->end()
                ->end()
            ->arrayNode('steps_definitions')
                ->useAttributeAsKey('key')
                ->prototype('variable')->end()
                ->end()
            ->scalarNode('moodledirroot')
                ->defaultNull()
                ->end()
                ->scalarNode('passed_cache')
            ->info('Sets the passed cache path')
                ->defaultValue(
                    is_writable(sys_get_temp_dir())
                        ? sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'behat_passed_cache'
                        : null
                )
                ->end()
            ->end()
        ->end();
        // phpcs:enable PEAR.WhiteSpace.ObjectOperatorIndent.Incorrect
    }

    /**
     * Returns the extension config key.
     *
     * @return string
     */
    public function getConfigKey() {
        return self::MOODLE_ID;
    }

    /**
     * Initializes other extensions.
     *
     * This method is called immediately after all extensions are activated but
     * before any extension `configure()` method is called. This allows extensions
     * to hook into the configuration of other extensions providing such an
     * extension point.
     *
     * @param ExtensionManager $extensionmanager
     */
    public function initialize(ExtensionManager $extensionmanager) {
        if (null !== $minkextension = $extensionmanager->getExtension('mink')) {
            $minkextension->registerDriverFactory(new WebDriverFactory());
        }
    }

    /**
     * You can modify the container here before it is dumped to PHP code.
     *
     * @param ContainerBuilder $container
     */
    public function process(ContainerBuilder $container) {
        // Load controller for definition printing.
        $this->loadDefinitionPrinters($container);
        $this->loadController($container);
    }

    /**
     * Alias old namespace of given. when and then for BC.
     */
    private function alias_old_namespaces() {
        class_alias('Moodle\\BehatExtension\\Context\\Step\\Given', 'Behat\\Behat\\Context\\Step\\Given', true);
        class_alias('Moodle\\BehatExtension\\Context\\Step\\When', 'Behat\\Behat\\Context\\Step\\When', true);
        class_alias('Moodle\\BehatExtension\\Context\\Step\\Then', 'Behat\\Behat\\Context\\Step\\Then', true);
    }
}
