#!/usr/bin/env php
<?php
/**
 * @author    nystudio107
 * @copyright Copyright (c) 2017 nystudio107
 * @link      https://nystudio107.com/
 * @package   nystudio107/craft
 * @since     1.0.0
 * @license   MIT
 */

use yii\helpers\Console;

use mikehaertl\shellcommand\Command as ShellCommand;

const INSTALL_PLUGINS = [
    'cookies',
    'eager-beaver',
    'image-optimize',
    'minify',
    'typogrify',
];

const CRAFT_SCRIPTS_SETUP = [
    'srclink'      => 'vendor/nystudio107/craft-scripts/scripts',
    'destlink'     => 'scripts',
    'srcpath'      => 'scripts/craft3-example.env.sh',
    'destpath'     => '.env.sh',
    'replacements' => [
        'GLOBAL_DB_TABLE_PREFIX=""' => [
            'substr'  => '""',
            'env'     => 'DB_TABLE_PREFIX',
            'default' => '',
        ],
        'GLOBAL_DB_DRIVER="mysql"' => [
            'substr'  => '"mysql"',
            'env'     => 'DB_DRIVER',
            'default' => 'mysql',
        ],
        'LOCAL_ROOT_PATH="REPLACE_ME"' => [
            'substr'  => '"REPLACE_ME"',
            'env'     => '',
            'default' => '',
        ],
        'LOCAL_ASSETS_PATH=${LOCAL_ROOT_PATH}"REPLACE_ME"' => [
            'substr'  => '"REPLACE_ME"',
            'env'     => '',
            'default' => 'web/assets/',
        ],
        'LOCAL_DB_NAME="REPLACE_ME"' => [
            'substr'  => '"REPLACE_ME"',
            'env'     => 'DB_DATABASE',
            'default' => 'REPLACE_ME',
        ],
        'LOCAL_DB_PASSWORD="REPLACE_ME"' => [
            'substr'  => '"REPLACE_ME"',
            'env'     => 'DB_PASSWORD',
            'default' => 'REPLACE_ME',
        ],
        'LOCAL_DB_USER="REPLACE_ME"' => [
            'substr'  => '"REPLACE_ME"',
            'env'     => 'DB_USER',
            'default' => 'REPLACE_ME',
        ],
        'LOCAL_DB_HOST="localhost"' => [
            'substr'  => '"localhost"',
            'env'     => 'DB_SERVER',
            'default' => 'localhost',
        ],
        'LOCAL_DB_PORT="3306"' => [
            'substr'  => '"3306"',
            'env'     => 'DB_PORT',
            'default' => '3306',
        ],
        'LOCAL_DB_SCHEMA="public"' => [
            'substr'  => '"public"',
            'env'     => 'DB_SCHEMA',
            'default' => 'public',
        ],
    ],
];

const CRAFT_MULTI_ENVIRONMENT_SETUP = [
    [
        'srcpath'      => 'vendor/nystudio107/craft3-multi-environment/config/db.php',
        'destpath'     => 'config/db.php',
        'replacements' => [
        ],
    ],
    [
        'srcpath'      => 'vendor/nystudio107/craft3-multi-environment/config/general.php',
        'destpath'     => 'config/general.php',
        'replacements' => [
        ],
    ],
    [
        'srcpath'      => 'vendor/nystudio107/craft3-multi-environment/config/volumes.php',
        'destpath'     => 'config/volumes.php',
        'replacements' => [
        ],
    ],
    [
        'srcpath'      => 'vendor/nystudio107/craft3-multi-environment/web/index.php',
        'destpath'     => 'web/index.php',
        'replacements' => [
        ],
    ],
    [
        'srcpath'      => 'vendor/nystudio107/craft3-multi-environment/craft',
        'destpath'     => 'craft',
        'replacements' => [
        ],
    ],
    [
        'srcpath'      => 'vendor/nystudio107/craft3-multi-environment/example.env.php',
        'destpath'     => '.env.php',
        'replacements' => [
            'REPLACE_ME_CRAFT_ENVIRONMENT' => [
                'substr'  => '',
                'env'     => '',
                'default' => 'local',
            ],
            'REPLACE_ME_DB_DRIVER'         => [
                'substr'  => '',
                'env'     => 'DB_DRIVER',
                'default' => 'mysql',
            ],
            'REPLACE_ME_DB_SERVER'         => [
                'substr'  => '',
                'env'     => 'DB_SERVER',
                'default' => 'localhost',
            ],
            'REPLACE_ME_DB_USER'           => [
                'substr'  => '',
                'env'     => 'DB_USER',
                'default' => 'REPLACE_ME',
            ],
            'REPLACE_ME_DB_PASSWORD'       => [
                'substr'  => '',
                'env'     => 'DB_PASSWORD',
                'default' => 'REPLACE_ME',
            ],
            'REPLACE_ME_DB_DATABASE'       => [
                'substr'  => '',
                'env'     => 'DB_DATABASE',
                'default' => 'REPLACE_ME',
            ],
            'REPLACE_ME_DB_SCHEMA'         => [
                'substr'  => '',
                'env'     => 'DB_SCHEMA',
                'default' => 'public',
            ],
            'REPLACE_ME_DB_TABLE_PREFIX'   => [
                'substr'  => '',
                'env'     => 'DB_TABLE_PREFIX',
                'default' => '',
            ],
            'REPLACE_ME_DB_PORT'           => [
                'substr'  => '',
                'env'     => 'DB_PORT',
                'default' => '3306',
            ],
            'REPLACE_ME_SECURITY_KEY'      => [
                'substr'  => '',
                'env'     => 'SECURITY_KEY',
                'default' => 'REPLACE_ME',
            ],
        ],
    ],
];

