package space.nstart.pcp.slots_service.service

import org.locationtech.jts.geom.Polygon
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.web.bind.annotation.RequestParam
import space.nstart.pcp.pcp_types_lib.dto.ballistics.RevolutionSign
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.SlotStatus
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.SlotDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteInfoDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.mission.SurveyModeResponseDTO
import space.nstart.pcp.pcp_types_lib.utils.wkt.WKTParser
import space.nstart.pcp.slots_service.configuration.CustomErrorException
import space.nstart.pcp.slots_service.czml.*
import java.time.Duration
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
import java.time.ZoneOffset
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.asin
import kotlin.math.sin

@Service
class CZMLService {

    private data class SatelliteCzmlStyle(
        val outlineColor: CZMLColor,
        val material: CZMLMaterial
    )

    val logger : Logger = LoggerFactory.getLogger(this.javaClass)

    @Autowired
    private lateinit var earthService: EarthService
    @Autowired
    private lateinit var complexMissionService: ComplexMissionService
    @Autowired
    private lateinit var ballisticsService: BallisticsService
    @Autowired
    private lateinit var slotService: SlotService

    private val docNameHeatMap = "heatMap"
    private val docNameReq = "req_"
    private val entNameReq = "request_"
    private val entityNip = "cell_"
    private val docPlan = "plan_"
    private val entityMar = "survey_"
    private val docStationName = "station_"
    private val entityStationName = "station_"
private val docReqCovName ="req_cov_"
    private val DEFAULT_STATION_ORBIT_HEIGHT_KM = 500.0

    @Autowired
    private lateinit var stationService: StationService

    private fun ellipseAx(hOrb: Double, gamma: Double): Double {
        val rz = 6378100.0
        val gam = asin(rz / (rz + hOrb) * sin(PI / 2.0 + gamma))
        val a = PI - gam - PI / 2 - gamma
        return rz * a
    }


    fun showSatPlan(id : Long , tn : LocalDateTime,
        tk : LocalDateTime,
        runId: Long? = null) : Iterable<CZMLEntity>{
        val docId = docPlan + id
        val wktParser = WKTParser()
        val r: MutableList<CZMLEntity> = mutableListOf(CZMLEntity("document", docId , "1.1"))

        var ind = 0
        val sat = complexMissionService.satellite(id).block()
        val mars = complexMissionService.plan(id, tn, tk, runId).collectList().block()

        if (sat != null) {

            println("${sat.red}  ${sat.green}  ${sat.blue}")

            val ents = mars?.mapNotNull { mar ->

                val poly = wktParser.parseWKT(mar.contourWkt?:"")
                if (poly is Polygon) {

                    val pos = mutableListOf<Double>()
                    poly.coordinates.forEach { coordinate ->
                        pos.add(coordinate.x)
                        pos.add(coordinate.y)
                        pos.add(0.0)
                    }


                    CZMLEntity(
                        entityMar + "${id}_${++ind}", "Маршрут КА $id №$ind", null,
                        description = "Время начала : ${mar.startTime}<br>" +
                                "Время конца : ${mar.startTime.plusSeconds(mar.duration.toLong())}<br>" +
                                "Виток : ${mar.revolution}<br>" +
                                "Крен : ${mar.roll}<br>" +
                                "Широта : ${mar.lat}"+
                                "Долгота : ${mar.longitude}"+
                                "Слотов : ${mar.bookedSlotIds.size}"
                        ,
                        polygon = CZMLPolygon(
                            positions = CZMLPosition(
                                cartographicDegrees = pos
                            ),
                            outlineWidth = 2.0,
                            fill = true,
                            outlineColor = CZMLColor(sat.red.toInt(), sat.green.toInt(), sat.blue.toInt(), 255),
                            material = CZMLMaterial(sat.red.toInt(), sat.green.toInt(), sat.blue.toInt(), 150)
                        )
                    )
                } else {
                    logger.warn("Контур маршрута не ПОЛИГОН! ${mar.contourWkt}")
                    null
                }
            }
            if (ents!=null)
                r.addAll(ents)


//            val slotEnts = mutableListOf<CZMLEntity>()
//
//            slotSurvs?.forEach { mar ->
//
//                if (mar is SurveyModeResponseDTO) {
//
//                    val poly = wktParser.parseWKT(mar.contourWkt ?: "") as Polygon
//                    val pos = mutableListOf<Double>()
//                    poly.coordinates.forEach { coordinate ->
//                        pos.add(coordinate.x)
//                        pos.add(coordinate.y)
//                        pos.add(0.0)
//                    }
//
//
//                    slotEnts.add(
//                        CZMLEntity(
//                            entityMar + "${id}_${++ind}", "Маршрут бронирования КА $id №$ind", null,
//                            description = "Время начала : ${mar.timeStart}<br>" +
//                                    "Время конца : ${mar.timeStart?.plusSeconds(mar.duration.toLong())}<br>" +
//                                    "Виток : ${mar.revolution}<br>" +
//                                    "Крен : ${mar.roll}<br>" +
//                                    "Слотов : ${mar.bookedSlotIds.size}",
//                            polygon = CZMLPolygon(
//                                positions = CZMLPosition(
//                                    cartographicDegrees = pos
//                                ),
//                                outlineWidth = 2.0,
//                                fill = true,
//                                outlineColor = CZMLColor(sat.red.toInt(), sat.green.toInt(), sat.blue.toInt(), 255),
//                                material = CZMLMaterial(sat.red.toInt(), sat.green.toInt(), sat.blue.toInt(), 150)
//
//                            )
//
//                        )
//                    )
//                }
//            }
//            r.addAll(slotEnts)
        }
        return r
    }



