<?php

use RR\Shunt\Context;
use RR\Shunt\Parser;
use RR\Shunt\Scanner;

const MESSAGE_DIR = __DIR__ . "/messages";

$__i18n_msg_store = [];
$__i18n_msg_store_plural = [];
$__i18n_msg_metadata = [];
/** @var string $__i18n_current_locale */
$__i18n_current_locale = "en";

const _I18N_MSGID = 0;
const _I18N_MSGSTR = 1;
const _I18N_MSGIDP = 2;
const _I18N_MSGSTRP = 3;
const _I18N_META = 10;

function i18n_parse(string $contents, ?string $filename = null): array {
    $syntax_error = fn(string $msg, int $line): never =>
        throw new Exception("i18n syntax error: $msg (in " . ($filename ?? "unknown") . ":" . $line . ")");
    $other_error = fn(string $msg, int $line): never =>
        throw new Exception("i18n error: $msg (in " . ($filename ?? "unknown") . ":" . $line . ")");

    $metadata = [];
    
    $msgs = [];
    $pmsgs = [];
    $lines = explode("\n", $contents);
    $currentId = "";
    $currentContext = "";
    $currentMessage = "";
    $currentIdP = [];
    $currentMessageP = [];
    $currentMetadataBuffer = [];
    $state = _I18N_MSGID;
    
    $processedLines = 0;
    foreach ($lines as $i => $ln) {
        $lnNum = $i + 1;
        if (trim($ln) === "")
            continue;

        if ($ln === "metadata({") {
            if ($processedLines > 0)
                $syntax_error("metadata must come first in file", $lnNum);
            $currentMetadataBuffer []= "{";
            $state = _I18N_META;
            ++$processedLines;
            continue;
        }
        if ($state == _I18N_META) {
            if ($ln === "})") {
                $currentMetadataBuffer []= "}";
                $state = _I18N_MSGSTR;
                $currentMetadataBuffer = implode("\n", $currentMetadataBuffer);
                $metadata = json_decode($currentMetadataBuffer, true);
            } else {
                $currentMetadataBuffer []= $ln;
            }
            ++$processedLines;
            continue;
        }

        switch ($ln[0]) {
            case "#":
                continue 2;
            case ":":
                if ($state === _I18N_MSGSTRP) {
                    // plural
                    if (count($currentIdP) > 0) {
                        if (isset($msgs[$currentId]))
                            $other_error("duplicate message id '$currentId'", $lnNum);
                        $pmsgs[$currentIdP[0]] = $currentMessageP;
                    }
                } else {
                    // singular
                    if ($currentId !== "") {
                        if ($currentContext !== "")
                            $currentId = $currentContext . "\x7F" . $currentId;
                        if (isset($msgs[$currentId]))
                            $other_error("duplicate message id '$currentId'", $lnNum);
                        $msgs[$currentId] = $currentMessage;
                    }
                }
                $currentIdP = [];
                $currentMessageP = [];
                $currentId = "";
                $currentContext = "";
                $currentMessage = "";
                if ($ln === ":...") {
                    // plural
                    $state = _I18N_MSGIDP;
                } else {
                    // singular
                    $currentId = json_decode(substr($ln, 2)) ?? $syntax_error("malformed string", $lnNum);
                    $state = _I18N_MSGID;
                }
                break;
            case "-":
                if ($state === _I18N_MSGIDP) {
                    $_id = json_decode(substr($ln, 2)) ?? $syntax_error("malformed string", $lnNum);
                    if ($currentContext !== "")
                        $_id = $currentContext . "\x7F" . $_id;
                    $currentIdP []= $_id;
                } elseif ($state === _I18N_MSGSTRP) {
                    $currentMessageP []= json_decode(substr($ln, 2)) ?? $syntax_error("malformed string", $lnNum);
                } else
                    $syntax_error("cannot define multiple ids/messages in singular mode", $lnNum);
                break;
            case "=":
                if ($ln === "=...") {
                    // plural
                    if ($state !== _I18N_MSGIDP)
                        $syntax_error("cannot start plural message in singular mode", $lnNum);
                    $state = _I18N_MSGSTRP;
                } else {
                    // singular
                    $state = _I18N_MSGSTR;
                    $currentMessage = json_decode(substr($ln, 2)) ?? $syntax_error("malformed string", $lnNum);
                }
                break;
            case "?":
                if ($state !== _I18N_MSGID)
                    $syntax_error("context must be defined before start of message", $lnNum);
                $currentContext = json_decode(substr($ln, 2)) ?? $syntax_error("malformed string", $lnNum);
                break;
            case " ":
                if ($state === _I18N_MSGSTR)
                    $currentMessage .= json_decode(substr($ln, 2)) ?? $syntax_error("malformed string", $lnNum);
                else
                    $currentId .= json_decode(substr($ln, 2)) ?? $syntax_error("malformed string", $lnNum);
                break;
            default:
                $syntax_error("invalid start of line '" . $ln[0] . "' (0x" . str_pad(strtoupper(dechex(ord($ln[0]))), 2, "0", STR_PAD_LEFT) . ")", $lnNum);
                break;
        }
        
        ++$processedLines;
    }

    if ($state === _I18N_MSGSTRP) {
        // plural
        if (count($currentIdP) > 0) {
            if (isset($msgs[$currentId]))
                $other_error("duplicate message id '$currentId'", $lnNum);
            $pmsgs[$currentIdP[0]] = $currentMessageP;
        }
    } else {
        // singular
        if ($currentId !== "") {
            if ($currentContext !== "")
                $currentId = $currentContext . "\x7F" . $currentId;
            if (isset($msgs[$currentId]))
                $other_error("duplicate message id '$currentId'", $lnNum);
            $msgs[$currentId] = $currentMessage;
        }
    }
    return [$metadata, $msgs, $pmsgs];
}