// Set path constants
define('CRAFT_BASE_PATH', __DIR__);
define('CRAFT_VENDOR_PATH', CRAFT_BASE_PATH.'/vendor');

// Load Composer's autoloader
require_once CRAFT_VENDOR_PATH.'/autoload.php';

// Load the .env file Craft created
if (file_exists(CRAFT_BASE_PATH.'/.env')) {
    $dotEnv = new Dotenv\Dotenv(CRAFT_BASE_PATH);
    $dotEnv->load();
}

// By default, run the setup script
if (empty($argv[1])) {
    setupNysCraft();
} else {
    // See what command we were passed in
    switch ($argv[1]) {
        case 'welcome':
            // Display a welcome message
            welcomeNysCraft();
            break;

        default:
            // Set up all the things!
            setupNysCraft();
            break;
    }
}

/**
 * Display a welcome message
 */
function welcomeNysCraft()
{
    outputString(PHP_EOL.'To set up your Craft install, from your project directory, run:', Console::FG_YELLOW);
    $script = './craft setup';
    outputString(PHP_EOL.'    '.$script, Console::FG_GREEN);
    outputString(PHP_EOL.'Then to set up Craft 3 Multi-Environment & Craft-Scripts, run:', Console::FG_YELLOW);
    $script = './nys-setup';
    outputString(PHP_EOL.'    '.$script, Console::FG_GREEN);
    outputString(PHP_EOL.'Your setup is not complete until you run these two commands.', Console::FG_YELLOW);
}

/**
 * Set up all the things!
 */
function setupNysCraft()
{
    // Say hello
    outputString(PHP_EOL.'Welcome to nys-setup', Console::FG_YELLOW);
    // Set up Craft-Scripts
    setupCraftScripts();
    // Setup Craft 3 Multi-Environment
    setupCraftMultiEnvironment();
    // Install the default plugins
    installPlugins();
    // Install the NodeJS packages
    installNodePackages();
    // Say goodbye
    outputString(PHP_EOL.'Setup complete. Have a nice day!', Console::FG_YELLOW);
}

/**
 * Setup Craft 3 Multi-Environment
 */
function setupCraftMultiEnvironment()
{
    // Set up Craft 3 Multi-Environment if the file .env.php doesn't exist already
    outputString(PHP_EOL.'Setting up Craft 3 Multi-Environment', Console::FG_YELLOW);
    if (!file_exists('.env.php')) {
        // Create the default Craft Multi-Environment setup
        foreach (CRAFT_MULTI_ENVIRONMENT_SETUP as $fileInfo) {
            if (copyFile($fileInfo['srcpath'], $fileInfo['destpath'])) {
                $replacements = $fileInfo['replacements'];
                if (!empty($replacements)) {
                    replaceInFile($fileInfo['destpath'], $replacements);
                }
            }
        }
    } else {
        outputString('### .env.php file already exists, exiting...', Console::FG_RED);
    }
    outputString(PHP_EOL.'You will still need to set up your REMOTE_ settings in .env.sh', Console::FG_GREEN);
}

/**
 * Set up Craft-Scripts
 */
function setupCraftScripts()
{
    // Set up Craft-Scripts if the file scripts/.env.sh doesn't exist already
    outputString(PHP_EOL.'Setting up Craft-Scripts', Console::FG_YELLOW);
    if (!file_exists('scripts/.env.sh')) {
        $fileInfo = CRAFT_SCRIPTS_SETUP;
        // Create the scripts symlink
        createSymLink($fileInfo['srclink'], $fileInfo['destlink']);
        if (!file_exists('.env.sh')) {
            // Create the default .env.sh
            if (copyFile($fileInfo['srcpath'], $fileInfo['destpath'])) {
                $replacements = $fileInfo['replacements'];
                if (!empty($replacements)) {
                    // We need to swap in some dynamic variables for specific settings replacements
                    $replacements['LOCAL_ROOT_PATH="REPLACE_ME"']['default'] = CRAFT_BASE_PATH.DIRECTORY_SEPARATOR;
                    replaceInFile($fileInfo['destpath'], $replacements);
                }
            }
        }
        // Create the scripts/.env.sh symlink
        createSymLink(CRAFT_BASE_PATH.DIRECTORY_SEPARATOR.'.env.sh', 'scripts/.env.sh');
    } else {
        outputString('### scripts/.env.sh file already exists, exiting...', Console::FG_RED);
    }

}

