#!/usr/bin/env php
<?php
error_reporting(E_ALL);
// Possible options and their default values.
$options = [
    'output' => 'swagger.json',
    'stdout' => false,
    'exclude' => [],
    'bootstrap' => false,
    'help' => false,
    'version' => false,
    'debug' => false,
    'processor' => [],
];
$aliases = [
    'o' => 'output',
    'e' => 'exclude',
    'b' => 'bootstrap',
    'v' => 'version',
    'h' => 'help',
    'd' => 'debug',
    'p' => 'processor'
];
$needsArgument = [
    'output',
    'exclude',
    'bootstrap',
    'processor',
];
$paths = array();
$error = false;
try {
    // Parse cli arguments
    for ($i = 1; $i < $argc; $i++) {
        $arg = $argv[$i];
        if (substr($arg, 0, 2) === '--') { // longopt
            $option = substr($arg, 2);
        } elseif ($arg[0] === '-') { // shortopt
            if (array_key_exists(substr($arg, 1), $aliases)) {
                $option = $aliases[$arg[1]];
            } else {
                throw new Exception('Unknown option: "' . $arg . '"');
            }
        } else {
            $paths[] = $arg;
            continue;
        }
        if (array_key_exists($option, $options) === false) {
            throw new Exception('Unknown option: "' . $arg . '"');
        }
        if (in_array($option, $needsArgument)) {
            if (empty($argv[$i + 1]) || $argv[$i + 1][0] === '-') {
                throw new Exception('Missing argument for "' . $arg . '"');
            }
            if (is_array($options[$option])) {
                $options[$option][] = $argv[$i + 1];
            } else {
                $options[$option] = $argv[$i + 1];
            }
            $i++;
        } else {
            $options[$option] = true;
        }
    }
} catch (Exception $e) {
    $error = $e->getMessage();
}
$version = trim(file_get_contents(__DIR__ . '/../VERSION'));
if ($options['version']) {
    echo $version, PHP_EOL;
    exit;
}
error_log('');
error_log('Swagger-PHP ' . $version);
error_log('------------'.  str_repeat('-', strlen($version)));
if (!$error && $options['bootstrap']) {
    if (is_readable($options['bootstrap']) === false) {
        $error = 'Invalid `--bootstrap` value: "'.$options['bootstrap'].'"';
    } else {
        require_once($options['bootstrap']);
    }
}

if ($error) {
    error_log('[ERROR] '.$error);
    $options['help'] = true; // Show help
}
if ($options['help']) {
    $help = <<<EOF

Usage: swagger [--option value] [/path/to/project ...]

Options:
  --output (-o)     Path to store the generated documentation.
  --stdout          Write to the standard output.
  --exclude (-e)    Exclude path(s).
                    ex: --exclude vendor,library/Zend
  --bootstrap (-b)  Bootstrap a php file for defining constants, etc.
                    ex: --bootstrap config/constants.php
  --processor       Register an additional processor.
  --version (-v)    Display Swagger-PHP version.
  --help (-h)       Display this help message.


EOF;
    error_log($help);
    exit;
}
if (count($paths) === 0) {
    $paths[] = getcwd();
    echo "Scanning files in '".$paths[0]."' ...\n";
}
if (class_exists('Swagger\Logger') === false) {
    if (file_exists(__DIR__.'/../vendor/autoload.php')) {  // cloned / dev environment?
        require_once(__DIR__.'/../vendor/autoload.php');
    } else {
        require_once(realpath(__DIR__.'/../../../').'/autoload.php');
    }
}
$errorTypes = [
    E_ERROR => 'ERROR',
    E_WARNING => 'WARNING',
    E_PARSE => 'PARSE',
    E_NOTICE => 'NOTICE',
    E_CORE_ERROR => 'CORE_ERROR',
    E_CORE_WARNING => 'CORE_WARNING',
    E_COMPILE_ERROR => 'COMPILE_ERROR',
    E_COMPILE_WARNING => 'COMPILE_WARNING',
    E_USER_ERROR => 'ERROR',
    E_USER_WARNING => 'WARNING',
    E_USER_NOTICE => 'NOTICE',
    E_STRICT => 'STRICT',
    E_RECOVERABLE_ERROR => 'RECOVERABLE_ERROR',
    E_DEPRECATED => 'DEPRECATED',
    E_USER_DEPRECATED => 'DEPRECATED'
];
set_error_handler(function ($errno, $errstr, $file, $line) use ($errorTypes) {
    if (!(error_reporting() & $errno)) {
        return; // This error code is not included in error_reporting
    }
    $type = array_key_exists($errno, $errorTypes) ? $errorTypes[$errno] : 'ERROR';
    error_log('[' . $type . '] '.$errstr .' in '.$file.' on line '.$line);
    if ($type === 'ERROR') {
        exit($errno);
    }
});
set_exception_handler(function ($exception) use ($options) {
    if ($options['debug']) {
        error_log($exception);
    } else {
        error_log('[EXCEPTION] '.$exception->getMessage() .' in '.$exception->getFile().' on line '.$exception->getLine());
    }
    exit($exception->getCode() ?: 1);
});
\Swagger\Logger::getInstance()->log = function ($entry, $type) {
    $type = $type === E_USER_NOTICE ? 'INFO' : 'WARN';
    if ($entry instanceof Exception) {
        $entry = $entry->getMessage();
    }
    error_log('[' . $type . '] ' . $entry . PHP_EOL);
};
$exclude = null;
if ($options['exclude']) {
    $exclude = $options['exclude'];
    if (strpos($exclude[0], ',') !== false) {
        $exploded = explode(',', $exclude[0]);
        error_log('[NOTICE] Comma-separated exclude paths are deprecated, use multiple --exclude statements: --exclude '.$exploded[0].' --exclude '.$exploded[1]);
        $exclude[0] = array_shift($exploded);
        $exclude = array_merge($exclude, $exploded);
    }
}

foreach ($options["processor"] as $processor) {
    $class = '\Swagger\Processors\\'.$processor;
    if (class_exists($class)) {
        $processor = new $class();
    } elseif (class_exists($processor)) {
        $processor = new $processor();
    }
    \Swagger\Analysis::registerProcessor($processor);
}

$swagger = Swagger\scan($paths, ['exclude' => $exclude]);
$methods = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch'];
$counter = 0;

// Output report
foreach ($swagger->paths as $path) {
    foreach ($path as $method => $operation) {
        if ($operation !== null && in_array($method, $methods)) {
            error_log(str_pad($method, 7, ' ', STR_PAD_LEFT) . ' ' . $path->path);
            $counter++;
        }
    }
}
error_log('----------------------'.  str_repeat('-', strlen($counter)));
error_log($counter.' operations documented');
error_log('----------------------'.  str_repeat('-', strlen($counter)));
if ($options['stdout']) {
    echo $swagger;
} else {
    if (is_dir($options['output'])) {
        $options['output'] .= '/swagger.json';
    }
    $swagger->saveAs($options['output']);
    error_log('Written to '.realpath($options['output']));
}
error_log('');