    fun showWithCells(idReq : String) : Iterable<CZMLEntity>{
        val id = idReq//"b9bad368-be24-410f-afa6-c140bacdbd8f"
        val req = earthService.reqcells(id)?:return listOf()
        val wktParser = WKTParser()
        val r: MutableList<CZMLEntity> = mutableListOf(CZMLEntity("document", docNameReq+id , "1.1"))
//        r.addAll(req.cells.map { cell ->
//
//            val poly = wktParser.parseWKT(cell.contour) as Polygon
//
//
//            val pos = mutableListOf<Double>()
//            poly.coordinates.forEach { coordinate ->
//                pos.add(coordinate.x)
//                pos.add(coordinate.y)
//                pos.add(0.0)
//            }
//
//            val alfa = ((cell.importance / 1.2) * 200).toInt()
//            println( alfa)
//            CZMLEntity(
//                entityNip + "${cell.id}", cell.id.toString(), null,
//
//                polygon = CZMLPolygon(
//                    positions = CZMLPosition(
//                        cartographicDegrees =  pos
//                    ),
//                    outlineWidth = 2.0,
//                    fill = true,
//                    outlineColor = CZMLColor(255,0,0,255),
//                    material = CZMLMaterial(255,0,0,alfa)
//
//                )
//
//            )
//        }
//        )
        val poly = wktParser.parseWKT(req.request.contour) as Polygon


        val pos = mutableListOf<Double>()
        poly.coordinates.forEach { coordinate ->
            pos.add(coordinate.x)
            pos.add(coordinate.y)
            pos.add(0.0)
        }

        r.add(
            CZMLEntity(
            entNameReq + "${req.request.requestId}", req.request.name, null,

            polygon = CZMLPolygon(
                positions = CZMLPosition(
                    cartographicDegrees =  pos
                ),
                outlineWidth = 2.0,
                fill = true,
                outlineColor = CZMLColor(191, 0, 255,255),
                material = CZMLMaterial(191, 0, 255,150)

            ),
                description = "важность : ${req.request.importance} <br> id : ${req.request.requestId}"


        ))

        return r

    }



    fun drawHeatMap(importance: Double, countLat: Int? = null, countLong: Int? = null): Iterable<CZMLEntity> {

        val cells = earthService.cells(0.0, countLat, countLong).toList()
        val wktParser = WKTParser()
        val r: MutableList<CZMLEntity> = mutableListOf(CZMLEntity("document", docNameHeatMap, "1.1"))


        if (!cells.any()) {
            return r
        }

        val importanceMax = cells.maxOf { it.importance }
        if (importanceMax <= 0.0) {
            return r
        }

        r.addAll(cells.filter { it.importance / importanceMax >= importance }.map { cell ->

            val poly = wktParser.parseWKT(cell.contour) as Polygon


            val pos = mutableListOf<Double>()
            poly.coordinates.forEach { coordinate ->
                pos.add(coordinate.x)
                pos.add(coordinate.y)
                pos.add(0.0)
            }

            val importance = cell.importance / importanceMax
            val alfa = 180

            CZMLEntity(
                entityNip + "${cell.id}", cell.id.toString(), null,

                description = "важность : $importance",
                polygon = CZMLPolygon(
                    positions = CZMLPosition(
                        cartographicDegrees = pos
                    ),
                    outlineWidth = 2.0,
                    fill = true,
                    outlineColor = CZMLColor(255, 0, 0, 255),
                    material = CZMLMaterial(255, (255 * (1 - importance)).toInt(), 0, alfa)

                )

            )
        }
        )

        return r
    }