/**
 * Install the default plugins
 */
function installPlugins()
{
    outputString(PHP_EOL.'Installing plugins', Console::FG_YELLOW);
    $installPluginCmd = './craft install/plugin ';
    foreach (INSTALL_PLUGINS as $pluginHandle) {
        outputString('- installing plugin '.$pluginHandle);
        executeShellCommand($installPluginCmd . $pluginHandle);
    }
}

function installNodePackages()
{
    $command = '';
    if (shellCommandExists('npm')) {
        $command = 'npm install';
    }
    if (shellCommandExists('yarn')) {
        $command = 'yarn';
    }

    outputString(PHP_EOL.'Installing NodeJS packages via '.$command. ' (this may take a while)', Console::FG_YELLOW);

    if (!empty($command)) {
        $result = executeShellCommand($command);
        outputString($result);
    } else {
        outputString('### unable to install NodeJS packages, yarn/npm not found', Console::FG_RED);
    }
}

/**
 * Output a string to the console, using optional $args to color it, if supported
 *
 * @param string $string
 *
 * @return mixed
 */
function outputString($string)
{
    $stream = \STDOUT;
    if (Console::streamSupportsAnsiColors($stream)) {
        $args = func_get_args();
        array_shift($args);
        $string = Console::ansiFormat($string, $args);
    }

    return Console::stdout($string.PHP_EOL);
}

/**
 * Create a symlink from $srcPath to $destPath
 *
 * @param string $srcPath
 * @param string $destPath
 *
 * @return bool
 */
function createSymLink(string $srcPath, string $destPath)
{
    $result = @symlink($srcPath, $destPath);
    if ($result) {
        outputString('- created symlink from '.$srcPath.' to '.$destPath);
    } else {
        outputString('### error creating symlink from '.$srcPath.' to '.$destPath, Console::FG_RED);
    }

    return $result;
}

/**
 * Copy the $srcPath file to $destPath
 *
 * @param string $srcPath
 * @param string $destPath
 *
 * @return bool
 */
function copyFile(string $srcPath, string $destPath)
{
    $result = @copy($srcPath, $destPath);
    if ($result) {
        outputString('- copied '.$srcPath.' to '.$destPath);
    } else {
        outputString('### error copying '.$srcPath.' to '.$destPath, Console::FG_RED);
    }

    return $result;
}

/**
 * Replace strings in $filePath and write out the modified file
 *
 * @param string $filePath
 * @param array  $replacements
 */
function replaceInFile(string $filePath, array $replacements)
{
    if (!empty($replacements)) {
        outputString('- setting up '.$filePath);
        $fileContents = file_get_contents($filePath);
        foreach ($replacements as $findValue => $params) {
            $replaceValue = empty($params['env']) ? $params['default'] : getenv($params['env']) ?? $params['default'];
            if (!empty($params['substr'])) {
                $subStr = $params['substr'];
                $wrapChar = '';
                // If the $subStr is quoted, quote the $replaceValue, too
                if ($subStr[0] == "'" || $subStr[0] == '"') {
                    $wrapChar = $subStr[0];
                }
                $replaceValue = str_replace($subStr, $wrapChar.$replaceValue.$wrapChar, $findValue);
            }
            $fileContents = str_replace($findValue, $replaceValue, $fileContents);
        }
        // Write the file out
        file_put_contents($filePath, $fileContents);
    }
}

/**
 * Execute a shell command
 *
 * @param string $command
 *
 * @return string
 */
function executeShellCommand(string $command): string
{
    // Create the shell command
    $shellCommand = new ShellCommand();
    $shellCommand->setCommand($command);

    // If we don't have proc_open, maybe we've got exec
    if (!function_exists('proc_open') && function_exists('exec')) {
        $shellCommand->useExec = true;
    }

    // Return the result of the command's output or error
    if ($shellCommand->execute()) {
        $result = $shellCommand->getOutput();
    } else {
        $result = $shellCommand->getError();
    }

    return $result;
}

/**
 * Return whether a shell command exists ir not
 *
 * @param string $command
 *
 * @return bool
 */
function shellCommandExists(string $command): bool
{
    $result = executeShellCommand('which '.$command);

    return !empty($result);
}