package org.nstart.dep265.requestservice.service

import org.locationtech.jts.geom.Geometry
import org.locationtech.jts.geom.GeometryCollection
import org.locationtech.jts.geom.MultiPolygon
import org.locationtech.jts.geom.Polygon
import org.locationtech.jts.io.WKTReader
import org.locationtech.jts.io.WKTWriter
import org.springframework.stereotype.Service
import java.io.StringReader
import kotlin.math.max
import kotlin.math.min

@Service
class GeometryService {
    private val wktReader = WKTReader()

    private val wktWriter = WKTWriter()

    fun parseGeometry(wkt: String): Geometry {
        val geometry = wktReader.read(StringReader(wkt))
        return geometry
    }

    fun parsePolygonalGeometry(wkt: String): Geometry {
        val geometry = parseGeometry(wkt)
        return keepPolygonalGeometry(geometry)
    }

    fun toWkt(geometry: Geometry): String {
        val wkt = wktWriter.write(geometry)
        return wkt
    }

    fun area(geometry: Geometry): Double {
        val area = geometry.area
        return area
    }

    fun cutCoverage(remainingGeometryWkt: String, routeGeometryWkt: String): GeometryCutResult {
        val remainingGeometry = parsePolygonalGeometry(remainingGeometryWkt)
        val routeGeometry = parsePolygonalGeometry(routeGeometryWkt)
        val intersection = keepPolygonalGeometry(remainingGeometry.intersection(routeGeometry))
        val coveredArea = intersection.area

        if (coveredArea <= 0.0) {
            val result = GeometryCutResult(
                remainingGeometryWkt = toWkt(remainingGeometry),
                remainingArea = remainingGeometry.area,
                coveredArea = 0.0,
                hasCoverage = false,
            )
            return result
        }

        val updatedRemainingGeometry = keepPolygonalGeometry(remainingGeometry.difference(routeGeometry))
        val result = GeometryCutResult(
            remainingGeometryWkt = toWkt(updatedRemainingGeometry),
            remainingArea = max(updatedRemainingGeometry.area, 0.0),
            coveredArea = coveredArea,
            hasCoverage = true,
        )
        return result
    }

    fun coveredPercent(originalArea: Double, remainingArea: Double): Double {
        if (originalArea <= 0.0) {
            return 0.0
        }

        val coveredArea = max(originalArea - remainingArea, 0.0)
        val coveredPercent = (coveredArea / originalArea) * 100.0
        return min(max(coveredPercent, 0.0), 100.0)
    }

    fun keepPolygonalGeometry(geometry: Geometry): Geometry {
        val polygonalGeometry = when (geometry) {
            is Polygon -> geometry
            is MultiPolygon -> geometry
            is GeometryCollection -> {
                val polygons = mutableListOf<Polygon>()
                collectPolygons(geometry, polygons)
                when {
                    polygons.isEmpty() -> geometry.factory.createPolygon()
                    polygons.size == 1 -> polygons.first()
                    else -> geometry.factory.createMultiPolygon(polygons.toTypedArray())
                }
            }
            else -> geometry.factory.createPolygon()
        }
        return polygonalGeometry
    }

    private fun collectPolygons(geometry: Geometry, polygons: MutableList<Polygon>) {
        when (geometry) {
            is Polygon -> if (!geometry.isEmpty) {
                polygons.add(geometry)
            }
            is MultiPolygon,
            is GeometryCollection -> {
                for (index in 0 until geometry.numGeometries) {
                    val nestedGeometry = geometry.getGeometryN(index)
                    collectPolygons(nestedGeometry, polygons)
                }
            }
        }
    }
}

data class GeometryCutResult(
    val remainingGeometryWkt: String,

    val remainingArea: Double,

    val coveredArea: Double,

    val hasCoverage: Boolean,
)
