#!/usr/bin/env php
<?php

/**
 * @author  : Sidi Said Redouane <sidisaidredouane@live.com>
 * @agency  : EURL ARVODIA
 * @email   : arvodia@hotmail.com
 * @project : BigCli
 * @date    : 2022
 * @license : All Rights Reserved
 * @update  : 01 mai 2021
 * @update  : 18 mars 2022
 */
if (!in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
    echo 'Warning: The console should be invoked via the CLI version of PHP, not the ' . PHP_SAPI . ' SAPI' . PHP_EOL;
}

while ('/' !== ($parent = dirname($parent ?? __DIR__))) {    
    if ('/vendor' === substr($parent, -7)) {
        define('PROJECT_DIR', dirname($parent));
        define('VENDOR_DIR', PROJECT_DIR . '/vendor');
    }
}


set_time_limit(0);

/**
 * @author      : Sidi Said Redouane <sidisaidredouane@live.com>
 * @agency      : EURL ARVODIA
 * @email       : arvodia@hotmail.com
 * @name        : arvodia-cli
 * @project     : ARVODIA CLI Tools
 * @Description : Commands Manager system
 * @date        : 2021
 * @license     : GNU General Public License v3.0
 * @homepage    : https://github.com/arvodia/arvodia-cli
 */
/**
 * Polyfills
 */
if (!function_exists('str_starts_with')) {

    function str_starts_with(?string $haystack, ?string $needle): bool {
        return \strncmp($haystack, $needle, \strlen($needle)) === 0;
    }

}
if (!function_exists('str_ends_with')) {

    function str_ends_with(?string $haystack, ?string $needle): bool {
        return $needle === '' || $needle === \substr($haystack, - \strlen($needle));
    }

}

/**
 * Generate Command
 */
class GenerateCommand extends ArvodiaCli {

    protected const DESCRIPTION = 'Generate commands';
    protected const HELP = <<<'EOF'
The <green>%command.full_name% </green> command generate new command class:
    
give a name to your command:
  add the <yellow>--name</yellow> option:
  
    <yellow>%command.full_name% --name=Example</yellow>
EOF;

    /**
     * 
     * @option(param=name,short=n, message="the name option is requires a value")
     */
    public function execute(string $name) {
        $this->nameValidator($name);
        if (($commandsDir = realpath(__DIR__ . '/../../../../src/Arvodia/Command')) ||
                ($commandsDir = realpath(__DIR__ . '/../src/Arvodia/Command'))) {
            if (!is_writable($commandsDir)) {
                $this->showBlock(sprintf('The folder "%s" is write protected', $commandsDir), 'BRED');
                self::FAILURE();
            }
            if (is_file($cmdPath = ($commandsDir . '/' . ($name = ucfirst($name)) . 'Command.php'))) {
                $this->showBlock(sprintf('The class "%s" already exists', $cmdPath), 'BRED');
                self::FAILURE();
            }
            $params = '';
            $annotations = '';
            while ($this->confirm('do you want to add an option ?', true)) {
                $params = (($argument = (($type = $this->choice('want to choose a type', ['string', 'int', 'bool', 'array'], 'string')) . ' $' . ($param = $this->nameValidator($this->show('please give a name to your option :', '>' . PHP_EOL) ?: trim(fgets(STDIN)))) . ($default = ($default = ($this->show('please enter a default value, if you leave it blank the option will be required :', '>' . PHP_EOL) ?: trim(fgets(STDIN)))) !== '' ? ' = ' . $this->formatValue($default, $type) : ''))) && '' === $default) ? $argument . ($params ? ', ' . $params : $params) : ($params ? $params . ', ' : $params) . $argument;
                $annotations .= ($annotation = (($shor = $this->shortValidator($this->show('A shortcut letter (optional) :', '>' . PHP_EOL) ?: trim(fgets(STDIN)))) ? ",short=$shor" : '') . (($desc = $this->show('add a description to your option (optional) :', '>' . PHP_EOL) ?: str_replace('"', "'", trim(fgets(STDIN)))) ? ', message="' . $desc . '"' : '')) ? PHP_EOL . "     * @option(param=$param$annotation)" : '';
            }
            file_put_contents($cmdPath, str_replace(['%EXAMPLE%', '%PARAMS%', '%OPTION%'], [$name, $params, $annotations], <<<'EOF'
<?php
namespace Arvodia\Command;
use Arvodia\Terminal\Terminal;
class %EXAMPLE%Command extends Terminal {
    protected const DESCRIPTION = '%EXAMPLE% commands';
    protected const HELP = 'The <green>%command.full_name% </green> command help:';
    /**
     * %OPTION%
     */
    public function execute(%PARAMS%) {
        $this->show('successful execution of the %EXAMPLE% command');
        self::SUCCESS();
    }
}
EOF));
            $this->showBlock('the command has been successfully generated', 'BGREEN');
            self::SUCCESS();
        } else {
            $this->showBlock('the "src/Arvodia/Command" folder does not exist', 'BRED');
            self::FAILURE();
        }
    }

