<?php

use Symfony\Component\Console\Input\InputInterface as In;
use Symfony\Component\Console\Output\OutputInterface as Out;
use Symfony\Component\Yaml\Yaml;

// be sure to use the bundled version of Go (we need the autoloader)
if (realpath($_SERVER['argv'][0]) !== realpath(__DIR__ . '/bin/go')) {
    die("Please use bin/go.\n");
}

/**
 * Runs the following tasks:
 *
 * - clear:cache
 * - clear:logs
 */
task(
    'app:clear',
    'Clears the cache and logs directories.',
    function () {
        $status = 0;
        $status += run('app:clear:cache');
        $status += run('app:clear:logs');

        return $status ? 1 : 0;
    }
);

/**
 * Removes the contents of the cache directory.
 */
task(
    'app:clear:cache',
    'Clears the cache directory.',
    function (Out $out) {
        $out->writeln('Clearing the cache...');

        purge(__DIR__ . '/app/cache', false);
    }
);

/**
 * Removes the contents of the logs directory.
 */
task(
    'app:clear:logs',
    'Clears the log directory.',
    function (Out $out) {
        $out->writeln('Clearing the logs...');

        purge(__DIR__ . '/app/logs', false);
    }
);

/**
 * Generates the API documentation for the application.
 */
task(
    'app:generate:docs',
    'Generates API documentation using Sami.',
    function (Out $out) {
        $out->writeln('Generating API documentation...');

        return cmd(
            __DIR__ . '/bin/sami.php',
            array(
                'update',
                __DIR__ . '/app/config/docs.php'
            )
        );
    }
);

/**
 * Minifies the web application assets.
 */
task(
    'app:minify',
    'Minifies the web application assets.',
    function (Out $out) {
        $assets = Yaml::parse(__DIR__ . '/app/config/assets.yml');
        $verbose = ($out::VERBOSITY_VERBOSE === $out->getVerbosity());

        $out->writeln('Minifying assets...');

        foreach ($assets as $target => $sources) {
            if ($verbose) {
                $out->writeln("  - $target");
            }

            if (!is_absolute_path($target)) {
                $target = __DIR__ . "/$target";
            }

            file_put_contents($target, '');

            foreach ($sources as $source) {
                if (!is_absolute_path($source)) {
                    $source = __DIR__ . "/$source";
                }

                if (!is_file($source)) {
                    throw new RuntimeException(
                        sprintf(
                            'The path "%s" does not exist or is not a file.',
                            $source
                        )
                    );
                }

                $source = realpath($source);
                $ext = pathinfo($source, PATHINFO_EXTENSION);

                switch (strtolower($ext)) {
                    case 'css':
                        $source = Minify_CSS::minify(
                            file_get_contents($source)
                        );

                        break;
                    case 'js':
                        $source = JSMin::minify(
                            file_get_contents($source)
                        );

                        break;
                    default:
                        throw new RuntimeException(
                            "The file extension \"$ext\" is not supported."
                        );
                }

                file_put_contents($target, $source, FILE_APPEND);
            }
        }
    }
);

/**
 * Runs the web application using the internal PHP web server.
 */
task(
    'app:run',
    'Runs the application using the internal PHP web server.',
    function (In $in, Out $out) {
        $args = array(
            '-S', $in->getOption('host') . ':' . $in->getOption('port'),
            '-t', __DIR__ . '/web',
            '-d',
            'session.save_path=' . $in->getOption('save'),
        );

        if (!is_dir($in->getOption('save'))) {
            mkdir($in->getOption('save'));
        }

        $options = array(
            'env' => array(
                'APP_DEBUG=' . ($in->getOption('no-debug') ? 0 : 1),
                'APP_MODE=' . $in->getOption('mode'),
            ),
        );

        $out->write('<fg=magenta>Running</fg=magenta> on ');
        $out->write("<fg=cyan>{$args[1]}</fg=cyan> ");

        $out->write(
            sprintf(
                '(<fg=yellow>debug:</fg=yellow> <fg=%s>%s</fg=%1$s>',
                $in->getOption('no-debug') ? 'red' : 'green',
                $in->getOption('no-debug') ? 'off' : 'on'
            )
        );

        $out->writeln(
            sprintf(
                ', <fg=yellow>mode:</fg=yellow> <fg=%s>%s</fg=%1$s>)...',
                ('prod' == $in->getOption('mode')) ? 'red' : 'green',
                $in->getOption('mode')
            )
        );

        if (null !== ($log = $in->getOption('log'))) {
            $out->writeln(
                sprintf(
                    '<fg=magenta>Logging</fg=magenta> to <fg=cyan>%s</fg=cyan>',
                    $log
                )
            );

            $args[] = '>';
            $args[] = '/dev/null';
            $args[] = '2>';
            $args[] = $log;
        }

        if (empty($log)) {
            $out->writeln('');
            $out->writeln('<fg=cyan>==========</fg=cyan>');
            $out->writeln('');
        }

        cmd('php', $args, $options);
    }
);

option('host', null, OPT_IS_REQUIRED, 'The server host name.', 'localhost');
option('log', 'l', OPT_IS_REQUIRED, 'The log file.');
option('mode', 'm', OPT_IS_REQUIRED, 'The application mode.', 'dev');
option('no-debug', null, OPT_NO_VALUE, 'Disable debugging?');
option('port', 'p', OPT_IS_REQUIRED, 'The server port number.', 8000);
option('save', 's', OPT_IS_REQUIRED, 'The session save path.', __DIR__ . '/app/cache/sessions');

/**
 * Runs the web application test suite.
 */
task(
    'app:test',
    'Runs the PHPUnit test suite.',
    function (In $in) {
        $args = array(
            '--verbose'
        );

        if ($in->getOption('coverage')) {
            $args[] = '--coverage-html';
            $args[] = __DIR__ . '/app/cache/coverage';
        }

        cmd('bin/phpunit', $args);
    }
);

option('coverage', 'c', OPT_NO_VALUE, 'Generate HTML code coverage?');

/**
 * Executes a command (`passthru()`) and returns its exit status.
 *
 * @param string $command   The command name.
 * @param array  $arguments The command arguments.
 * @param array  $options   The command options.
 *
 * @return integer The status code.
 */
function cmd($command, array $arguments = array(), array $options = array())
{
    $string = '';

    if (!empty($options['env'])) {
        $string .= join(' ', $options['env']) . ' ';
    }

    $string .= $command;

    if ($arguments) {
        array_walk(
            $arguments,
            function (&$argument) {

                if (!preg_match('/^\s*\d{0,1}[\|><]/', $argument)) {
                    $argument = escapeshellarg($argument);
                }
            }
        );

        $string .= ' ' . join(' ', $arguments);
    }

    passthru($string, $status);

    return $status;
}

/**
 * Purges a file or directory.
 *
 * @param string  $path The path to purge.
 * @param boolean $root If directory, delete root folder?
 * @param array   $keep Keep these files.
 */
function purge($path, $root = true, array $keep = array('.gitignore'))
{
    if (is_dir($path)) {
        foreach (scandir($path) as $item) {
            if (('.' == $item) || ('..' == $item)) {
                continue;
            }

            if (in_array($item, $keep)) {
                continue;
            }

            purge($path . DIRECTORY_SEPARATOR . $item);
        }

        if ($root) {
            rmdir($path);
        }
    } else {
        unlink($path);
    }
}
