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

/*
 * This file is part of xrDebug.
 *
 * (c) Rodolfo Berrios <rodolfo@chevere.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

use Chevere\Filesystem\File;
use Chevere\Router\Dependencies;
use Chevere\Router\Dispatcher;
use Chevere\xrDebug\Build;
use Chevere\xrDebug\Debugger;
use Clue\React\Sse\BufferedChannel;
use Colors\Color;
use Psr\Http\Message\ServerRequestInterface;
use React\EventLoop\Loop;
use React\Http\HttpServer;
use React\Http\Middleware\LimitConcurrentRequestsMiddleware;
use React\Http\Middleware\RequestBodyBufferMiddleware;
use React\Http\Middleware\RequestBodyParserMiddleware;
use React\Http\Middleware\StreamingRequestMiddleware;
use React\Http\Server;
use React\Socket\SocketServer;
use React\Stream\ThroughStream;
use samejack\PHP\ArgvParser;
use function Chevere\Filesystem\directoryForPath;
use function Chevere\ThrowableHandler\handleAsTerminal;
use function Chevere\Writer\writers;
use function Chevere\xrDebug\getCipher;
use function Chevere\xrDebug\getPrivateKey;
use function Chevere\xrDebug\getResponse;
use function exec;

require_once __DIR__ . '/loader.php';

$color = new Color();
$logo = (new File($appDirectory->path()->getChild('logo')))->getContents();
$logger = writers()->log();
$version = XRDEBUG_VERSION;
$url = 'https://docs.xrdebug.com';
$header = (string) $color(
    <<<CONSOLE
    <cyan>{$logo}</cyan>
    <green>xrDebug version {$version}</green>
    <dark_gray>https://xrdebug.com</dark_gray>
    <dark_gray>Copyright Rodolfo Berrios A.</dark_gray>

    CONSOLE
)->colorize();
$logger->write($header);
$options = (new ArgvParser())->parseConfigs();
if (array_key_exists('version', $options)) {
    exit(0);
}
if (array_key_exists('h', $options) || array_key_exists('help', $options)) {
    $supportedIdes = (string) $color('')->yellow();
    $help = (string) $color(
        <<<CONSOLE

        <yellow>Usage</yellow>
          xrdebug [options]

        <yellow>Server options</yellow>
          <cyan>-p</cyan> Port <dark_gray>[use 0 for random]</dark_gray> <red>(default 27420)</red>
          <cyan>-a</cyan> IP address <red>(default 0.0.0.0)</red>
          <cyan>-c</cyan> Cert file for TLS <dark_gray>[PEM: local_cert]</dark_gray>
          <cyan>-z</cyan> Private key for TLS <dark_gray>[PEM: local_pk]</dark_gray>

        <yellow>Cipher options</yellow>
          <cyan>-e</cyan> Enable end-to-end encryption
          <cyan>-k</cyan> <dark_gray>[for -e option]</dark_gray> Symmetric key
          <cyan>-s</cyan> Enable sign verification
          <cyan>-x</cyan> <dark_gray>[for -s option]</dark_gray> Private key

        <yellow>Session options</yellow>
          <cyan>-n</cyan> Session name <red>(default xrDebug)</red>
          <cyan>-w</cyan> Working directory <red>(default temp dir)</red>

        <yellow>IDE options</yellow>
          <cyan>-i</cyan> Editor <red>(default vscode)</red>

          atom emacs espresso idea macvim netbeans
          phpstorm sublime textmate vscode

        <yellow>Miscellaneous</yellow>
          <cyan>-h|--help</cyan> Show this help message
          <cyan>--version</cyan> Show version

        <yellow>Docs</yellow>
          <cyan>{$url}</cyan>


        CONSOLE
    )->colorize();
    $logger->write($help);
    exit(0);
}
$address = $options['a'] ?? '0.0.0.0';
$port = $options['p'] ?? '27420';
$sessionName = $options['n'] ?? 'xrDebug';
$localCert = $options['c'] ?? null;
$localPk = $options['z'] ?? null;
$isEncryptionEnabled = $options['e'] ?? false;
$isSignVerificationEnabled = $options['s'] ?? false;
$scheme = isset($localCert) ? 'tls' : 'tcp';
$editor = $options['i'] ?? 'vscode';
$uri = "{$scheme}://{$address}:{$port}";
$context = $scheme === 'tcp'
    ? []
    : [
        'tls' => [
            'local_cert' => $localCert,
            'local_pk' => $localPk,
            'verify_peer' => false,
        ],
    ];
$workingDirectory = realpath($options['w'] ?? sys_get_temp_dir());
if (! $workingDirectory) {
    $logger->write(
        $color("[ERROR] Working directory doesn't exists.")->red() . "\n"
    );
    exit(1);
}
$workingDirectory = directoryForPath($workingDirectory);
$cipher = null;
if ($isEncryptionEnabled) {
    $symmetricKey = $options['k'] ?? null;
    if ($symmetricKey === true) {
        $symmetricKey = null;
    }
    $cipher = getCipher($symmetricKey, $logger, $color);
}
$privateKey = null;
if ($isSignVerificationEnabled) {
    $privateKey = $options['x'] ?? null;
    if ($privateKey === true) {
        $privateKey = null;
    }
    $privateKey = getPrivateKey($privateKey, $logger, $color);
}
$locksDirectory = $workingDirectory->getChild('locks/');
$appCompiledPath = $workingDirectory->getChild('app/compiled/')->path();

try {
    $locksDirectory->removeContents();
} catch (Throwable) {
}
$build = new Build(
    source: $appDirectory->getChild('src/'),
    version: XRDEBUG_VERSION,
    sessionName: $sessionName,
    editor: $editor,
    isEncryptionEnabled: $isEncryptionEnabled,
    isSignVerificationEnabled: $isSignVerificationEnabled
);
$app = new File($appCompiledPath->getChild('en.html'));
$app->removeIfExists();
$app->create();
$app->put($build->__toString());
$dependencies = new Dependencies($routes);
$dispatcher = new Dispatcher($routeCollector);
$loop = Loop::get();
$channel = new BufferedChannel();
$debugger = new Debugger($channel, $logger, $cipher);
$containerMap = [
    'app' => $app,
    'channel' => $channel,
    'cipher' => $cipher,
    'debugger' => $debugger,
    'directory' => $locksDirectory,
    'logger' => $logger,
    'loop' => $loop,
    'privateKey' => $privateKey,
    'stream' => new ThroughStream(),
];
$handler = function (ServerRequestInterface $request) use ($dispatcher, $dependencies, $containerMap) {
    try {
        return getResponse($request, $dispatcher, $dependencies, $containerMap);
    } catch (Throwable $e) {
        handleAsTerminal($e);
    }
};
$http = new HttpServer(
    $loop,
    new StreamingRequestMiddleware(),
    new LimitConcurrentRequestsMiddleware(100),
    new RequestBodyBufferMiddleware(8 * 1024 * 1024),
    new RequestBodyParserMiddleware(100 * 1024, 1),
    $handler
);
$socket = new SocketServer($uri, $context, $loop);
$http->listen($socket);
$socket->on('error', 'printf');
$scheme = parse_url($socket->getAddress(), PHP_URL_SCHEME);
$httpAddress = strtr(
    $socket->getAddress(),
    [
        'tls' => 'https',
        'tcp' => 'http',
        '0.0.0.0' => 'localhost'
    ]
);
$phpVersion = PHP_VERSION . ' ' . PHP_OS . ' (' . PHP_SAPI . ')';
$message = <<<LOG

<cyan>PHP {$phpVersion}</cyan>

Running at {$scheme} <cyan>{$httpAddress}</cyan>
<italic>Press Ctrl+C to quit</italic>
--

LOG;
$message = (string) $color($message)->colorize();
$logger->write($message);
$command = match (strtoupper(substr(PHP_OS, 0, 3))) {
    'WIN' => 'start',
    'DAR' => 'open',
    default => 'xdg-open',
};
exec("{$command} {$httpAddress}");
$loop->run();
