package de.arxsilex.maptools.converter.core

/** A non-empty collection of rectangular, normalized export areas. */
data class MapAreaSelection(val areas: List<NormalizedMapArea>) {
    init {
        require(areas.isNotEmpty()) { "Mindestens ein Auswahlbereich ist erforderlich." }
    }

    val envelope: NormalizedMapArea = NormalizedMapArea(
        left = areas.minOf(NormalizedMapArea::left),
        top = areas.minOf(NormalizedMapArea::top),
        right = areas.maxOf(NormalizedMapArea::right),
        bottom = areas.maxOf(NormalizedMapArea::bottom),
    )

    /** Exact union area of the rectangles; overlaps are counted only once. */
    val areaFraction: Float = rectangleUnionArea(areas)

    companion object {
        val Full = MapAreaSelection(listOf(NormalizedMapArea.Full))
    }
}

private fun rectangleUnionArea(areas: List<NormalizedMapArea>): Float {
    val xCoordinates = areas.flatMap { listOf(it.left, it.right) }.distinct().sorted()
    var result = 0f
    for (index in 0 until xCoordinates.lastIndex) {
        val left = xCoordinates[index]
        val right = xCoordinates[index + 1]
        if (right <= left) continue
        val intervals = areas.asSequence()
            .filter { it.left < right && it.right > left }
            .map { it.top to it.bottom }
            .sortedBy(Pair<Float, Float>::first)
            .toList()
        if (intervals.isEmpty()) continue
        var covered = 0f
        var start = intervals.first().first
        var end = intervals.first().second
        intervals.drop(1).forEach { (nextStart, nextEnd) ->
            if (nextStart > end) {
                covered += end - start
                start = nextStart
                end = nextEnd
            } else {
                end = maxOf(end, nextEnd)
            }
        }
        covered += end - start
        result += (right - left) * covered
    }
    return result.coerceIn(0f, 1f)
}

fun GeoBounds.subsets(selection: MapAreaSelection): List<GeoBounds> = selection.areas.map(::subset)
