<?php

/**
 *
 * @package workflow.engine.classes
 */
class CLI
{
    public static $tasks = array ();
    public static $currentTask = null;

    /**
     * Adds a new task defined by it's name.
     * All other task functions will
     * remember the current task defined here.
     *
     * @param string $name name of the task, used in the command-line
     */
    public static function taskName ($name)
    {
        self::$currentTask = $name;
        self::$tasks[$name] = array (
            'name' => $name,
            'description' => null,
            'args' => array (),
            'function' => null,
            'opt' => array (
                'short' => '',
                'long' => array (),
                'descriptions' => array ()
            )
        );
    }

    /**
     * Adds a description to the current task.
     * The description should contain a
     * one-line description of the command and a few lines of text with more
     * information.
     *
     * @param string $description task description
     */
    public static function taskDescription ($description)
    {
        assert( self::$currentTask !== null );
        self::$tasks[self::$currentTask]["description"] = $description;
    }

    /**
     * Adds an argument to the current task.
     * The options will affect how it is
     * displayed in the help command. Optional will put [] in the argument and
     * multiple will put ... in the end. Arguments are displayed together with
     * the task name in the help command.
     *
     * @param string $name argument name
     */
    public static function taskArg ($name, $optional = true, $multiple = false)
    {
        assert( self::$currentTask !== null );
        self::$tasks[self::$currentTask]["args"][$name] = array ('optional' => $optional,'multiple' => $multiple
        );
    }

    /**
     * Defines short and long options as used by getopt to the current task.
     *
     * @param string $short short options
     * @param array $long long options
     */
    public static function taskOpt ($name, $description, $short, $long = null)
    {
        assert( self::$currentTask !== null );
        $opts = self::$tasks[self::$currentTask]["opt"];
        if ($short) {
            $opts['short'] .= $short;
        }
        if ($long) {
            $opts['long'][] = $long;
        }
        $opts['descriptions'][$name] = array ('short' => $short,'long' => $long,'description' => $description
        );
        self::$tasks[self::$currentTask]["opt"] = $opts;
    }

    /**
     * Defines the function to run for the current task.
     *
     * @param callback $function function to run
     */
    public static function taskRun ($function)
    {
        assert( self::$currentTask !== null );
        self::$tasks[self::$currentTask]["function"] = $function;
    }

    /**
     * Displays the help instructions.
     *
     * @param array $args if defined, the task name should be argument 0
     * @param array $opts options as returned by getopt
     */
    public static function help ($args, $opts = null)
    {
        global $argv;
        $scriptName = $argv[0];
        if (is_array($args) && count($args) > 0 ) {
            $taskName = $args[0];
        } else {
            $taskName = $args;
        }

        if (! $taskName) {
            echo "usage: processmaker <task> [options] [args]\n";
            echo "       If using Linux/UNIX, prepend './' to specify the directory: " . $scriptName . " <task> [options] [args]\n";
            echo "Type   'processmaker help <task>' for help on a specific task.";
            echo "\n\nAvailable tasks:\n";
            $tasks = array ();
            ksort( self::$tasks );
            foreach (self::$tasks as $name => $data) {
                $description = explode( "\n", $data['description'] );
                $tasks[] = "  $name";
            }
            $tasks = join( "\n", $tasks );
            echo $tasks . "\n\n";
        } else {
            $options = array();
            $tasks = array();
            ksort( self::$tasks );
            foreach (self::$tasks as $name => $data) {
                $description = explode( "\n", $data['description'] );
                $options[] = "$name";
            }
            if (!in_array($taskName, $options)) {
                echo "\nThe task does not exist \n";
                echo "Use one of the following tasks:\n";
                $tasks = array ();
                ksort( self::$tasks );
                foreach (self::$tasks as $name => $data) {
                    $description = explode( "\n", $data['description'] );
                    $tasks[] = "  $name";
                }
                $tasks = join( "\n", $tasks );
                echo $tasks . "\n\n";
            } else{
                $valid_args = array ();
                foreach (self::$tasks[$taskName]['args'] as $arg => $data) {
                    $arg = strtoupper( $arg );
                    if ($data['multiple']) {
                        $arg = "$arg...";
                    }
                    if ($data['optional']) {
                        $arg = "[$arg]";
                    }
                    $valid_args[] = $arg;
                }

            $nameHotfixFile = ($taskName == "hotfix-install")? "HOTFIX-FILE" : "";

            $valid_args = join( " ", $valid_args );
            $description = explode( "\n", self::$tasks[$taskName]['description'] );
            $taskDescription = trim( array_shift( $description ) );
            $description = trim( implode( "\n", $description ) );
            $message = <<< EOT
$taskName: {$taskDescription}
Usage: processmaker $taskName $nameHotfixFile $valid_args

  $description

EOT;
            $valid_options = array ();
            foreach (self::$tasks[$taskName]['opt']['descriptions'] as $opt => $data) {
                $optString = array ();
                if ($data['short']) {
                    $optString[] = "-{$data['short']}";
                }
                if ($data['long']) {
                    $optString[] = "--{$data['long']}";
                }
                $valid_options[] = "  " . join( ", ", $optString ) . "\n\t" . wordwrap( $data['description'], 70, "\n\t" );
            }
            $valid_options = join( "\n", $valid_options );
            if ($valid_options) {
                $message .= <<< EOT

Options:

$valid_options

EOT;
            }
            echo $message . "\n";
        }
        }
    }

