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

require_once __DIR__."/../vendor/autoload.php";

$opts = (object)[
    'help' => false,
    'server' => '127.0.0.1:8000',
    'args' => [],
    'json' => false,
    'from' => null,
    'until' => null,
];

function parse_datetime(string $timestamp): ?DateTime {
    if (strtotime($timestamp)) {
        return new DateTime($timestamp);
    }
    return null;
}

function parse_options() {
    global $argc,$argv,$opts;
    $o = getopt("hs:f:u:j",["help","server:","from:","until:","json"],$optind);
    foreach ($o as $opt=>$val) {
        switch ($opt) {
            case 'h':
            case 'help':
                $opts->help = true;
                break;
            case 's':
            case 'server':
                $opts->server = $val;
                break;
            case 'f':
            case 'from':
                $opts->from = parse_datetime($val);
                break;
            case 'u':
            case 'until':
                $opts->until = parse_datetime($val);
                break;
            case 'j':
            case 'json':
                $opts->json = true;
                break;
        }
    }
    $opts->args = array_slice($argv,$optind);
}

function print_help() {
    global $argv;
    printf("Syntax:\n  %s [options] {action} [args...]\n\n", basename($argv[0]));
    printf("Options:\n");
    printf("  -h,--help             Show this help\n");
    printf("  -s,--server SERVER    Specify the server (ip:port) to use\n");
    printf("  -f,--from DATETIME    Set the validity start timestamp\n");
    printf("  -u,--until DATETIME   Set the validity end timestamp\n");
    printf("  -j,--json             Assume json encoded values with set\n");
    printf("\nActions:\n");
    printf("  set {collection} {key}={value}*\n    Set the keys to value, using the --from and --until values. Multiple\n    key-value pairs can be specified. Use --json to pass values as json.\n");
    printf("  get {collection} [{when}]\n    Fetch the current values, or the values at {when}.\n");
    printf("  show {collection}\n    Show keys and all values, allowing to delete values.\n");
    printf("  delete {collection} {key|id}*\n    Delete a key or a value from a collection\n");
    printf("  purge {collection}\n    Delete an entire collection\n");
}

function exit_error(string $msg, int $code=1): never {
    fwrite(STDERR, $msg."\n");
    exit($code);
}

parse_options();

if ($opts->help) {
    print_help();
    exit(0);
}

$http = new React\Http\Browser();

function http_get(string $url, array $headers) {
    global $opts,$http;
    return $http->get("http://{$opts->server}/{$url}", $headers);
}

function http_post(string $url, array $headers, ?string $body = null) {
    global $opts,$http;
    return $http->post("http://{$opts->server}/{$url}", $headers, $body??'');
}

function action_get(object $opts) {
    $collection = array_shift($opts->args);
    if (!$collection) {
        exit_error("Missing collection. Expected: get {collection}");
    }
    http_get($collection, [])->then(
        function ($response) {
            echo $response->getBody()->getContents();
        },
        function ($error) {
            echo $error->getMessage()."\n";
        }
    );
}

function action_show(object $opts) {
    $collection = array_shift($opts->args);
    if (!$collection) {
        exit_error("Missing collection. Expected: get {collection}");
    }
    http_get($collection."/all", [])->then(
        function ($response) {
            echo $response->getBody()->getContents();
        },
        function ($error) {
            echo $error->getMessage()."\n";
        }
    );
}
function action_delete(object $opts) {
    $collection = array_shift($opts->args);
    if (!$collection) {
        exit_error("Missing collection. Expected: delete {collection} {keys|ids}*");
    }
    $body = json_encode($opts->args);
    http_post($collection."/delete", [ 'content-type' => 'application/json' ], $body)->then(
        function ($response) {
            echo $response->getBody()->getContents();
        },
        function ($error) {
            echo $error->getMessage()."\n";
        }
    );
}

function action_purge(object $opts) {
    $collection = array_shift($opts->args);
    if (!$collection) {
        exit_error("Missing collection. Expected: purge {collection}");
    }
    http_post($collection."/purge", [], null)->then(
        function ($response) {
            echo $response->getBody()->getContents();
        },
        function ($error) {
            echo $error->getMessage()."\n";
        }
    );
}

function action_set(object $opts) {
    $collection = array_shift($opts->args);
    if (!$collection) {
        exit_error("Missing collection. Expected: get {collection}");
    }
    $body = [];
    while ($kvpair = array_shift($opts->args)) {
        $op = [];
        if (!str_contains($kvpair,"=")) {
            exit_error("Invalid key-value pair: {$kvpair}");
        }
        [$key,$value] = explode("=",  $kvpair, 2);
        if (ctype_digit($key)) {
            $op['id'] = $key;
        } else {
            $op['key'] = $key;
        }
        if ($opts->json) {
            $json = json_decode($value);
            if ($json === null && $value !== 'null') {
                $op['value'] = $value;
            } else {
                $op['value'] = $json;
            }
        } else {
            $op['value'] = $value;
        }
        
        if ($opts->from || $opts->until) {
            $vfrom = $opts->from ? [ 'from' => $opts->from->format('Y-m-d H:i:s P') ] : null;
            $vuntil = $opts->until ? [ 'until' => $opts->until->format('Y-m-d H:i:s P') ] : null;
            $op['validity'] = [ ...$vfrom, ...$vuntil ];
        }
        $body[$key] = $op;
    }
    http_post($collection, [ 'content-type'=>'application/json' ], json_encode($body))->then(
        function ($response) {
            echo $response->getBody()->getContents();
        },
        function ($error) {
            echo $error->getMessage()."\n";
        }
    );
}

$action = array_shift($opts->args);

switch ($action) {

    case 'get':
        action_get($opts);
        break;

    case 'show':
        action_show($opts);
        break;

    case 'set':
        action_set($opts);
        break;

    case 'delete':
        action_delete($opts);
        break;

    case 'purge':
        action_purge($opts);
        break;

    default:
        print_help();
        exit(0);
}

