package space.nstart.pcp.complan.apl

import ballistics.flightLine.PointOnEarthCalculator
import ballistics.types.EarthType
import ballistics.types.Orientation
import ballistics.types.OrbitalPoint
import ballistics.types.THBLPoint
import ballistics.types.WorkCSType
import ballistics.utils.fromDateTime
import ballistics.utils.math.Vector3D
import org.locationtech.jts.geom.Coordinate
import org.locationtech.jts.geom.GeometryFactory
import org.locationtech.jts.io.WKTWriter
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO
import kotlin.math.PI

@Service
class SurveyContourBuilder {

    private val logger = LoggerFactory.getLogger(this::class.java)
    private val geometryFactory = GeometryFactory()
    private val wktWriter = WKTWriter()

    fun build(points: List<OrbPointDTO>, roll: Double, capture: Double): String? {
        val orderedPoints = points.sortedBy { it.time }
        if (orderedPoints.size < 2) {
            logger.warn("Контур маршрута не построен: недостаточно точек орбиты, points={}", orderedPoints.size)
            return null
        }

        return try {
            val pointOnEarthCalculator = PointOnEarthCalculator(EarthType.PZ90d02, WorkCSType.WCSOrbit)
            val rightCoordinates = ArrayList<Coordinate>(orderedPoints.size)
            val leftCoordinates = ArrayList<Coordinate>(orderedPoints.size)

            orderedPoints.map { it.toOrbitalPoint() }.forEach { point ->
                val rightPoint = viewParams(pointOnEarthCalculator, point, roll + capture)
                    ?: THBLPoint(0.0, 0.0, 0.0, 0.0)
                rightCoordinates += Coordinate(rightPoint.long.toDegrees(), rightPoint.lat.toDegrees(), 0.0)

                val leftPoint = viewParams(pointOnEarthCalculator, point, roll - capture)
                    ?: THBLPoint(0.0, 0.0, 0.0, 0.0)
                leftCoordinates += Coordinate(leftPoint.long.toDegrees(), leftPoint.lat.toDegrees(), 0.0)
            }

            val shellCoordinates = ArrayList<Coordinate>(rightCoordinates.size + leftCoordinates.size + 1)
            shellCoordinates.addAll(rightCoordinates)
            for (index in leftCoordinates.indices.reversed()) {
                shellCoordinates += leftCoordinates[index]
            }
            if (shellCoordinates.isNotEmpty()) {
                shellCoordinates += Coordinate(shellCoordinates.first().x, shellCoordinates.first().y, 0.0)
            }

            val distinctVertices = shellCoordinates
                .dropLast(1)
                .map { it.x to it.y }
                .distinct()
            if (shellCoordinates.size < 4 || distinctVertices.size < 3) {
                logger.warn("Контур маршрута не построен: недостаточно уникальных точек полигона")
                return null
            }

            val polygon = geometryFactory.createPolygon(shellCoordinates.toTypedArray())
            wktWriter.write(polygon)
        } catch (error: Exception) {
            logger.warn("Контур маршрута не построен: {}", error.message)
            null
        }
    }

    private fun viewParams(
        pointOnEarthCalculator: PointOnEarthCalculator,
        point: OrbitalPoint,
        gamma: Double
    ): THBLPoint? = pointOnEarthCalculator.pointOnEarth(point, Orientation(0.0, gamma.toRadians(), 0.0))

    private fun OrbPointDTO.toOrbitalPoint() = OrbitalPoint(
        t = fromDateTime(time),
        vit = revolution.toInt(),
        r = Vector3D(x, y, z),
        v = Vector3D(vx, vy, vz)
    )

    private fun Double.toDegrees(): Double = this * 180.0 / PI

    private fun Double.toRadians(): Double = this * PI / 180.0
}