    /**
     * Run the CLI task, which will check which command is specified and run it.
     */
    public static function run ()
    {
        CLI::taskName( "help" );
        CLI::taskRun( array ('self','help'
        ) );
        global $argv;
        $args = $argv;
        $cliname = array_shift( $args );
        $taskName = array_shift( $args );
        if (isset($taskName[0])) {
            while ($taskName[0] == '-') {
                $taskName = array_shift( $args );
            }
        }
        if (! $taskName) {
            echo self::error( "Specify a task from the list below." ) . "\n\n";
            self::help( null, null );
            return;
        }
        $taskData = null;
        foreach (self::$tasks as $name => $data) {
            if (strcasecmp( $name, $taskName ) === 0) {
                $taskData = $data;
                break;
            }
        }
        if (! $taskData) {
            echo self::error( "Command not found: '$taskName'" ) . "\n\n";
            self::help( null, null );
            return;
        }

        $short = "h" . $taskData['opt']['short'];
        $long = array_merge( array ("help"
        ), $taskData['opt']['long'] );
        $getopt = Console_Getopt::getopt2( $args, $short, $long );
        if (! is_array( $getopt )) {
            echo self::error( "Invalid options (" . $getopt->getMessage() . ")" ) . "\n\n";
            self::help( $taskName );
            return;
        }
        list ($options, $arguments) = $getopt;
        foreach ($taskData['opt']['descriptions'] as $optName => $optDescription) {
            $short = str_replace( ":", "", $optDescription['short'] );
            $long = str_replace( "=", "", $optDescription['long'] );
            $validOpts[$short] = $optName;
            $validOpts[$long] = $optName;
        }
        $taskOpts = array ();
        try {
            foreach ($options as $opt) {
                list ($optName, $optArg) = $opt;
                if ($optName === "h" || $optName === "--help") {
                    self::help( $taskName );
                    return;
                }
                if (strpos( $optName, '--' ) === 0) {
                    $optName = substr( $optName, 2 );
                }
                if (! array_key_exists( $optName, $validOpts )) {
                    throw new Exception( "option not found: $optName" );
                }
                if (array_key_exists( $validOpts[$optName], $taskOpts )) {
                    throw new Exception( "'$optName' specified more then once" );
                }
                $taskOpts[$validOpts[$optName]] = $optArg;
            }
        } catch (Exception $e) {
            echo self::error( "Invalid options: " . $e->getMessage() ) . "\n\n";
            self::help( $taskName );
            return;
        }
        try {
            call_user_func( $taskData['function'], $arguments, $taskOpts );
        } catch (Exception $e) {
            echo self::error( "\n  Error executing '$taskName':\n\n  {$e->getMessage()}\n" ) . "\n";
            global $tempDirectory;
            if (!empty($tempDirectory)) {
                G::rm_dir($tempDirectory);
            }
        }
    }

    /**
     * Returns an information colorized version of the message.
     *
     * @param string $message the message to colorize
     */
    public static function info ($message)
    {
        return pakeColor::colorize( $message, "INFO" );
    }

    /**
     * Returns a warning colorized version of the message.
     *
     * @param string $message the message to colorize
     */
    public static function warning ($message)
    {
        return pakeColor::colorize( $message, "COMMENT" );
    }

    /**
     * Returns an error colorized version of the message.
     *
     * @param string $message the message to colorize
     */
    public static function error ($message)
    {
        return pakeColor::colorize( $message, "ERROR" );
    }

    /**
     * Prompt the user for information.
     *
     * @param string $message the message to display
     * @return string the text typed by the user
     */
    public static function prompt ($message)
    {
        echo "$message";
        $handle = fopen( "php://stdin", "r" );
        $line = fgets( $handle );
        return $line;
    }

    /**
     * Ask a question of yes or no.
     *
     * @param string $message the message to display
     * @return bool true if the user choosed no, false otherwise
     */
    public static function question ($message)
    {
        $input = strtolower( self::prompt( "$message [Y/n] " ) );
        return (array_search( trim( $input ), array ("y",""
        ) ) !== false);
    }

    /**
     * Display a message to the user.
     * If filename is specified, it will setup
     * a logging file where all messages will be recorded.
     *
     * @param string $message the message to display
     * @param string $filename the log file to write messages
     */
    public static function logging ($message, $filename = null)
    {
        static $log_file = null;
        if (isset( $filename )) {
            $log_file = fopen( $filename, "a" );
            fwrite( $log_file, " -- " . date( "c" ) . " " . $message . " --\n" );
        } else {
            if (isset( $log_file )) {
                fwrite( $log_file, $message );
            }
            echo $message;
        }
    }
}
