package de.arxsilex.maptools.converter.core.pbf

import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.absolutePathString

fun main(arguments: Array<String>) {
    require(arguments.isNotEmpty()) {
        "Aufruf: ParallelPbfBenchmarkCli <quelle.osm.pbf> [bericht.json] [threads]"
    }
    val source = Path.of(arguments[0]).toAbsolutePath().normalize()
    require(Files.isRegularFile(source)) { "PBF-Datei nicht gefunden: $source" }
    val reportPath = arguments.getOrNull(1)?.let(Path::of)?.toAbsolutePath()?.normalize()
        ?: Path.of("parallel-pbf-benchmark.json").toAbsolutePath().normalize()
    val options = ParallelPbfDecodeOptions(
        workerCount = arguments.getOrNull(2)?.toInt()
            ?: (Runtime.getRuntime().availableProcessors() - 1).coerceIn(1, 16),
    )

    println("P0 scan source=$source bytes=${Files.size(source)}")
    val scanStartedAt = System.nanoTime()
    val index = PbfBlockScanner.scan(source, onProgress = { progress ->
        if (progress.blocksRead % 500 == 0) {
            println("P0 scan blocks=${progress.blocksRead} bytes=${progress.bytesRead}")
        }
    })
    val scanMillis = (System.nanoTime() - scanStartedAt) / 1_000_000L
    println("P1 decode blocks=${index.dataBlockCount} threads=${options.workerCount}")
    val report = ParallelPbfPrimitiveDecoder.decode(source, index, options, onProgress = { progress ->
        if (progress.completedBlocks % 250 == 0 || progress.completedBlocks == progress.totalBlocks) {
            println("P1 decode blocks=${progress.completedBlocks}/${progress.totalBlocks}")
        }
    })

    Files.createDirectories(reportPath.parent)
    Files.writeString(reportPath, report.toJson(source, scanMillis))
    println("P1 completed elapsedMs=${report.elapsedMillis} report=$reportPath")
}

private fun ParallelPbfDecodeReport.toJson(source: Path, scanMillis: Long): String = """
    {
      "schemaVersion": 1,
      "source": "${source.absolutePathString().jsonEscaped()}",
      "sourceBytes": ${Files.size(source)},
      "availableProcessors": ${Runtime.getRuntime().availableProcessors()},
      "workerCount": $workerCount,
      "scanMillis": $scanMillis,
      "decodeMillis": $elapsedMillis,
      "blockCount": $blockCount,
      "nodeCount": $nodeCount,
      "wayCount": $wayCount,
      "relationCount": $relationCount,
      "packedBytes": $packedBytes,
      "unpackedBytes": $unpackedBytes,
      "decompressionProvider": "$decompressionProvider",
      "hardwareAccelerated": $hardwareAccelerated
    }
""".trimIndent() + "\n"

private fun String.jsonEscaped(): String = buildString(length) {
    this@jsonEscaped.forEach { character ->
        when (character) {
            '\\' -> append("\\\\")
            '"' -> append("\\\"")
            '\n' -> append("\\n")
            '\r' -> append("\\r")
            '\t' -> append("\\t")
            else -> append(character)
        }
    }
}