    class CovsDTO(
        val cov : List<CZMLEntity> = listOf(),
        val slots : List<SlotDTO> = listOf()
    )

    private fun SatelliteInfoDTO.toSlotStyle() = SatelliteCzmlStyle(
        outlineColor = CZMLColor(red.toInt(), green.toInt(), blue.toInt(), 255),
        material = CZMLMaterial(red.toInt(), green.toInt(), blue.toInt(), 150)
    )

    private fun defaultSlotMaterial(slotStatus: SlotStatus) =
        when (slotStatus) {
            SlotStatus.BOOKED -> CZMLMaterial(0, (255 * (1 - 0.5)).toInt(), 0, 200)
            SlotStatus.BUSY -> CZMLMaterial(125, 125, 125, 200)
            else -> CZMLMaterial(255, (255 * (1 - 0.5)).toInt(), 0, 150)
        }

    fun covs(
        id : String,
        tn : LocalDateTime,
        tk : LocalDateTime,
        sign : RevolutionSign?,
        cover : Boolean?,
        sun : Double?,
        satellites : List<Long>?
    ): CovsDTO {

        println("$id $tn $tk $sign $cover $sun $satellites")
//            val req = earthService.reqcells(id)?:return listOf()
        val r: MutableList<CZMLEntity> = mutableListOf(CZMLEntity("document", docReqCovName + id, "1.1"))
        val wktParser = WKTParser()
        try {
            val slots = slotService.slots(id, tn, tk, sign, cover, satellites, sun).collectList().block()?.toList() ?: return CovsDTO()
            val satelliteStyles = slots
                .map { it.satelliteId }
                .distinct()
                .associateWith { satelliteId ->
                    complexMissionService.satellite(satelliteId).block()?.toSlotStyle()
                }

            r.addAll(slots.map { slot ->
                val poly = wktParser.parseWKT(slot.contour) as Polygon
                val satelliteStyle = satelliteStyles[slot.satelliteId]


                val pos = mutableListOf<Double>()
                poly.coordinates.forEach { coordinate ->
                    pos.add(coordinate.x)
                    pos.add(coordinate.y)
                    pos.add(0.0)
                }
                CZMLEntity(
                    "slot_${slot.satelliteId}_${slot.cycle}_${slot.slotNumber}",
                    "Слот",
                    "1.1",
                    description = "КА №${slot.satelliteId}<br>" +
                            "Виток ${slot.revolution} ${slot.revolutionSign}<br>" +
                            "ДМВ ${slot.tn}<br>" +
                            "Длительность ${abs(Duration.between(slot.tn, slot.tk).seconds)}<br>" +
                            "Крен ${slot.roll}<br>" +
                            "№слота ${slot.slotNumber}<br>" +
                            "Цикл ${slot.cycle}",

                    polygon = CZMLPolygon(
                        positions = CZMLPosition(
                            cartographicDegrees = pos
                        ),
                        outlineWidth = 2.0,
                        fill = true,
                        outlineColor = satelliteStyle?.outlineColor ?: CZMLColor(255, 0, 0, 255),
                        material = satelliteStyle?.material ?: defaultSlotMaterial(slot.state)
                    ),
                    polyline = CZMLPolyLine(
                        width = 2.0,
                        material = satelliteStyle?.material ?: CZMLMaterial(255, 0, 0, 255),
                        positions = CZMLPosition(
                            cartographicDegrees = if (pos.size >= 3) pos + pos.take(3) else pos
                        )
                    )
                )
            })
            return CovsDTO(r, slots)
        }
        catch (ex : CustomErrorException){
            logger.warn(ex.message)
            throw ex
        }
    }


