package de.arxsilex.maptools.converter.core

data class GeoBounds(
    val west: Double,
    val south: Double,
    val east: Double,
    val north: Double,
) {
    init {
        require(west in -180.0..180.0) { "Der westliche Längengrad ist ungültig." }
        require(east in -180.0..180.0) { "Der östliche Längengrad ist ungültig." }
        require(south in -90.0..90.0) { "Der südliche Breitengrad ist ungültig." }
        require(north in -90.0..90.0) { "Der nördliche Breitengrad ist ungültig." }
        require(west < east) { "West muss kleiner als Ost sein." }
        require(south < north) { "Süd muss kleiner als Nord sein." }
    }

    fun subset(area: NormalizedMapArea): GeoBounds {
        val longitudeSpan = east - west
        val latitudeSpan = north - south
        return GeoBounds(
            west = west + longitudeSpan * area.left,
            south = north - latitudeSpan * area.bottom,
            east = west + longitudeSpan * area.right,
            north = north - latitudeSpan * area.top,
        )
    }

    /** Converts geographic coordinates back into the normalized selection used by the UI. */
    fun normalizedAreaFor(subset: GeoBounds): NormalizedMapArea {
        val epsilon = 0.000_000_1
        require(
            subset.west >= west - epsilon && subset.east <= east + epsilon &&
                subset.south >= south - epsilon && subset.north <= north + epsilon,
        ) { "Die Bounding-Box muss innerhalb der geladenen Quelldaten liegen." }
        val longitudeSpan = east - west
        val latitudeSpan = north - south
        return NormalizedMapArea(
            left = ((subset.west - west) / longitudeSpan).toFloat().coerceIn(0f, 1f),
            top = ((north - subset.north) / latitudeSpan).toFloat().coerceIn(0f, 1f),
            right = ((subset.east - west) / longitudeSpan).toFloat().coerceIn(0f, 1f),
            bottom = ((north - subset.south) / latitudeSpan).toFloat().coerceIn(0f, 1f),
        )
    }

    fun intersects(other: GeoBounds): Boolean =
        east >= other.west && west <= other.east && north >= other.south && south <= other.north

    fun union(other: GeoBounds): GeoBounds = GeoBounds(
        west = minOf(west, other.west),
        south = minOf(south, other.south),
        east = maxOf(east, other.east),
        north = maxOf(north, other.north),
    )
}

fun Iterable<GeoBounds>.unionOrNull(): GeoBounds? = iterator().let { iterator ->
    if (!iterator.hasNext()) return@let null
    var combined = iterator.next()
    while (iterator.hasNext()) combined = combined.union(iterator.next())
    combined
}

data class PbfHeaderMetadata(
    val bounds: GeoBounds?,
    val requiredFeatures: List<String>,
    val optionalFeatures: List<String>,
    val writingProgram: String?,
    val source: String?,
)
