<?php


function valair_library_set_allowed_native_runtimes(string $raw): void
{
    $raw = trim($raw);
    if ($raw === '' || $raw === '*') {
        $GLOBALS['VALAIR_LIBRARY_ALLOWED_NATIVE_RUNTIMES'] = null;
        return;
    }
    $allowed = [];
    foreach (explode(',', $raw) as $name) {
        $name = trim($name);
        if ($name === '') {
            continue;
        }
        if (str_starts_with($name, 'phpef-runtime-native-')) {
            $name = substr($name, strlen('phpef-runtime-native-'));
        }
        $allowed[$name] = true;
    }
    $GLOBALS['VALAIR_LIBRARY_ALLOWED_NATIVE_RUNTIMES'] = $allowed;
}

function valair_library_native_runtime_allowed(string $runtimeName): bool
{
    $allowed = $GLOBALS['VALAIR_LIBRARY_ALLOWED_NATIVE_RUNTIMES'] ?? null;
    if ($allowed === null) {
        return true;
    }
    return isset($allowed[$runtimeName]);
}

function valair_library_compile_die_disallowed_native(string $runtimeName, string $callee): never
{
    $package = 'phpef-runtime-native-' . $runtimeName;
    throw new RuntimeException(
        "library-embed2native wants to use {$package} for {$callee}, but that native runtime is not allowed by meson.build.pw. " .
        "Add `phpef-runtime-natives = {$runtimeName}` (or include {$runtimeName} in the list) and add {$package} to the ebuild RDEPEND, " .
        "or disable the library optimizer / allow embed fallback."
    );
}

function valair_library_embed_vapi_paths(): array
{
    $paths = [];

    $explicit = getenv("PHPEF_RUNTIME_EMBED_VAPI");
    if (is_string($explicit) && $explicit !== "") {
        $paths[] = $explicit;
    }

    $vapidir = getenv("PHPEF_RUNTIME_VAPIDIR");
    if (is_string($vapidir) && $vapidir !== "") {
        $paths[] = rtrim($vapidir, "/") . "/phpef-runtime-embed.vapi";
    }

    $paths[] = "/usr/share/vala/vapi/phpef-runtime-embed.vapi";
    $paths[] = "/usr/local/share/vala/vapi/phpef-runtime-embed.vapi";

    return array_values(array_unique($paths));
}

function valair_library_embed_phpfunc_names(): array
{
    static $names = null;
    if ($names !== null) {
        return $names;
    }

    $names = [];
    foreach (valair_library_embed_vapi_paths() as $path) {
        $vapi = @file_get_contents($path);
        if ($vapi === false) {
            continue;
        }
        if (preg_match_all("/\\bpublic\\s+static\\s+[A-Za-z0-9_?.<>\\[\\]]+\\s+(phpfunc_[A-Za-z_][A-Za-z0-9_]*)\\s*\\(/", $vapi, $matches)) {
            foreach ($matches[1] as $functionName) {
                $names[$functionName] = true;
            }
        }
    }

    return $names;
}

function valair_library_native_standard_vapi_paths(): array
{
    $paths = [];
    $explicit = getenv("PHPEF_RUNTIME_NATIVE_STANDARD_VAPI");
    if (is_string($explicit) && $explicit !== "") {
        $paths[] = $explicit;
    }
    $explicitList = getenv("PHPEF_RUNTIME_NATIVE_VAPIS");
    if (is_string($explicitList) && $explicitList !== "") {
        foreach (explode(PATH_SEPARATOR, $explicitList) as $path) {
            if ($path !== "" && basename($path) === "phpef-runtime-native-standard.vapi") {
                $paths[] = $path;
            }
        }
    }
    $vapidir = getenv("PHPEF_RUNTIME_VAPIDIR");
    if (is_string($vapidir) && $vapidir !== "") {
        $paths[] = rtrim($vapidir, "/") . "/phpef-runtime-native-standard.vapi";
    }
    $paths[] = "/usr/share/vala/vapi/phpef-runtime-native-standard.vapi";
    $paths[] = "/usr/local/share/vala/vapi/phpef-runtime-native-standard.vapi";
    return array_values(array_unique($paths));
}

