package space.nstart.pcp.complan.spacecraft

import org.locationtech.jts.geom.Coordinate
import org.locationtech.jts.geom.GeometryFactory
import org.locationtech.jts.geom.LineString
import space.nstart.pcp.pcp_types_lib.dto.ballistics.FlightLineDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.RevolutionSign
import java.time.LocalDateTime
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes

/**
 * Класс космического аппарата**
 */
class SpaceCraft (
    val id : Long,
    val constraints: SCConstraints = SCConstraints()
) {
    val flightLine : MutableList<FlightLineDTO> = mutableListOf()

    /**Время начала планирования аппарата*/
    var timeStart: LocalDateTime = UtcDateTimes.now()
    /**Время конца планирования аппарата*/
    var timeStop : LocalDateTime = UtcDateTimes.now()
/**
 * Создание линий полета восходящего и нисходящего витка
 * @param geomFactory фабрика геометрии
 * @param fLC координаты линии
 * @return
 * @return Возвращает геометрию линии полученной из массива координат
 */
    fun createLineString(
        geomFactory: GeometryFactory,
        fLC: MutableList<Coordinate>,
    ): LineString {
        require(fLC.size >= 2) { "Для построения трассы требуется минимум две точки" }

        val coordinates = fLC.toTypedArray()
        val minY = coordinates.minOf { it.y }
        val maxY = coordinates.maxOf { it.y }

        val startIndex1 = coordinates.indexOfFirst { it.y == minY }
        val endIndex1 = coordinates.indexOfFirst { it.y == maxY }

        val array = if (startIndex1 < endIndex1)
            coordinates.sliceArray(startIndex1..endIndex1).toMutableList()
        else
            coordinates.sliceArray(endIndex1..startIndex1).toMutableList()

        val geom = geomFactory.createLineString(array.toTypedArray())

        return geom
    }

    /**
     * Получение линии полета в формате LineString
     * @return Возвращает часть линии полета от минимального занчения координаты Y до максимального. Параметры: **первый - НИСХОДЯЩИЙ, второй - ВОСХОДЯЩИЙ**
     * TODO Переписать структуру хранения FlightLine, неудобно держать в памяти первый или второй, нужны признаки
     */
    fun createFlightLine(): Pair<LineString, LineString> {
        val geomFactory = GeometryFactory()
        val passes = getFlightLinePasses()
        val fLC1 = lineCorrection(selectPass(passes, RevolutionSign.DESC).coordinates).toMutableList()
        val fLC2 = lineCorrection(selectPass(passes, RevolutionSign.ASC).coordinates).toMutableList()

        return Pair(createLineString(geomFactory, fLC1), createLineString(geomFactory, fLC2))
    }

    private fun getFlightLinePasses(): List<FlightLinePass> {
        if (flightLine.isEmpty()) {
            return emptyList()
        }

        val passes = mutableListOf<FlightLinePass>()
        var currentSign = flightLine.first().revSign
        var currentCoordinates = mutableListOf<Coordinate>()

        flightLine.forEach { point ->
            if (point.revSign != currentSign) {
                addPass(passes, currentSign, currentCoordinates)
                currentSign = point.revSign
                currentCoordinates = mutableListOf()
            }
            currentCoordinates.add(Coordinate(point.long, point.lat))
        }
        addPass(passes, currentSign, currentCoordinates)

        return passes
    }

    private fun addPass(
        passes: MutableList<FlightLinePass>,
        revSign: RevolutionSign,
        coordinates: List<Coordinate>
    ) {
        if (coordinates.size >= 2) {
            passes.add(FlightLinePass(revSign, coordinates.map { it.copy() }))
        }
    }

    private fun selectPass(passes: List<FlightLinePass>, revSign: RevolutionSign): FlightLinePass {
        val sameDirectionPasses = passes.filter { it.revSign == revSign }
        return sameDirectionPasses.firstOrNull { it.isFullPass() }
            ?: sameDirectionPasses.maxByOrNull { it.latitudeRange }
            ?: throw IllegalStateException("Не найдена трасса полета для направления $revSign")
    }

    fun lineCorrection(tmpLine : List<Coordinate>) : List<Coordinate> {
        if (tmpLine.isEmpty()) {
            return emptyList()
        }

        val line = mutableListOf<Coordinate>()

        line.add(tmpLine.first().copy())
        for (i in 1..<tmpLine.count()) {
            val coord = tmpLine[i].copy()
            while (coord.x - line.last().x > HALF_WORLD_DEGREES) {
                coord.x -= FULL_WORLD_DEGREES
            }
            while (coord.x - line.last().x < -HALF_WORLD_DEGREES) {
                coord.x += FULL_WORLD_DEGREES
            }
            line.add(coord.copy())
        }
        return line
    }

    private data class FlightLinePass(
        val revSign: RevolutionSign,
        val coordinates: List<Coordinate>
    ) {
        val minLat = coordinates.minOf { it.y }
        val maxLat = coordinates.maxOf { it.y }
        val latitudeRange = maxLat - minLat

        fun isFullPass(): Boolean = minLat <= FULL_PASS_MIN_LAT && maxLat >= FULL_PASS_MAX_LAT
    }

    private companion object {
        const val FULL_PASS_MIN_LAT = -80.0
        const val FULL_PASS_MAX_LAT = 80.0
        const val HALF_WORLD_DEGREES = 180.0
        const val FULL_WORLD_DEGREES = 360.0
    }
}
