package space.nstart.pcp.complan.types

import com.fasterxml.jackson.databind.ObjectMapper
import org.locationtech.jts.geom.MultiPolygon
import org.locationtech.jts.geom.LineString
import org.locationtech.jts.geom.Geometry
import org.locationtech.jts.geom.LinearRing
import org.locationtech.jts.geom.Polygon
import org.locationtech.jts.geom.Point
import java.io.File
import kotlin.collections.forEach
import kotlin.math.PI


class GeoJson {

    data class GeometryGeoJson(val type: String, val coordinates: Any)
    data class Feature(val name: String, val bbox: List<Double>? = null, val type: String, val geometry: GeometryGeoJson, val properties: Any)
    data class FeatureCollection(val type: String, val features: List<Feature>)

    private fun multipolygonToGeojsonCoord(multipolygon: MultiPolygon, radToDeg : Boolean = false): List<List<List<Array<Double>>>> {
        return (0 until multipolygon.numGeometries).map{i->
            val polygon = multipolygon.getGeometryN(i) as Polygon
            polygonToGeojsonCoord(polygon, radToDeg)
        }
    }

    private fun polygonToGeojsonCoord(polygon: Polygon, radToDeg : Boolean = false): List<List<Array<Double>>> {
        val coordinates = mutableListOf<List<Array<Double>>>()

        coordinates.add(linearRingToCoord(polygon.exteriorRing, radToDeg))

        (0 until polygon.numInteriorRing).forEach { i ->
            coordinates.add(linearRingToCoord(polygon.getInteriorRingN(i), radToDeg))
        }

        return coordinates
    }

    private fun linearRingToCoord(ring: LinearRing, radToDeg : Boolean = false): List<Array<Double>> {
        if (radToDeg) {
            return ring.coordinates.map{ coordinate ->
                listOf(coordinate.x * 180 / PI, coordinate.y * 180 / PI).toTypedArray()
            }
        } else {
            return ring.coordinates.map{ coordinate ->
                listOf(coordinate.x, coordinate.y).toTypedArray()
            }
        }

    }

    fun getBBox(geometry: Geometry): List<Double>{
        val bbox = geometry.envelopeInternal
        return listOf(
            bbox.minX,
            bbox.maxY,
            bbox.maxX,
            bbox.minY,
        )
    }

    /**
     * Конвертация из Geometry в формат geoJSON
     * @param geometry исходная геометрия
     * @return Возвращает конвертированную геометрию для записи в формате geoGSON
     */
    fun convertToGeoJson(geometry: Geometry): GeometryGeoJson {
        return when (geometry) {
            is Point -> GeometryGeoJson(type = "Point", coordinates = listOf( geometry.x, geometry.y ))
            is LineString -> GeometryGeoJson(type = "LineString", coordinates = geometry.coordinates.map{arrayOf( it.x, it.y )})
            is Polygon -> GeometryGeoJson(type = "Polygon", coordinates = polygonToGeojsonCoord(geometry))
            is MultiPolygon -> GeometryGeoJson(type = "MultiPolygon", coordinates = multipolygonToGeojsonCoord(geometry))
            else -> throw IllegalArgumentException("Unknown geometry type: $geometry")
        }
    }
    fun convertToGeoJsonRadToDeg(geometry: Geometry): GeometryGeoJson {
        return when (geometry) {
            is Point -> GeometryGeoJson(type = "Point", coordinates = listOf( radToDeg(geometry.x), (geometry.y) ))
            is LineString -> GeometryGeoJson(type = "LineString", coordinates = geometry.coordinates.map{arrayOf( radToDeg(it.x), radToDeg(it.y) )})
            is Polygon -> GeometryGeoJson(type = "Polygon", coordinates = polygonToGeojsonCoord(geometry, radToDeg = true))
            is MultiPolygon -> GeometryGeoJson(type = "MultiPolygon", coordinates = multipolygonToGeojsonCoord(geometry, radToDeg = true))
            else -> throw IllegalArgumentException("Unknown geometry type: $geometry")
        }
    }

    fun radToDeg(value : Double): Double {
        return value * 180 / PI
    }

    companion object {
        fun createFeatureCollection(
            geoms: List<GeoJsonItem>,
            fileName: String,
            radToDeg: Boolean = false
        ): FeatureCollection {
            val gj = GeoJson()
            val features = geoms.map { geom ->
                Feature(
                    name = fileName,
                    bbox = gj.getBBox(geom.geometry),
                    type = "Feature",
                    geometry = if (radToDeg) {
                        gj.convertToGeoJsonRadToDeg(geom.geometry)
                    } else {
                        gj.convertToGeoJson(geom.geometry)
                    },
                    properties = geom.properties
                )
            }
            return FeatureCollection(type = "FeatureCollection", features = features)
        }

        /**
         * Создать geoJSON.
         * Файлы geoJson хранятся в папке ресурсов рядом с картой в папке geoJson.
         * Названия **AprioryScans, Catalog, FactScans, Region** соответственно.
         * @param geom массив элементов [GeoJsonItem], содержащий геометрию и свойства слоя при необходимости
         * @param outputPath целевой путь сохранения файла
         */
        fun createGeoJson(geoms: List<GeoJsonItem>, fileName: String, dirToSave: String, radToDeg: Boolean = false) {
            val objectMapper = ObjectMapper()
            val geoJsonString = objectMapper.writerWithDefaultPrettyPrinter()
                .writeValueAsString(createFeatureCollection(geoms, fileName, radToDeg))

            val dataDir = File(dirToSave)
            if(!dataDir.exists()){
                dataDir.mkdirs()
            }

            val path = "$fileName.json"
            val file = File(dataDir, path)
            file.writeText(geoJsonString)
        }
    }
}
