<?php

function cli_error(string $message): void
{
    $stderr = fopen('php://stderr', 'w');
    if ($stderr !== false) {
        fwrite($stderr, $message);
        fclose($stderr);
        return;
    }
    echo $message;
}
function c_quoted_path(string $path): string
{
    return '"' . addcslashes($path, "\\\"\n\r\t") . '"';
}

function source_map_from_vala(string $valaSource, string $sourceMapInputFile): array
{
    if (!preg_match('/PHPEFC-C-SOURCE-LINES-BEGIN\R(.*?)\RPHPEFC-C-SOURCE-LINES-END/s', $valaSource, $match)) {
        cli_error("Could not find PHPEFC C source-line map comment in: {$sourceMapInputFile}\n");
        exit(1);
    }

    $sourceMap = json_decode($match[1], true);
    if (!is_array($sourceMap) || ($sourceMap['format'] ?? null) !== 'phpefc-c-source-lines-v0') {
        cli_error("Expected phpefc-c-source-lines-v0 source map in: {$sourceMapInputFile}\n");
        exit(1);
    }

    return $sourceMap;
}

if ($argc < 4 || $argc > 5) {
    cli_error("Usage: {$argv[0]} <input.c> <output.c> <input.vala> [debug-bundle.json]\n");
    exit(2);
}

$inputFile = $argv[1];
$outputFile = $argv[2];
$sourceMapInputFile = $argv[3];
$debugBundleFile = $argv[4] ?? "";

$cSource = @file_get_contents($inputFile);
if ($cSource === false) {
    cli_error("Could not read input C file: {$inputFile}\n");
    exit(1);
}

$valaSource = @file_get_contents($sourceMapInputFile);
if ($valaSource === false) {
    cli_error("Could not read Vala source-map input file: {$sourceMapInputFile}\n");
    exit(1);
}

$sourceMap = source_map_from_vala($valaSource, $sourceMapInputFile);

$generatedValaFile = isset($sourceMap['generatedValaFile']) ? basename((string) $sourceMap['generatedValaFile']) : '';
$byValaLine = [];
foreach (($sourceMap['mappings'] ?? []) as $mapping) {
    if (!is_array($mapping)) {
        continue;
    }

    $valaLine = isset($mapping['valaLine']) ? (int) $mapping['valaLine'] : 0;
    $phpLine = isset($mapping['phpLine']) ? (int) $mapping['phpLine'] : 0;
    $phpSourceFile = isset($mapping['phpSourceFile']) ? (string) $mapping['phpSourceFile'] : '';
    if ($valaLine <= 0 || $phpLine <= 0 || $phpSourceFile === '') {
        continue;
    }

    $byValaLine[$valaLine] = [
        'phpLine' => $phpLine,
        'phpSourceFile' => $phpSourceFile,
        'kind' => isset($mapping['kind']) ? (string) $mapping['kind'] : '',
    ];
}

if ($byValaLine === []) {
    cli_error("Source map contains no usable line mappings in: {$sourceMapInputFile}\n");
    exit(1);
}

$lineRewrites = [];
$replacements = 0;
$mappedLines = [];
foreach (preg_split('/\R/', $cSource) as $line) {
    if (preg_match('/^(\s*#line\s+)(\d+)(\s+")([^"]+)(".*)$/', $line, $match)) {
        $valaLine = (int) $match[2];
        $oldPath = $match[4];
        if (isset($byValaLine[$valaLine]) && ($generatedValaFile === '' || basename($oldPath) === $generatedValaFile)) {
            $mapping = $byValaLine[$valaLine];
            $replacements++;
            $kind = $mapping['kind'] !== '' ? ' ' . $mapping['kind'] : '';
            $lineRewrites[$valaLine . ":" . $mapping['phpSourceFile'] . ":" . $mapping['phpLine']] = [
                'kind' => $mapping['kind'],
                'old' => [
                    'file' => $oldPath,
                    'line' => $valaLine,
                    'language' => 'vala',
                ],
                'new' => [
                    'file' => $mapping['phpSourceFile'],
                    'line' => $mapping['phpLine'],
                    'language' => 'php',
                ],
            ];
            $inlineComment = ' /* phpefc-source-line:' . $kind . ' original ' . trim($line)
                . ' => ' . $mapping['phpSourceFile'] . ':' . $mapping['phpLine'] . ' */';
            $mappedLines[] = $match[1] . $mapping['phpLine'] . ' ' . c_quoted_path($mapping['phpSourceFile']) . $inlineComment;
            continue;
        }
    }

    $mappedLines[] = $line;
}

if ($replacements === 0) {
    cli_error("No Valac #line markers matched source map from {$sourceMapInputFile} in {$inputFile}\n");
    exit(1);
}

$mapped = implode("\n", $mappedLines);
if (file_put_contents($outputFile, $mapped) === false) {
    cli_error("Could not write mapped C file: {$outputFile}\n");
    exit(1);
}

if ($debugBundleFile !== '') {
    $debugBundle = [
        'format' => 'phpef-debug-bundle-v0',
        'sourceLines' => $sourceMap,
        'lineRewrites' => array_values($lineRewrites),
        'cSourceFile' => $inputFile,
        'mappedCSourceFile' => $outputFile,
        'valaSourceFile' => $sourceMapInputFile,
        'rewrittenLineMarkers' => $replacements,
    ];
    $debugBundleJson = json_encode($debugBundle, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
    if (file_put_contents($debugBundleFile, $debugBundleJson) === false) {
        cli_error("Could not write PHPEF debug bundle file: {$debugBundleFile}\n");
        exit(1);
    }
    echo "Wrote PHPEF debug bundle to {$debugBundleFile}\n";
}
echo "Wrote mapped C source to {$outputFile} ({$replacements} #line markers rewritten)\n";
