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

import de.arxsilex.maptools.converter.core.preview.PreviewDatasetBuilder
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.absolutePathString
import kotlin.io.path.name

fun main(arguments: Array<String>) {
    val mode = arguments.firstOrNull()
    val scanBlocks = mode == "--scan" || mode == "--decode" || mode == "--preview"
    val decodeObjects = mode == "--decode"
    val buildPreview = mode == "--preview"
    val pathArgument = if (scanBlocks) arguments.getOrNull(1) else arguments.singleOrNull()
    require(pathArgument != null) { "Aufruf: inspectPbf|scanPbf -PpbfFile=<datei.osm.pbf>" }
    val path = Path.of(pathArgument).toAbsolutePath().normalize()
    require(Files.isRegularFile(path)) { "PBF-Datei nicht gefunden: ${path.absolutePathString()}" }

    val metadata = PbfHeaderReader.read(path)
    println("Datei: ${path.name}")
    println("Pfad: ${path.absolutePathString()}")
    println("Größe: ${Files.size(path)} Byte")
    metadata.bounds?.let {
        println("Grenzen: West=${it.west}, Süd=${it.south}, Ost=${it.east}, Nord=${it.north}")
    } ?: println("Grenzen: nicht im OSMHeader enthalten")
    println("Erforderliche Features: ${metadata.requiredFeatures.joinToString().ifEmpty { "keine" }}")
    println("Optionale Features: ${metadata.optionalFeatures.joinToString().ifEmpty { "keine" }}")
    println("Erzeuger: ${metadata.writingProgram ?: "unbekannt"}")
    println("Quelle: ${metadata.source ?: "nicht angegeben"}")

    if (scanBlocks) {
        val startedAt = System.nanoTime()
        var lastReportedPercent = -1
        val index = PbfBlockScanner.scan(path, onProgress = { progress ->
            val percent = if (progress.totalBytes == 0L) 100 else
                (progress.bytesRead * 100L / progress.totalBytes).toInt()
            if (percent >= lastReportedPercent + 10 || percent == 100) {
                println("Scan: $percent % · ${progress.blocksRead} Blöcke")
                lastReportedPercent = percent
            }
        })
        val elapsedSeconds = (System.nanoTime() - startedAt) / 1_000_000_000.0
        println("Blöcke gesamt: ${index.entries.size}")
        println("OSMData-Blöcke: ${index.dataBlockCount}")
        println(
            "Kompression: " + index.entries.groupingBy { it.compression }.eachCount()
                .entries.joinToString { "${it.key}=${it.value}" },
        )
        println("Datei-SHA-256: ${index.sourceSha256}")
        println("Scandauer: %.2f s".format(elapsedSeconds))
        arguments.getOrNull(2)?.let { indexArgument ->
            val indexPath = Path.of(indexArgument).toAbsolutePath().normalize()
            PbfBlockIndexStore.write(index, indexPath)
            val restored = PbfBlockIndexStore.read(indexPath)
            check(PbfBlockIndexStore.matchesSource(restored, path, verifySha256 = true)) {
                "Der gespeicherte Index konnte nicht gegen die Quelldatei validiert werden."
            }
            println("Index gespeichert: $indexPath")
        }

        if (decodeObjects) {
            var nodeCount = 0L
            var taggedNodeCount = 0L
            var wayCount = 0L
            var taggedWayCount = 0L
            var decodedBlocks = 0
            var lastDecodePercent = -1
            val dataEntries = index.entries.filter { it.type == "OSMData" }
            PbfDataBlockReader.open(path).use { source ->
                dataEntries.forEachIndexed { blockIndex, entry ->
                    PrimitiveBlockDecoder.decode(
                        bytes = source.read(entry),
                        onNode = { node ->
                            nodeCount++
                            if (node.tags.isNotEmpty()) taggedNodeCount++
                        },
                        onWay = { way ->
                            wayCount++
                            if (way.tags.isNotEmpty()) taggedWayCount++
                        },
                    )
                    decodedBlocks++
                    val percent = (blockIndex + 1) * 100 / dataEntries.size.coerceAtLeast(1)
                    if (percent >= lastDecodePercent + 10 || percent == 100) {
                        println("Dekodierung: $percent % · $decodedBlocks Datenblöcke")
                        lastDecodePercent = percent
                    }
                }
            }
            println("Nodes: $nodeCount (davon $taggedNodeCount mit Tags)")
            println("Ways: $wayCount (davon $taggedWayCount mit Tags)")
        }
        if (buildPreview) {
            val bounds = metadata.bounds ?: error("Für die Vorschau fehlen geografische Grenzen.")
            val previewStartedAt = System.nanoTime()
            var lastProgress = ""
            val preview = PreviewDatasetBuilder.build(
                source = path,
                bounds = bounds,
                existingIndex = index,
                onProgress = { progress ->
                    val percent = progress.completed * 100 / progress.total.coerceAtLeast(1)
                    val marker = "${progress.phase}: ${percent / 10 * 10}"
                    if (marker != lastProgress) {
                        println("Vorschau ${progress.phase}: $percent %")
                        lastProgress = marker
                    }
                },
            )
            val seconds = (System.nanoTime() - previewStartedAt) / 1_000_000_000.0
            println("Vorschau-Punkte: ${preview.points.size}")
            println("Vorschau-Linien: ${preview.polylines.size}")
            println("Vorschau-Beschriftungen: ${preview.labels.size}")
            println(
                "Beschriftungsklassen: " + preview.labels.groupingBy { it.labelClass }.eachCount()
                    .entries.joinToString { "${it.key}=${it.value}" },
            )
            println("Beispiele: ${preview.labels.take(12).joinToString { it.text }}")
            println("Vorschau-Aufbau: %.2f s".format(seconds))
        }
    }
}
