package de.arxsilex.maptools.converter.worker

import de.arxsilex.maptools.converter.core.GeoBounds
import de.arxsilex.maptools.converter.core.ContentSelection
import de.arxsilex.maptools.converter.core.MapFormatVersion
import de.arxsilex.maptools.converter.core.MapsforgeProcessorPolicy
import de.arxsilex.maptools.converter.core.MapsforgeProcessorType
import de.arxsilex.maptools.converter.core.PoiSchemaVersion
import java.nio.file.Files
import java.nio.file.Path

data class MapsforgeRequest(
    val input: Path,
    val mapOutput: Path?,
    val poiOutput: Path?,
    val bounds: GeoBounds,
    val content: ContentSelection,
    val mapFormatVersion: MapFormatVersion,
    val poiSchemaVersion: PoiSchemaVersion,
    val processorType: MapsforgeProcessorType,
    val temporaryDirectory: Path,
) {
    init {
        require(Files.isRegularFile(input)) { "Eingabedatei nicht gefunden: $input" }
        require(mapOutput != null || poiOutput != null) { "Mindestens eine Mapsforge-Ausgabe fehlt." }
        require(poiOutput == null || content.pointsOfInterest) {
            "Eine .poi-Ausgabe benötigt die Inhaltsgruppe Points of Interest."
        }
    }

    fun mapArguments(output: Path, tagMapping: Path): Array<String> = arrayOf(
        "--read-pbf", "file=$input",
        "--mapfile-writer", "file=$output", "type=${processorType.cliValue}",
        "bbox=${bounds.south},${bounds.west},${bounds.north},${bounds.east}",
        "map-start-position=${(bounds.south + bounds.north) / 2},${(bounds.west + bounds.east) / 2}",
        "map-start-zoom=12", "tag-values=${mapFormatVersion == MapFormatVersion.V5}",
        "preferred-languages=de,en", "tag-conf-file=$tagMapping",
        "comment=Map data (c) OpenStreetMap contributors", "progress-logs=true",
        "threads=${mapWriterThreads()}",
    )

    internal fun mapWriterThreads(availableProcessors: Int = Runtime.getRuntime().availableProcessors()): Int =
        availableProcessors.coerceIn(1, MAX_MAP_WRITER_THREADS)

    fun poiArguments(output: Path): Array<String> = arrayOf(
        "--read-pbf", "file=$input",
        "--poi-writer", "file=$output",
        "bbox=${bounds.south},${bounds.west},${bounds.north},${bounds.east}",
        "all-tags=true", "preferred-language=de",
        "comment=Map data (c) OpenStreetMap contributors", "progress-logs=true",
    )

    companion object {
        private const val MAX_MAP_WRITER_THREADS = 8

        fun parse(arguments: Array<String>): MapsforgeRequest {
            require(arguments.firstOrNull() == "mapsforge") { "Erwarteter Worker-Befehl: mapsforge" }
            val values = arguments.drop(1).chunked(2).associate { pair ->
                require(pair.size == 2 && pair[0].startsWith("--")) { "Ungültige Worker-Argumente." }
                pair[0].removePrefix("--") to pair[1]
            }
            fun required(name: String): String = values[name] ?: error("Worker-Argument --$name fehlt.")
            fun optionalPath(name: String): Path? = values[name]?.takeUnless { it == "-" }?.let(Path::of)
            val input = Path.of(required("input")).toAbsolutePath().normalize()
            return MapsforgeRequest(
                input = input,
                mapOutput = optionalPath("map-output")?.toAbsolutePath()?.normalize(),
                poiOutput = optionalPath("poi-output")?.toAbsolutePath()?.normalize(),
                mapFormatVersion = MapFormatVersion.entries.single {
                    it.metadataVersion == required("map-format").toInt()
                },
                poiSchemaVersion = PoiSchemaVersion.entries.single {
                    it.metadataVersion == required("poi-schema").toInt()
                },
                processorType = values["processor"]?.let(MapsforgeProcessorType::fromCliValue)
                    ?: MapsforgeProcessorPolicy.select(Files.size(input), preferDiskProcessor = false),
                bounds = GeoBounds(
                    west = required("west").toDouble(), south = required("south").toDouble(),
                    east = required("east").toDouble(), north = required("north").toDouble(),
                ),
                content = ContentSelection(
                    roads = required("roads").toBooleanStrict(),
                    pathsAndTrails = required("paths").toBooleanStrict(),
                    buildings = required("buildings").toBooleanStrict(),
                    landAndWater = required("land-water").toBooleanStrict(),
                    pointsOfInterest = required("pois").toBooleanStrict(),
                    sourceMetadata = required("metadata").toBooleanStrict(),
                ),
                temporaryDirectory = values["temp-dir"]?.let(Path::of)?.toAbsolutePath()?.normalize()
                    ?: input.parent.resolve(".maptools-temp"),
            )
        }
    }
}