function valair_library_native_standard_phpfunc_names(): array
{
    static $names = null;
    if ($names !== null) {
        return $names;
    }
    $names = [];
    foreach (valair_library_native_standard_vapi_paths() as $path) {
        $vapi = @file_get_contents($path);
        if ($vapi === false) {
            continue;
        }
        if (preg_match_all("/\\bpublic\\s+static\\s+[A-Za-z0-9_?.<>\\[\\]]+\\s+(phpfunc_[A-Za-z_][A-Za-z0-9_]*)\\s*\\(/", $vapi, $matches)) {
            foreach ($matches[1] as $functionName) {
                $names[$functionName] = true;
            }
        }
    }
    return $names;
}

function valair_library_embed_is_callee(string $callee): bool
{
    if (str_starts_with($callee, "PhpefRuntimeEmbed.")) {
        return true;
    }
    if (str_starts_with($callee, "phpfunc_zendcall_") || str_starts_with($callee, "zendcall_")) {
        return true;
    }
    if (preg_match("/^phpfunc_[A-Za-z_][A-Za-z0-9_]*$/", $callee) === 1) {
        $embedPhpfuncNames = valair_library_embed_phpfunc_names();
        return $embedPhpfuncNames === [] || isset($embedPhpfuncNames[$callee]);
    }

    return false;
}

function valair_library_embed_count_calls(mixed $node): int
{
    if (is_array($node)) {
        if (($node["kind"] ?? null) === "Comment") {
            return 0;
        }

        $count = 0;
        foreach ($node as $key => $value) {
            if (is_string($value)) {
                if ($key === "callee" && valair_library_embed_is_callee($value)) {
                    $count++;
                }
                if (str_contains($value, "libphp.so")) {
                    $count++;
                }
                continue;
            }
            $count += valair_library_embed_count_calls($value);
        }
        return $count;
    }

    if (is_object($node)) {
        return valair_library_embed_count_calls(ValaIrOptimizerSupport::toDebugArray($node));
    }

    return 0;
}

function valair_library_embed_original_call_name(string $callee): ?string
{
    $shortName = $callee;
    $dotPos = strrpos($shortName, ".");
    if ($dotPos !== false) {
        $shortName = substr($shortName, $dotPos + 1);
    }

    if (str_starts_with($shortName, "phpfunc_zendcall_")) {
        return substr($shortName, strlen("phpfunc_")) . "()";
    }
    if (str_starts_with($shortName, "zendcall_")) {
        return $shortName . "()";
    }
    if (str_starts_with($shortName, "phpfunc_")) {
        $phpName = substr($shortName, strlen("phpfunc_"));
        $typedPos = strpos($phpName, "_typed_");
        if ($typedPos !== false) {
            $phpName = substr($phpName, 0, $typedPos);
        }
        return $phpName . "()";
    }
    if ($callee === "PhpefRuntimeEmbed.get_constant") {
        return "get_constant()";
    }
    return null;
}

function valair_library_embed_call_names(mixed $node): array
{
    $names = [];
    valair_library_embed_collect_call_names($node, $names);
    return array_keys($names);
}

function valair_library_embed_collect_call_names(mixed $node, array &$names): void
{
    if (is_array($node)) {
        if (($node["kind"] ?? null) === "Comment") {
            return;
        }
        foreach ($node as $key => $value) {
            if (is_string($value)) {
                if ($key === "callee" && valair_library_embed_is_callee($value)) {
                    $name = valair_library_embed_original_call_name($value);
                    if ($name !== null) {
                        $names[$name] = true;
                    }
                }
                continue;
            }
            valair_library_embed_collect_call_names($value, $names);
        }
        return;
    }

    if (is_object($node)) {
        valair_library_embed_collect_call_names(ValaIrOptimizerSupport::toDebugArray($node), $names);
    }
}

function valair_library_short_callee_name(string $callee): string
{
    $dotPos = strrpos($callee, ".");
    return $dotPos === false ? $callee : substr($callee, $dotPos + 1);
}

