<?php
namespace Drush\Commands\core;

use Consolidation\SiteProcess\Util\Escape;
use Drush\Drush;
use Drush\Commands\DrushCommands;
use Drush\Exceptions\UserAbortException;
use Drush\Exec\ExecTrait;
use Drush\Log\LogLevel;
use Robo\LoadAllTasks;
use Robo\Contract\IOAwareInterface;
use Robo\Contract\BuilderAwareInterface;

class InitCommands extends DrushCommands implements BuilderAwareInterface, IOAwareInterface
{
    use LoadAllTasks;
    use ExecTrait;

    /**
     * Enrich the bash startup file with bash aliases and a smart command prompt.
     *
     * @command core:init
     *
     * @option $edit Open the new config file in an editor.
     * @option $add-path Always add Drush to the $PATH in the user's .bashrc
     *   file, even if it is already in the $PATH. Use --no-add-path to skip
     *   updating .bashrc with the Drush $PATH. Default is to update .bashrc
     *   only if Drush is not already in the $PATH.
     * @optionset_get_editor
     * @aliases init,core-init
     * @usage core-init --edit
     *   Enrich Bash and open drush config file in editor.
     * @usage core-init --edit --bg
     *   Return to shell prompt as soon as the editor window opens
     */
    public function initializeDrush($options = ['edit' => false, 'add-path' => ''])
    {
        $home = $this->getConfig()->home();
        $drush_config_dir = $home . "/.drush";
        $drush_config_file = $drush_config_dir . "/drush.yml";
        $drush_bashrc = $drush_config_dir . "/drush.bashrc";
        $drush_prompt = $drush_config_dir . "/drush.prompt.sh";
        $examples_dir = DRUSH_BASE_PATH . "/examples";
        $example_configuration = $examples_dir . "/example.drush.yml";
        $example_bashrc = $examples_dir . "/example.bashrc";
        $example_prompt = $examples_dir . "/example.prompt.sh";

        /**
         * That's right. Robo collections and tasks are usable in Drush.
         */
        $collection = $this->collectionBuilder();

        // Create a ~/.drush directory if it does not yet exist
        $collection->taskFilesystemStack()->mkdir($drush_config_dir);

        // If there is no ~/.drush/drush.yml, copy example there.
        if (!is_file($drush_config_file)) {
            $collection->taskWriteToFile($drush_config_file)->textFromFile($example_configuration);
            $collection->progressMessage('Copied example.drush.yml to {path}', ['path' => $drush_config_file], LogLevel::OK);
            $this->logger()->notice('Copied drush.yml to ' . $drush_config_file);
        } else {
            $this->logger()->debug($drush_config_file . ' already exists. Skip copy.');
        }

        // Decide whether we want to add our Bash commands to
        // ~/.bashrc or ~/.bash_profile, and create a task to
        // update it with includes of the various files we write,
        // as needed.  If it is, then we will add it to the collection.
        $bashrc = $this->findBashrc($home);
        $taskUpdateBashrc = $this->taskWriteToFile($bashrc)->append();

        // List of Drush bash configuration files, and
        // their source templates.
        $drushBashFiles = [
            $drush_bashrc => $example_bashrc,
            $drush_prompt => $example_prompt,
        ];

        // Mapping from Drush bash configuration files
        // to a description of what each one is.
        $drushBashFileDescriptions = [
            $drush_bashrc => 'Drush bash customizations',
            $drush_prompt => 'Drush prompt customizations',
        ];

        foreach ($drushBashFiles as $destFile => $sourceFile) {
            // If the destination file does not exist, then
            // copy the example file there.
            if (!is_file($destFile)) {
                $collection->taskWriteToFile($destFile)->textFromFile($sourceFile);
                $description = $drushBashFileDescriptions[$destFile];
                $collection->progressMessage('Copied {description} to {path}', ['description' => $description, 'path' => $destFile], LogLevel::OK);
                $pattern = basename($destFile);
                $taskUpdateBashrc->appendUnlessMatches("#$pattern#", "\n# Include $description.". $this->bashAddition($destFile));
            }
        }

        // If Drush is not in the $PATH, then figure out which
        // path to add so that Drush can be found globally.
        $add_path = $options['add-path'];
        if ((!self::programExists('drush') || $add_path) && ($add_path !== false)) {
            $drush_path = $this->findPathToDrush();
            $drush_path = preg_replace("%^" . preg_quote($home) . "/%", '$HOME/', $drush_path);
            $pattern = "$drush_path";
            $taskUpdateBashrc->appendUnlessMatches("#$pattern#", "\n# Path to Drush, added by 'drush init'.\nexport PATH=\"\$PATH:$drush_path\"\n\n");
        }

        $openEditor = false;
        if ($taskUpdateBashrc->wouldChange()) {
            if ($this->io()->confirm(dt("Modify !file to include Drush configuration files?", ['!file' => $bashrc]))) {
                $collection->addTask($taskUpdateBashrc);
                $collection->progressMessage('Updated bash configuration file {path}', ['path' => $bashrc], LogLevel::OK);
                $collection->progressMessage('Start a new shell in order to experience the improvements (e.g. `{shell}`).', ['shell' => 'bash'], LogLevel::OK);
                $openEditor = $options['edit'];
            } else {
                throw new UserAbortException();
            }
        } else {
            $collection->progressMessage('No code added to {path}', ['path' => $bashrc]);
        }
        $result = $collection->run();

        if ($result->wasSuccessful() && $openEditor) {
            $editor = drush_get_editor();
            // A bit awkward due to backward compat.
            $cmd = sprintf($editor, Escape::shellArg($drush_config_file));
            $process = $this->processManager()->shell($cmd);
            $process->setTty(true);
            $process->mustRun();
        }

        return $result;
    }

    /**
     * Determine which .bashrc file is best to use on this platform.
     */
    protected function findBashrc($home)
    {
        # Set a default value in case nothing else exists.
        $file = $home . '/.bashrc';

        if (!empty($_ENV['SHELL']) && strstr($_ENV['SHELL'], 'zsh')) {
            $file = $home . '/.zshrc';
        } elseif (file_exists($home . '/.bash_profile')) {
            $file = $home . '/.bash_profile';
        } elseif (file_exists($home . '/.bashrc')) {
            $file = $home . '/.bashrc';
        } elseif (file_exists($home . '/.profile')) {
            $file = $home . '/.profile';
        } elseif (file_exists($home . '/.functions')) {
            $file = $home . '/.functions';
        }

        return $file;
    }

    /**
     * Determine where Drush is located, so that we can add
     * that location to the $PATH.
     */
    protected function findPathToDrush()
    {
        // First test: is Drush inside a vendor directory?
        // Does vendor/bin exist?  If so, use that.  We do
        // not have a good way to locate the 'bin' directory
        // if it has been relocated in the composer.json config
        // section.
        if ($vendor_pos = strpos(DRUSH_BASE_PATH, "/vendor/")) {
            $vendor_dir = substr(DRUSH_BASE_PATH, 0, $vendor_pos + 7);
            $vendor_bin = $vendor_dir . '/bin';
            if (is_dir($vendor_bin)) {
                return $vendor_bin;
            }
        }

        // Fallback is to use the directory that Drush is in.
        return DRUSH_BASE_PATH;
    }

    protected function bashAddition($file)
    {
        return <<<EOD

if [ -f "$file" ] ; then
  source $file
fi

EOD;
    }
}