    fun station(id : String, view : Boolean, orbitHeightKm: Double? = null) : Iterable<CZMLEntity>{
        val req = stationService.station(id).block()?:return listOf()
        val normalizedOrbitHeightKm = orbitHeightKm?.takeIf { it > 0 } ?: DEFAULT_STATION_ORBIT_HEIGHT_KM
        val radius = ellipseAx((normalizedOrbitHeightKm * 1000.0), req.elevationMin * PI / 180)

        val r: MutableList<CZMLEntity> = mutableListOf(CZMLEntity("document", docStationName+id , "1.1"))
        r.add(
            CZMLEntity(
                entityStationName + "${req.id}", req.name, null,
                billboard = CZMLBillboard(
                    "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAScwAAEnMBjCK5BwAAABl0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC4xOdTWsmQAAAFuSURBVEhLlZRRlsUgCEO79C6tO3O4SBSp7bzmnDSCkDw/Zo7W2mEw8UM7z3Mhvbj32a8Eph2cMb2u66YfQgSvlwAZvYXEvMzEgeTnd6CXAWoMn0LQ2HED7YgB3VlrNsevfDPPIaxqvdfDy80h8E4GPUzEXQjUblWDm0PgHcASoPeFfed0NQxzCGjeQD+/or5AM5MesJjD/nGZL8gGb+ZSelG7X2b/xDCQAaCvAFF9IGP0KaR/0kJV7na/nJp+vlfNmDgPsSjUECDFSKb0ZJ5m3RPOQwyCne7MUfq5TrNrgBdhklFDFJDNsnkN2QYw8KQ1RDV3Us0ZakB8DNm0KjMyRuk9hSwBGOD/SwjIQfSq1oCxwF8lCrKpmGvtYCrSHyE2O8ylnfuQJ51780Xet/sRoH9YGtQZZLP/qH2n7aSAlb3fXwJZBm9aWQLuLzBoxloz6FcuATs1aGaQO3FnCsdMLHmxU4NmtnSTDft9O/4AurwQU2nT95gAAAAASUVORK5CYII=",
                    1.0,
                    show = view
                ),
                position = CZMLPosition(
                    cartographicDegrees = listOf(req.position.long, req.position.lat, req.position.height)
                ),
                ellipse = CZMLEllipse(
                    semiMajorAxis = radius,
                    semiMinorAxis = radius,
                    outlineColor = CZMLColor(250, 0, 0, 250),
                    extrudedHeight = 1000.0,
                    height = 1000.0
                ),
                description = "Станция ${req.name}<br>Высота орбиты для зоны видимости: ${normalizedOrbitHeightKm} км",
                model = CZMLModel(
                    gltf = "/model/station.glb",
                    show = !view
                )
            ))
        return r
    }



    fun fromDateTime(t: LocalDateTime) = t.toEpochSecond(ZoneOffset.UTC) + t.nano / 1e9

