package de.arxsilex.maptools.converter.worker

import de.arxsilex.maptools.converter.core.GeoBounds
import de.arxsilex.maptools.converter.core.GeoRectangleRegion
import de.arxsilex.maptools.converter.core.GeoSelectionRegion
import java.nio.file.Files
import java.nio.file.Path

data class CombineRequest(
    val inputs: List<Path>,
    val output: Path,
    val bounds: GeoBounds,
    val cacheDirectory: Path,
    val temporaryDirectory: Path,
    val areas: List<GeoSelectionRegion> = listOf(GeoRectangleRegion(bounds)),
    val preCroppedInputs: Set<Path> = emptySet(),
) {
    init {
        require(inputs.size >= 2) { "Zum Kombinieren werden mindestens zwei Eingabedateien benötigt." }
        require(inputs.distinct().size == inputs.size) { "Eine Quelldatei wurde mehrfach angegeben." }
        inputs.forEach { input ->
            require(Files.isRegularFile(input)) { "Eingabedatei nicht gefunden: $input" }
            require(input.toAbsolutePath().normalize() != output.toAbsolutePath().normalize()) {
                "Ein- und Ausgabedatei dürfen nicht identisch sein."
            }
        }
        require(areas.isNotEmpty()) { "Mindestens ein Auswahlbereich ist erforderlich." }
        require(SelectionBoundsCodec.envelope(areas) == bounds) { "Die Auswahlhülle stimmt nicht mit den Teilbereichen überein." }
        require(preCroppedInputs.all { it in inputs }) {
            "Bereits zugeschnittene Quellen müssen zugleich als Eingabe angegeben sein."
        }
    }

    fun osmosisMergeArguments(croppedInputs: List<Path>, temporaryOutput: Path): Array<String> {
        require(croppedInputs.size >= 2) { "Zum Zusammenführen werden mindestens zwei Ausschnitte benötigt." }
        return buildList {
            croppedInputs.forEachIndexed { index, input ->
                add("--read-pbf")
                add("file=${input.toAbsolutePath().normalize()}")
                add("--sort")
                add("type=TypeThenId")
                if (index > 0) {
                    add("--merge")
                    add("conflictResolutionMethod=version")
                    add("boundRemovedAction=warn")
                }
            }
            add("--write-pbf")
            add("file=${temporaryOutput.toAbsolutePath().normalize()}")
            add("omitmetadata=true")
            add("usedense=true")
            add("compress=deflate")
        }.toTypedArray()
    }

    companion object {
        fun parse(arguments: Array<String>): CombineRequest {
            require(arguments.firstOrNull() == "combine") { "Erwarteter Worker-Befehl: combine" }
            val inputs = mutableListOf<Path>()
            val preCroppedInputs = linkedSetOf<Path>()
            val values = linkedMapOf<String, String>()
            var index = 1
            while (index < arguments.size) {
                require(index + 1 < arguments.size && arguments[index].startsWith("--")) {
                    "Ungültige Worker-Argumente."
                }
                val name = arguments[index].removePrefix("--")
                val value = arguments[index + 1]
                if (name == "input") {
                    inputs.add(Path.of(value).toAbsolutePath().normalize())
                } else if (name == "pre-cropped-input") {
                    preCroppedInputs.add(Path.of(value).toAbsolutePath().normalize())
                } else {
                    values[name] = value
                }
                index += 2
            }
            fun required(name: String): String = values[name] ?: error("Worker-Argument --$name fehlt.")
            val output = Path.of(required("output")).toAbsolutePath().normalize()
            val areas = SelectionBoundsCodec.fromArguments(values)
            return CombineRequest(
                inputs = inputs,
                output = output,
                bounds = SelectionBoundsCodec.envelope(areas),
                cacheDirectory = values["cache-dir"]?.let(Path::of)?.toAbsolutePath()?.normalize()
                    ?: output.parent.resolve("cache"),
                temporaryDirectory = values["temp-dir"]?.let(Path::of)?.toAbsolutePath()?.normalize()
                    ?: output.parent.resolve(".maptools-temp"),
                areas = areas,
                preCroppedInputs = preCroppedInputs,
            )
        }
    }
}