    private function nameValidator(string $name): string {
        if (!preg_match('/^[a-zA-Z0-9\-]+$/', $name, $matches) || !preg_match('/^[a-zA-Z]/', $name, $matches) || '_' === substr($name, -1)) {
            $this->showBlock(sprintf('The name "%s" value is invalid', $name), 'BRED');
            self::FAILURE();
        }
        return $name;
    }

    private function shortValidator(string $name): string {
        if ('' !== $name && !preg_match('/^[a-z]{1}$/', $name, $matches)) {
            $this->showBlock(sprintf('The shortcut "%s" value is invalid', $name), 'BRED');
            self::FAILURE();
        }
        return $name;
    }

    private function formatValue(string $default, string $type): string {
        if ('string' === $type) {
            return '"' . str_replace('"', "'", $default) . '"';
        } elseif ('int' === $type) {
            return (int) $default;
        } elseif ('bool' === $type) {
            return $this->isEnabled($default) ? 'true' : 'false';
        } elseif ('array' === $type) {
            return '["' . str_replace('"', "'", $default) . '"]';
        }
    }

}

/**
 * ARVODIA CLI Tools
 * 
 */
class ArvodiaCli {

    private const GREEN = "\033[0;32m";
    private const RED = "\033[0;31m";
    private const YELLOW = "\033[1;33m";
    private const CYAN = "\033[0;36m";
    private const BGREEN = "\033[42;1;30m";
    private const BRED = "\033[41;1;37m";
    private const BBLUE = "\033[44;1;37m";
    private const BPINK = "\033[45;1;37m";
    private const RESET = "\033[0m";
    private const ANNOTATION_KEYS = ['param', 'short', 'message'];
    private const CLI_TITLE = 'Welcome to ARVODIA CLI Tools';
    private const CLI_USAGE = '%command.full_name% [command-name] [options] [=<value>]';
    private const GLOBAL_OPTION = [
        'help' => [
            'short' => 'h',
            'long' => 'help',
            'required' => false,
            'default' => false,
            'type' => 'bool',
            'desc' => 'Display help for the given command. When no command is given display help for the <green>list</green> command'
        ],
        'verbose' => [
            'short' => 'v',
            'long' => 'verbose',
            'required' => false,
            'default' => [],
            'type' => 'array',
            'desc' => 'Verbose mode it provides additional details for diagnostic and debugging purposes.'
        ],
    ];
    private const LIST_DESCRIPTION = 'List commands';
    private const LIST_OPTION = [
        'raw' => [
            'short' => 'r',
            'long' => 'raw',
            'required' => false,
            'default' => false,
            'type' => 'bool',
            'desc' => 'To output raw command list'
        ],
    ];
    private const LIST_HELP = <<<'EOF'
The <green>list</green> command lists all commands:

    <green>%command.full_name% </green>

It's also possible to get raw list of commands:

    <green>%command.full_name% --raw</green>
    or
    <green>%command.full_name% -r</green>
EOF;

    private $commands;
    private $commandName;
    private $commandsDir;
    private $commandFullName;
    private $parametresOptions;
    private $screenWidth;