function i18n_locale(string $locale) {
    global $__i18n_current_locale;
    $__i18n_current_locale = $locale;
}

function i18n_get(string $msgid, array $params = [], ?string $context = null): string {
    global $__i18n_current_locale, $__i18n_msg_store;

    $key = $msgid;
    if ($context !== null)
        $key = $context . "\x7F" . $msgid;

    $msg = ($__i18n_msg_store[$__i18n_current_locale] ?? [])[$msgid] ?? $key;

    uksort($params, fn(string $a, string $b): int => strlen($b) <=> strlen($a));
    return str_replace(
        array_map(fn(string $k): string => "%$k%", array_keys($params)),
        array_values($params),
        $msg
    );
}

function i18n_get_plural(string $msgid_singular, string $msgid_plural, int $count, array $params = [], ?string $context = null): string {
    global $__i18n_current_locale, $__i18n_msg_metadata, $__i18n_msg_store_plural;

    $key = $msgid_singular;
    if ($context !== null)
        $key = $context . "\x7F" . $key;

    $msgs = ($__i18n_msg_store_plural[$__i18n_current_locale] ?? [])[$msgid_singular] ?? null;
    $msg = "";
    if ($msgs === null) {
        $msg = $count === 1 ? $msgid_singular : $msgid_plural;
    } else {
        $expression = (($__i18n_msg_metadata[$__i18n_current_locale] ?? [])["plural"] ?? [])["indexMapping"] ?? "n == 1 ? 0 : 1";
        $ctx = new Context();
        $ctx->def("ifelse", function(bool $c, $a, $b) { return $c ? $a : $b; });
        $ctx->def("n", $count);
        $parser = new Parser(new Scanner($expression));
        $index = $parser->reduce($ctx);
        $index = max(0, min(count($msgs) - 1, $index));
        $msg = $msgs[$index];
    }

    uksort($params, fn(string $a, string $b): int => strlen($b) <=> strlen($a));
    return str_replace(
        array_map(fn(string $k): string => "%$k%", array_keys($params)),
        array_values($params),
        $msg
    );
}

function i18n_get_current_locale(): string {
    global $__i18n_current_locale;
    return $__i18n_current_locale;
}

function i18n_get_available_locales(): array {
    global $__i18n_msg_store;
    return [
        "en",
        ...array_keys($__i18n_msg_store),
    ];
}

function i18n_get_message_store(string $locale): array {
    global $__i18n_msg_store, $__i18n_msg_store_plural;
    return [
        $__i18n_msg_store[$locale] ?? (object)[],
        $__i18n_msg_store_plural[$locale] ?? (object)[],
    ];
}

function __(string $msgid, array $params = [], ?string $context = null): string {
    return i18n_get($msgid, $params, $context);
}

function ___(string $msgid_singular, string $msgid_plural, int $count, array $params = [], ?string $context = null): string {
    return i18n_get_plural($msgid_singular, $msgid_plural, $count, $params, $context);
}

foreach (scandir(MESSAGE_DIR) as $ent) {
    $path = MESSAGE_DIR . "/" . $ent;
    if ($ent[0] === "." || !is_file($path) || strcasecmp(pathinfo($ent, PATHINFO_EXTENSION), "msg") !== 0)
        continue;
    $lang = pathinfo($ent, PATHINFO_FILENAME);
    [$meta, $msgs, $pmsgs] = i18n_parse(file_get_contents($path), $ent);
    $__i18n_msg_store[$lang] = $msgs;
    $__i18n_msg_store_plural[$lang] = $pmsgs;
    $__i18n_msg_metadata[$lang] = $meta;
}