<?php
/**
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 * @link          http://cakephp.org CakePHP(tm) Project
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
 */
namespace Migrations\Shell;

use Cake\Console\Shell;
use Migrations\MigrationsDispatcher;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;

/**
 * A wrapper shell for phinx migrations, used to inject our own
 * console actions so that database configuration already defined
 * for the application can be reused.
 *
 * @property \Migrations\Shell\Task\CreateTask $Create
 * @property \Migrations\Shell\Task\DumpTask $Dump
 * @property \Migrations\Shell\Task\MarkMigratedTask $MarkMigrated
 * @property \Migrations\Shell\Task\MigrateTask $Migrate
 * @property \Migrations\Shell\Task\RollbackTask $Rollback
 * @property \Migrations\Shell\Task\StatusTask $Status
 */
class MigrationsShell extends Shell
{
    /**
     * {@inheritDoc}
     */
    public $tasks = [
        'Migrations.Create',
        'Migrations.Dump',
        'Migrations.MarkMigrated',
        'Migrations.Migrate',
        'Migrations.Rollback',
        'Migrations.Status',
    ];

    /**
     * Array of arguments to run the shell with.
     *
     * @var array
     */
    public $argv = [];

    /**
     * Defines what options can be passed to the shell.
     * This is required because CakePHP validates the passed options
     * and would complain if something not configured here is present
     *
     * @return \Cake\Console\ConsoleOptionParser
     */
    public function getOptionParser()
    {
        return parent::getOptionParser()
            ->addOption('plugin', ['short' => 'p'])
            ->addOption('target', ['short' => 't'])
            ->addOption('connection', ['short' => 'c'])
            ->addOption('source', ['short' => 's'])
            ->addOption('seed')
            ->addOption('ansi')
            ->addOption('no-ansi')
            ->addOption('no-lock', ['boolean' => true])
            ->addOption('force', ['boolean' => true])
            ->addOption('version', ['short' => 'V'])
            ->addOption('no-interaction', ['short' => 'n'])
            ->addOption('template', ['short' => 't'])
            ->addOption('format', ['short' => 'f'])
            ->addOption('only', ['short' => 'o'])
            ->addOption('dry-run', ['short' => 'x'])
            ->addOption('exclude', ['short' => 'e']);
    }

    /**
     * Defines constants that are required by phinx to get running
     *
     * @return void
     */
    public function initialize()
    {
        if (!defined('PHINX_VERSION')) {
            define('PHINX_VERSION', 'UNKNOWN');
        }
        parent::initialize();
    }

    /**
     * This acts as a front-controller for phinx. It just instantiates the classes
     * responsible for parsing the command line from phinx and gives full control of
     * the rest of the flow to it.
     *
     * The input parameter of the ``MigrationDispatcher::run()`` method is manually built
     * in case a MigrationsShell is dispatched using ``Shell::dispatch()``.
     *
     * @return bool Success of the call.
     */
    public function main()
    {
        $app = $this->getApp();
        $input = new ArgvInput($this->argv);
        $app->setAutoExit(false);
        $exitCode = $app->run($input, $this->getOutput());

        if (in_array('-h', $this->argv) || in_array('--help', $this->argv)) {
            return $exitCode === 0;
        }

        if (
            isset($this->argv[1]) && in_array($this->argv[1], ['migrate', 'rollback']) &&
            !$this->params['no-lock'] &&
            $exitCode === 0
        ) {
            $dispatchCommand = 'migrations dump';
            if (!empty($this->params['connection'])) {
                $dispatchCommand .= ' -c ' . $this->params['connection'];
            }

            if (!empty($this->params['plugin'])) {
                $dispatchCommand .= ' -p ' . $this->params['plugin'];
            }

            $dumpExitCode = $this->dispatchShell($dispatchCommand);
        }

        if (isset($dumpExitCode) && $exitCode === 0 && $dumpExitCode !== 0) {
            $exitCode = 1;
        }

        return $exitCode === 0;
    }

    /**
     * Returns the MigrationsDispatcher the Shell will have to use
     *
     * @return \Migrations\MigrationsDispatcher
     */
    protected function getApp()
    {
        return new MigrationsDispatcher(PHINX_VERSION);
    }

    /**
     * Returns the instance of OutputInterface the MigrationsDispatcher will have to use.
     *
     * @return \Symfony\Component\Console\Output\ConsoleOutput
     */
    protected function getOutput()
    {
        return new ConsoleOutput();
    }

    /**
     * Override the default behavior to save the command called
     * in order to pass it to the command dispatcher
     *
     * {@inheritDoc}
     */
    public function runCommand($argv, $autoMethod = false, $extra = [])
    {
        array_unshift($argv, 'migrations');
        $this->argv = $argv;

        return parent::runCommand($argv, $autoMethod, $extra);
    }

    /**
     * Display the help in the correct format
     *
     * @param string $command The command to get help for.
     * @return int|bool|null Exit code or number of bytes written to stdout
     */
    protected function displayHelp($command)
    {
        return $this->main();
    }

    /**
     * {@inheritDoc}
     */
    // @codingStandardsIgnoreStart
    protected function _displayHelp($command)
    {
        // @codingStandardsIgnoreEnd
        return $this->displayHelp($command);
    }
}