function valair_library_native_standard_replacement(string $callee): ?string
{
    $shortName = valair_library_short_callee_name($callee);
    if (!str_starts_with($shortName, "phpfunc_") || str_starts_with($shortName, "phpfunc_zendcall_")) {
        return null;
    }

    $nativeNames = valair_library_native_standard_phpfunc_names();
    if (!isset($nativeNames[$shortName])) {
        return null;
    }
    if (!valair_library_native_runtime_allowed('standard')) {
        valair_library_compile_die_disallowed_native('standard', $shortName);
    }

    if ($callee === $shortName || str_starts_with($callee, "PhpefRuntimeEmbed.")) {
        return "PhpefRuntimeNativeStandard." . $shortName;
    }
    return null;
}


function valair_library_embed_result_text(int $after, int $before, array $callNames): string
{
    $resultText = "{$after}/{$before} embed libphp.so calls left";
    if ($callNames !== []) {
        $resultText .= " (" . implode(", ", $callNames) . ")";
    }
    return $resultText;
}

function valair_library_embed2native_value(mixed $value): mixed
{
    if (is_array($value)) {
        $changed = false;
        $result = [];
        foreach ($value as $key => $entry) {
            $newEntry = valair_library_embed2native_value($entry);
            $changed = $changed || $newEntry !== $entry;
            $result[$key] = $newEntry;
        }
        return $changed ? $result : $value;
    }

    if (!is_object($value)) {
        return $value;
    }

    $node = $value;
    if ($node instanceof ValaIrCallStatement || $node instanceof ValaIrStaticCallExpression) {
        $array = $node->toArray();
        $callee = $array["callee"] ?? null;
        if (is_string($callee)) {
            $replacement = valair_library_native_standard_replacement($callee);
            if ($replacement !== null) {
                $node = $node->withCalleeAndArgs($replacement, $node->args());
            }
        }
    }

    $reflection = new ReflectionObject($node);
    foreach ($reflection->getProperties() as $property) {
        $property->setAccessible(true);
        $propertyValue = $property->getValue($node);
        if (!is_array($propertyValue) && !is_object($propertyValue)) {
            continue;
        }
        $newPropertyValue = valair_library_embed2native_value($propertyValue);
        if ($newPropertyValue !== $propertyValue) {
            $property->setValue($node, $newPropertyValue);
        }
    }

    return $node;
}

function valair_library_embed2native_node(object $node): object
{
    return valair_library_embed2native_value($node);
}

function valair_library_embed2native_stmts(array $stmts): array
{
    return valair_library_embed2native_value($stmts);
}

function valair_optimize_library_embed2native(object $valaIr): object
{
    $before = valair_library_embed_count_calls($valaIr);
    $optimized = valair_library_embed2native_node($valaIr);
    $after = valair_library_embed_count_calls($optimized);
    $resultText = valair_library_embed_result_text($after, $before, valair_library_embed_call_names($optimized));
    if ($after > 0) {
        ValaIrOptimizerSupport::cliRedWarning("WARNING: PERFORMANCE NOT OPTIMIZED, USING libphp.so ({$resultText})\n");
    }
    $marker = "// phpefc optimizer library-embed2native result: {$resultText}";
    return ValaIrOptimizerSupport::appendMarker($optimized, $marker);
}

function valair_optimize_library_cli(array $argv, int $argc): void
{
    $filteredArgv = [$argv[0]];
    for ($i = 1; $i < $argc; $i++) {
        if ($argv[$i] === '--allowed-native-runtimes') {
            $i++;
            if ($i >= $argc) {
                ValaIrOptimizerSupport::cliError("Missing value for --allowed-native-runtimes\n");
                exit(2);
            }
            valair_library_set_allowed_native_runtimes($argv[$i]);
            continue;
        }
        $filteredArgv[] = $argv[$i];
    }

    ValaIrOptimizerSupport::runFixedCli(
        $filteredArgv,
        count($filteredArgv),
        "library-embed2native",
        "valair_optimize_library_embed2native",
    );
}

valair_optimize_library_cli($argv, $argc);
