package space.nstart.pcp.angularmotion

import ballistics.flightLine.PointOnEarthCalculator
import ballistics.types.EarthType
import ballistics.types.Orientation
import ballistics.types.THBLPoint
import ballistics.types.WorkCSType
import java.util.Locale
import kotlin.math.PI

/**
 * Строит WKT-контур полосы съемки по результату ПУУД.
 *
 * [captureAngleDeg] трактуется как полуширина полосы. Правая/левая граница строятся
 * прямым пересечением визирного луча с Землей через [PointOnEarthCalculator].
 */
class SurveyContourCalculator(
    earthType: EarthType = EarthType.PZ90d02,
    wcs: WorkCSType = WorkCSType.WCSOrbit,
) {
    private val pointOnEarthCalculator = PointOnEarthCalculator(earthType, wcs)

    fun calculate(result: AngularMotionResult, y: Double,z: Double,focus: Double): String {


        val sourcePoints = decimateContourSourcePoints(result.points)
        require(sourcePoints.size >= MIN_CONTOUR_POINTS) {
            "Для построения контура съемки требуется не менее $MIN_CONTOUR_POINTS точек ПУУД"
        }

        val right = ArrayList<SurveyContourPoint>(sourcePoints.size)
        val left = ArrayList<SurveyContourPoint>(sourcePoints.size)

        sourcePoints.forEach { point ->
            val rightPoint = pointOnEarthCalculator.pointOnEarth(
                point.orbitalPoint,
                point.orientation,
                z, y, focus

            )
            val leftPoint = pointOnEarthCalculator.pointOnEarth(
                point.orbitalPoint,
                point.orientation,
                -z, y, focus
            )

            if (rightPoint != null && leftPoint != null) {
                right += rightPoint.toContourPoint()
                left += leftPoint.toContourPoint()
            }
        }
        require(right.size >= MIN_CONTOUR_POINTS && left.size >= MIN_CONTOUR_POINTS) {
            "Для построения контура съемки не удалось рассчитать границы полосы"
        }

        val shell = ArrayList<SurveyContourPoint>(right.size + left.size + 1)
        shell += right
        for (index in left.indices.reversed()) {
            shell += left[index]
        }
        shell += shell.first()
        return toWkt(shell)
    }


    private fun decimateContourSourcePoints(points: List<AngularMotionPoint>): List<AngularMotionPoint> {
        if (points.size <= MIN_CONTOUR_POINTS) {
            return points
        }

        val startTime = points.first().t
        val stopTime = points.last().t
        val duration = stopTime - startTime
        if (duration <= 0.0) {
            return listOf(points.first(), points.last()).distinct()
        }

        val step = maxOf(
            DEFAULT_CONTOUR_SOURCE_STEP_SEC,
            duration / (MAX_CONTOUR_BOUNDARY_POINTS - 1),
        )

        val selected = ArrayList<AngularMotionPoint>(MAX_CONTOUR_BOUNDARY_POINTS)
        selected += points.first()
        var nextTime = startTime + step

        for (index in 1 until points.lastIndex) {
            val point = points[index]
            if (point.t + TIME_EPS >= nextTime) {
                selected += point
                nextTime += step
            }
        }

        if (selected.last() !== points.last()) {
            selected += points.last()
        }

        return selected
    }

    private fun toWkt(points: List<SurveyContourPoint>): String {
        val coordinates = points.joinToString(", ") { point ->
            String.format(Locale.US, "%.8f %.8f", point.longitudeDeg, point.latitudeDeg)
        }
        return "POLYGON (($coordinates))"
    }

    private fun Orientation.withKren(kren: Double): Orientation = Orientation(tang, kren, risk)

    private fun THBLPoint.toContourPoint(): SurveyContourPoint =
        SurveyContourPoint(latitudeDeg = lat.toDegrees(), longitudeDeg = long.toDegrees())

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

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

    private data class SurveyContourPoint(
        val latitudeDeg: Double,
        val longitudeDeg: Double,
    )

    private companion object {
        const val MIN_CONTOUR_POINTS = 2
        const val DEFAULT_CONTOUR_SOURCE_STEP_SEC = 2.0
        const val MAX_CONTOUR_BOUNDARY_POINTS = 10
        const val TIME_EPS = 1.0e-9
    }
}