    public function __construct(array $argv = []) {
        if (get_class() === get_class($this)) {
            global $argv;
            foreach ($argv as $key => $arg) {
                if (!$key || '-' === $arg[0]) {
                    continue;
                } else {
                    $this->commandName = $arg;
                    break;
                }
            }
            $this->commandFullName = 'php ' . $argv[0] . ' ' . $this->commandName;
            if (in_array('-h', $argv) || in_array('--help', $argv)) {
                if (!$this->commandName || 'list' === $this->commandName) {
                    $this->commandName = 'list';
                    $this->commandFullName = 'php ' . $argv[0] . ' ' . $this->commandName;
                }
                $this->printHelp();
                self::SUCCESS();
            }
            if (!$this->commandName || 'list' === $this->commandName) {
                $this->commandName ?: $this->printHelp();
                $this->commandName = 'list';
                !$this->getCommands(null, true) ?: $this->showList(['Available commands' => array_column($this->getCommands(), 'desc', 'name')], $this->commandName ? (bool) $this->getOptions('raw') : false);
                self::SUCCESS();
            }
            define('VERBOSE', min(count($this->getOptions('verbose') ?? []), 3));
            if ($command = ($this->getCommands($this->commandName)['class'] ?? false)) {
                $options = array_diff_key($this->getOptions() ?: [], self::GLOBAL_OPTION);
                foreach ($this->getCommands($this->commandName)['options'] as $option => $param) {
                    $parametres[] = $options[$option] ?? $param['default'];
                }
                // autoloader
                self::cmdBooter($this->commandsDir, $this->commandName);
                // execute
                (new $command())->setCommandFullName($this->commandFullName)->execute(...$parametres ?? []);
            } else {
                $this->exception(16, $this->commandName);
            }
        }
    }

    public function setCommandFullName($name): self {
        $this->commandFullName = $name;
        return $this;
    }

    protected static function SUCCESS() {
        exit(0);
    }

    protected static function FAILURE() {
        exit(1);
    }