    fun satFLD(
        id : Long,
        view : Boolean,
        showOrbit : Boolean,
        showSw : Boolean,
        showFl : Boolean,
        timeStart: LocalDateTime? = null,
        timeStop: LocalDateTime? = null
    ): Iterable<CZMLEntity> {

        val tstart = timeStart ?: UtcDateTimes.now()
        val tstop = timeStop ?: tstart.plusDays(1)
        var vuz = ballisticsService.ascNodes(id, tstart, tstop).collectList().block()
        vuz = vuz?.filter { it.time >= tstart && it.time <= tstop }?.sortedBy { it.time }
        val fl = ballisticsService.flightLine(id, tstart, tstop).collectList().block()
            ?.filter { it.time >= tstart && it.time <= tstop }
            ?.sortedBy { it.time }
        val orbP = ballisticsService.points(id, tstart, tstop).collectList().block()
            ?.filter { it.time >= tstart && it.time <= tstop }
            ?.sortedBy { it.time }
        val tn = fromDateTime(tstart)

        val sat = complexMissionService.satellite(id).block()
        val doc = mutableListOf<CZMLEntity>()

        if (sat != null && fl != null && vuz != null && orbP != null) {


            val flr = mutableListOf<Double>()
            val fll = mutableListOf<Double>()
            val sw = mutableListOf<Double>()
            val orb = mutableListOf<Double>()
            for (op in fl) {
                fll.addAll(listOf(fromDateTime(op.time) - tn, op.longLeft, op.latLeft, 50.0))
                flr.addAll(listOf(fromDateTime(op.time) - tn, op.longRight, op.latRight, 50.0))
                sw.addAll(listOf(fromDateTime(op.time) - tn, op.long, op.lat, 50.0))
            }
            for (op in orbP)
                orb.addAll(listOf(fromDateTime(op.time) - tn, op.x, op.y, op.z))


            val path = CZMLPath(
                vuz,
                tstart,
                tstop,
                1,
                120,
                CZMLMaterial(
                    sat.red.toInt(),
                    sat.green.toInt(),
                    sat.blue.toInt(),
                    250
                )
            )


            val sats = listOf(
//                "sat",
                "radarSat",
                "CloudSat")
            doc.addAll(
                mutableListOf(
                    CZMLEntity(
                        id = "document", name = "sat_$id", version = "1.1",
                        clock = CZMLClock(tstart, tstop, tstart)
                    ),
                    CZMLEntity(
                        id = "swath_r_ka_$id", name = sat.name,

                        position = CZMLPosition(
                            cartographicDegrees = flr,
                            interpolationAlgorithm = "LAGRANGE",
                            interpolationDegree = 1.0,
                            epoch = UtcDateTimes.format(tstart)
                        ),
                        path = path.apply { show = showSw }

                    ),
                    CZMLEntity(
                        id = "swath_l_ka_$id", name = sat.name,
                        position = CZMLPosition(
                            cartographicDegrees = fll,
                            interpolationAlgorithm = "LAGRANGE",
                            interpolationDegree = 1.0,
                            epoch = UtcDateTimes.format(tstart)
                        ),
                        path = path.apply { show = showSw }
                    ),

                    CZMLEntity(
                        id = "flight_line_ka_$id", name = sat.name,
                        point = CZMLPoint(
                            pixelSize = 5.0, color = CZMLColor(
                                sat.red.toInt(),
                                sat.green.toInt(),
                                sat.blue.toInt(),
                                250
                            ),
                            show = showFl

                        ),
                        position = CZMLPosition(
                            cartographicDegrees = sw,
                            interpolationAlgorithm = "LAGRANGE",
                            interpolationDegree = 1.0,
                            epoch = UtcDateTimes.format(tstart)
                        ),
                        path = path.apply { show = showFl }
                    ),

                    CZMLEntity(
                        id = "orbit_$id", name = sat.name,
                        point = CZMLPoint(
                            pixelSize = 8.0,
                            color = CZMLColor(
                                sat.red.toInt(),
                                sat.green.toInt(),
                                sat.blue.toInt(),
                                250
                            ),
                            show = view
                        ),
                        model = CZMLModel(
                            gltf = "/model/${sats.shuffled().first()}.glb",
                            show = !view
                        ),
//                        billboard = CZMLBillboard(
//                            "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAScwAAEnMBjCK5BwAAABl0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC4xOdTWsmQAAAFuSURBVEhLlZRRlsUgCEO79C6tO3O4SBSp7bzmnDSCkDw/Zo7W2mEw8UM7z3Mhvbj32a8Eph2cMb2u66YfQgSvlwAZvYXEvMzEgeTnd6CXAWoMn0LQ2HED7YgB3VlrNsevfDPPIaxqvdfDy80h8E4GPUzEXQjUblWDm0PgHcASoPeFfed0NQxzCGjeQD+/or5AM5MesJjD/nGZL8gGb+ZSelG7X2b/xDCQAaCvAFF9IGP0KaR/0kJV7na/nJp+vlfNmDgPsSjUECDFSKb0ZJ5m3RPOQwyCne7MUfq5TrNrgBdhklFDFJDNsnkN2QYw8KQ1RDV3Us0ZakB8DNm0KjMyRuk9hSwBGOD/SwjIQfSq1oCxwF8lCrKpmGvtYCrSHyE2O8ylnfuQJ51780Xet/sRoH9YGtQZZLP/qH2n7aSAlb3fXwJZBm9aWQLuLzBoxloz6FcuATs1aGaQO3FnCsdMLHmxU4NmtnSTDft9O/4AurwQU2nT95gAAAAASUVORK5CYII=",
//                            1.0
//                        ),
                        position = CZMLPosition(
                            cartesian = orb,
                            interpolationAlgorithm = "LAGRANGE",
                            interpolationDegree = 1.0,
                            epoch = UtcDateTimes.format(tstart)
                        ),
                        path = path.apply { show = showOrbit }
                    )


                )
            )

        }
        return doc

    }

}