    protected function isEnabled($variable) {
        if (!isset($variable)) {
            return null;
        }
        return filter_var($variable, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
    }

    protected function show($text, $color = null, ?string $newline = PHP_EOL): void {
        echo $this->getColor($color) . $text = preg_replace_callback('#<(green|red|yellow|cyan)>(.+)</(?:green|red|yellow|cyan)>#isU', function ($regs) {
            return $this->getColor($regs[1]) . $regs[2] . self::RESET;
        }, $text = str_replace('%command.full_name%', $this->commandFullName, $text)) . self::RESET . $newline;
    }

    protected function showBlock(string $text, string $color = 'BGREEN', bool $centerAlign = false): void {
        $this->screenWidth = $this->screenWidth ?: (preg_match_all("/rows.([0-9]+);.columns.([0-9]+);/", strtolower(exec('stty -a |grep columns')), $output) && sizeof($output) == 3 ? $output[2][0] : 50);
        // $this->screenHeight = $output[1][0];
        $text = implode('', array_map(function ($row) use ($centerAlign) {
                    return str_pad(' ' . $row, $this->screenWidth, " ", $centerAlign ? STR_PAD_BOTH : STR_PAD_RIGHT);
                }, explode(PHP_EOL, $text)));
        echo PHP_EOL . $this->getColor($color) . str_pad(" ", $this->screenWidth, " ", STR_PAD_BOTH) . PHP_EOL . $text . PHP_EOL . str_pad(" ", $this->screenWidth, " ", STR_PAD_BOTH) . PHP_EOL . self::RESET . PHP_EOL . PHP_EOL;
    }

    protected function showList(array $array = [], bool $raw = false, int $level = 0): void {
        foreach ($array as $title => $value) {
            if (!$raw && !$level) {
                echo PHP_EOL;
            }
            if (is_array($value)) {
                if (!$raw) {
                    $this->show(str_repeat(' ', $level * 3) . $title . ':', 'YELLOW');
                }
                $this->showList($value, $raw, $level + 1);
            } else {
                $this->show(($raw ? '' : str_repeat(' ', $level * 3)) . str_pad($title, $pad ?? ($pad = ($this->getMaxStrlen(array_keys($array)) + 2)), " "), $raw ? null : 'GREEN', null);
                $this->show($value);
            }
        }
    }

    protected function confirm(string $question, bool $default = null): bool {
        $answer = null;
        $this->show('<green>' . $question . ' (yes/no)</green> ' . (is_bool($default) ? '[<yellow>' . ($default ? 'yes' : 'no') . '</yellow>]' : '') . ':');
        while (!is_bool($answer)) {
            $this->show('>', null, null);
            if (!is_bool($answer = $this->isEnabled(($answer = trim(fgets(STDIN))) === '' ? (is_null($default) ? 'echec' : $default ) : $answer))) {
                $this->showBlock("The answer is not a Boolean equivalencies,
    To confirm between : '1', 'true', 'on' or 'yes'
    To cancel between  : '0', 'false', 'off' or 'no'", 'BRED');
            }
        }
        return $answer;
    }

    protected function choice(string $question, array $choices, $default = null) {
        $answer = null;
        array_map(function ($param) {
            if (is_array($param)) {
                $this->show('[ERROR] Choice cannot be Multidimensional Arrays!', 'RED');
                self::FAILURE();
            }
            return $param;
        }, $choices);
        $this->showList(['<green>' . $question . '</green> ' . (!is_null($default) ? '[<yellow>' . $default . '</yellow>]' : '') => $choices]);
        while (is_null($answer)) {
            $this->show('>', null, null);
            if (is_null($answer = in_array($answer = $tmp = ($answer = trim(fgets(STDIN))) === '' && !is_null($default) ? $default : $answer, $choices) ?
                            (is_numeric($answerKey = array_search($answer, $choices)) ? $answer : $answerKey) :
                            (array_key_exists($answer, $choices) ? (is_numeric($answer) ? $choices[$answer] : $answer) : null))) {
                $this->showBlock(sprintf('[ERROR] Value "%s" is invalid', $tmp), 'BRED');
            }
        }
        return $answer;
    }

    private static function cmdBooter(string $commandsDir, string $commandName) {
        if (defined('PROJECT_DIR')) {
            if (($libraries = (json_decode(file_get_contents($commandsDir . '/config.json'), TRUE))['cmd_loader'][$commandName] ?? false)) {
                foreach ($libraries as $dir => $class) {
                    if (!($configDir = realpath(VENDOR_DIR . $dir))) {
                        var_dump('ERROR DIR PAth : ' . $dir);
                        die(1);
                    }
                    if (file_exists(($classLoad = realpath($configDir . '/' . $class)))) {
                        require $classLoad;
                    }
                }
            }
        }
    }

    private function getColor($color = null): string {
        return defined('self::' . $color = strtoupper($color)) ? constant('self::' . $color) : self::RESET;
    }

    private function getMaxStrlen(array $array): int {
        foreach ($array as $string) {
            $max = max(strlen($string), $max ?? 0);
        }
        return $max;
    }

    private function printHelp(): void {
        if (!($command = $this->getCommands($this->commandName))) {
            $this->exception(16, $this->commandName);
        }
        $this->commandsDir && is_file($this->commandsDir . '/config.json') && ($cliTitle = (json_decode(file_get_contents($this->commandsDir . '/config.json'), TRUE))['arvodia_cli_title'] ?? false);
        $this->commandName ? (($desc = $command['desc']) ? $this->show('<yellow>Description:</yellow>' . PHP_EOL . $desc . PHP_EOL,) : '') : $this->showBlock($cliTitle ?? self::CLI_TITLE ?: self::CLI_TITLE, 'BGREEN', true);
        $this->show('Usage:', 'YELLOW');
        $this->show($this->commandName ? ($command['usage'] ?: str_replace('[command-name] ', '', self::CLI_USAGE) ) : self::CLI_USAGE);
        foreach ($this->commandName ? $command['options'] : self::GLOBAL_OPTION as $param) {
            $options[($param['short'] ? '-' . $param['short'] . ($param['short'] == 'v' ? '|-vv|-vvv' : '') . ', ' : '') . '--' . $param['long']] = $param['desc'] . ($param['required'] ? '<red>[required]</red>' : ($param['default'] ? '<yellow>[default: "' . $param['default'] . '"]</yellow>' : ''));
        }
        if ($options ?? false) {
            $this->showList(['Options' => $options]);
        }
        if ($this->commandName && $command['help']) {
            $this->show(PHP_EOL . 'Help:', 'YELLOW');
            $this->show($command['help']);
        }
    }

    private function getClassCommands(): array {
        foreach (($tokens = token_get_all(file_get_contents(__FILE__))) as $key => $token) {
            if (is_array($token) && $token[0] == T_CLASS && ($tokens[$key + 2] ?? null == T_STRING) && str_ends_with($tokens[$key + 2][1] ?? null, 'Command') && ($name = substr(strtolower($tokens[$key + 2][1]), 0, -7))) {
                $class = $tokens[$key + 2][1];
                if (isset($commands[$name])) {
                    $this->exception(7, [$name, $class]);
                }
                $commands[$name] = $class;
            }
        }
        if (file_exists(dirname(__DIR__) . '/src/Arvodia/Autoloader.php')) {
            require_once dirname(__DIR__) . '/src/Arvodia/Autoloader.php';
            if ($this->commandsDir = Autoloader::register()) {
                foreach (scandir($this->commandsDir) as $class) {
                    if (str_ends_with($class, 'Command.php')) {
                        $name = substr(strtolower($class = str_replace('.php', '', $class)), 0, -7);
                        if (isset($commands[$name])) {
                            $this->exception(7, [$name, $class]);
                        }
                        $commands[$name] = 'Arvodia\Command\\' . $class;
                    }
                }
            }
        }
        return $commands ?? [];
    }

    private function getCommands(string $name = null, bool $reload = false): ?array {
        if (!is_null($this->commands) && !$reload) {
            return $name ? $this->commands[$name] ?? null : $this->commands;
        }
        $this->commands['list'] = [
            'name' => 'list',
            'class' => null,
            'desc' => self::LIST_DESCRIPTION,
            'usage' => null,
            'help' => self::LIST_HELP,
            'options' => self::LIST_OPTION,
        ];
        foreach ($this->getClassCommands() as $command => $class) {
            if ($name && $name !== $command) {
                continue;
            }
            if (($rc = new ReflectionClass($class))->getParentClass()) {
                if (!$rc->hasMethod('execute')) {
                    $this->exception(1, $class);
                }
                $this->commands[$command] = [
                    'name' => $command,
                    'class' => $class,
                    'desc' => $rc->getConstant('DESCRIPTION'),
                    'usage' => $rc->getConstant('USAGE'),
                    'help' => $rc->getConstant('HELP'),
                    'options' => [],
                ];
                $docComment = ($rf = $rc->getMethod('execute'))->getDocComment();
                foreach ($rf->getParameters() as $key => $param) {
                    if (strlen($param->getName()) === 1) {
                        $this->exception(13, [$param->getName(), $class]);
                    }
                    if (in_array($param->getName(), array_keys(self::GLOBAL_OPTION))) {
                        $this->exception(17, [$param->getName(), $class]);
                    }
                    $this->commands[$command]['options'][$param->getName()] = [
                        'short' => null,
                        'long' => $param->getName(),
                        'position' => $param->getPosition(),
                        'required' => !$param->isOptional(),
                        'default' => $param->isOptional() ? $param->getDefaultValue() : null,
                        'type' => $param->hasType() ? $param->getType()->getName() : null,
                        'desc' => null,
                    ];
                }
                if ($docComment && preg_match_all('#@option\((.*)\)#i', $docComment, $annotations)) {
                    foreach ($annotations[1] as $annotation) {
                        $annotation = preg_replace_callback('#"(.*)"#U', function ($regs) {
                            return str_replace([',', '='], ['&#44;', '&#61;'], $regs[0]);
                        }, $annotation);
                        $optionArguments = [];
                        foreach (explode(',', $annotation) as $annotArguments) {
                            $annotArguments = explode('=', $annotArguments);
                            for ($i = 0; $i < count($annotArguments); $i += 2) {
                                if (!in_array($var = trim($annotArguments[$i]), self::ANNOTATION_KEYS) || !isset($annotArguments[$i + 1]) || !$annotArguments[$i + 1]) {
                                    $this->exception(2, [$var, $class]);
                                }
                                $optionArguments[$var] = trim($annotArguments[$i + 1]);
                            }
                        }
                        if (!isset($optionArguments['param'])) {
                            $this->exception(3, $class);
                        }
                        if (!isset($this->commands[$command]['options'][$optionArguments['param']])) {
                            $this->exception(4, [$optionArguments['param'], $class]);
                        }
                        if (isset($optionArguments['short']) && isset($optionArguments['short'][1])) {
                            $this->exception(5, [$optionArguments['short'], $class]);
                        }
                        if ($name && isset($optionArguments['short']) && in_array($optionArguments['short'], array_column(array_merge(self::GLOBAL_OPTION, $this->commands[$command]['options']), 'short'))) {
                            $this->exception(6, [$optionArguments['short'], $class]);
                        }
                        $this->commands[$command]['options'][$optionArguments['param']]['short'] = $optionArguments['short'] ?? null;
                        $this->commands[$command]['options'][$optionArguments['param']]['desc'] = ($optionArguments['message'] ?? null) ? trim(str_replace(['&#44;', '&#61;'], [',', '='], $optionArguments['message']), '"') : null;
                    }
                }
            }
        }
        return $name ? $this->commands[$name] ?? null : $this->commands;
    }

    private function getOptions(string $name = null) {
        if (!is_null($this->parametresOptions)) {
            return $name ? $this->parametresOptions[$name] ?? null : $this->parametresOptions;
        }
        $this->parametresOptions = [];
        global $argv;
        $options = array_merge($this->commandName ? $this->getCommands($this->commandName)['options'] ?? [] : [], self::GLOBAL_OPTION);
        foreach ($argv as $key => $args) {
            if ($jump ?? false || 0 === $key) {
                $jump = false;
                continue;
            }
            if (isset($args[1]) && '-' == $args[0]) {
                $opts = ($value = explode('=', substr($args, '-' == $args[1] ? 2 : 1)))[0];
                $value = ($value[1] ?? false) && array_shift($value) ? implode('=', $value) : null;
                if ('-' == $args[1] && !isset($opts[1])) {
                    $this->exception(12, $args);
                }
                if ('-' != $args[1] && isset($opts[1])) {
                    if (strpos($args, '=')) {
                        $this->exception(15, $args);
                    }
                    $i = 0;
                    $multiShort = [];
                    while ($opts[$i] ?? false) {
                        if (array_column($options, 'required', 'short')[$opts[$i]] ?? false) {
                            $this->exception(14, '-' . $opts[$i]);
                        }
                        $multiShort[] = ['-' . $opts[$i] => $opts[$i]];
                        $i++;
                    }
                    $opts = $multiShort;
                }
                foreach (is_array($opts) ? $opts : [[$args => $opts]] as $argOpt) {
                    $arg = array_key_first($argOpt);
                    $opt = current($argOpt);
                    if (in_array($opt, array_column($options, $style = (isset($opt[1]) ? 'long' : 'short')))) {
                        if ($options[$option = array_search($opt, array_column($options, $style, 'long'))]['required']) {
                            if (is_null($value)) {
                                if (is_null($value = $argv[$key + 1] ?? null)) {
                                    $this->exception(8, $option);
                                }
                                $jump = true;
                            }
                        }
                        if (isset($this->parametresOptions[$option]) && 'array' !== $options[$option]['type']) {
                            $this->exception(9, $option);
                        }
                        if ('bool' === $options[$option]['type']) {
                            if ($options[$option]['required']) {
                                if (is_null($value = $this->isEnabled($value))) {
                                    $this->exception(10, $option);
                                }
                            } else {
                                $value = true;
                            }
                        } elseif ((!is_null($options[$option]['type']) && 'int' === $options[$option]['type']) && !preg_match('/^[0-9\.]+$/', $value, $matches)) {
                            $this->exception(11, [$option, $options[$option]['type'], gettype($value)]);
                        } elseif ((!is_null($options[$option]['type']) && 'array' !== $options[$option]['type'] && 'int' !== $options[$option]['type']) && gettype($value) !== $options[$option]['type']) {
                            $this->exception(11, [$option, $options[$option]['type'], gettype($value)]);
                        }
                        $this->parametresOptions[$option] = ('array' === $options[$option]['type'] ? array_merge([$value], $this->parametresOptions[$option] ?? []) : $value);
                    } else {
                        $this->exception(12, $arg);
                    }
                }
            } else if ($this->commandName != $args) {
                $this->exception(12, $args);
            }
        }
        foreach (array_diff(array_keys($options ?? []), array_keys($this->parametresOptions ?? [])) as $option) {
            if ($options[$option]['required']) {
                $this->exception(8, $option);
            }
        }
        return $name ? $this->parametresOptions[$name] ?? null : $this->parametresOptions;
    }

    private function exception(int $exception = null, $detail = null): void {
        $detail = is_null($detail) ? '' : (is_array($detail) ? $detail : [$detail]);
        switch ($exception) {
            case 1:$this->show(sprintf('Error : The "execute()" method of the "%s" class has not been declared.', ...$detail), 'RED');
                break;
            case 2:$this->show(sprintf('Error : Annotation argument "%1$s" is invalid of the class "%2$s".', ...$detail), 'RED');
                break;
            case 3:$this->show(sprintf('Error : Argument "param" was not declared in the execute method annotation of class "%s".', ...$detail), 'RED');
                break;
            case 4:$this->show(sprintf('Error : In the annotation argument "%1$s" was not declared in the function "execute ()" of the class "%2$s".', ...$detail), 'RED');
                break;
            case 5:$this->show(sprintf('Error : Short options name "%1$s" must contain a single character of the class "%2$s".', ...$detail), 'RED');
                break;
            case 6:$this->show(sprintf('Error : Short options name "%1$s" already exists from class "%2$s".', ...$detail), 'RED');
                break;
            case 7:$this->show(sprintf('Error : The name of the command "%1$s", already exists of the class "%2$s".', ...$detail), 'RED');
                break;
            case 8:$this->show(sprintf('Error : Not enough arguments (missing: "%s").', ...$detail), 'RED');
                break;
            case 9:$this->show(sprintf('Error : Options "%s" is not of type array.', ...$detail), 'RED');
                break;
            case 10:$this->show(sprintf('Error : Options "%s": accept the values "1", "true", "on", "yes", "0", "false", "off" or "no".', ...$detail), 'RED');
                break;
            case 11:$this->show(sprintf('Error : Argument "%1$s" must be of type "%2$s", "%3$s" given.', ...$detail), 'RED');
                break;
            case 12:$this->show(sprintf('Error : The "%s" option does not exist.', ...$detail), 'RED');
                break;
            case 13:$this->show(sprintf('Error : The name of the variable "%1$s" less finger to have two characters, in the method "execute ()" of the class "%2$s".', ...$detail), 'RED');
                break;
            case 14:$this->show(sprintf('Error : You cannot use option "%s" with a required value in a multi short option.', ...$detail), 'RED');
                break;
            case 15:$this->show(sprintf('Error : The separator "=" invalid in the option "%s".', ...$detail), 'RED');
                break;
            case 16:$this->show(sprintf('Error : Command "%s" is not defined.', ...$detail), 'RED');
                break;
            case 17:$this->show(sprintf('Error : Option "%1$s", in the  "%2$s" class, already exists.', ...$detail), 'RED');
                break;
            default:$this->show(implode(" ", $detail ?: []), 'RED');
                break;
        }
        self::FAILURE();
    }

}

/**
 * Run ARVODIA CLI
 */
new ArvodiaCli();
