package ballistics

import ballistics.flightLine.FlightLineCalculator
import ballistics.mpl.MPLCalculator
import ballistics.orbitalPoints.AbstractOrbPointsCalculator
import ballistics.orbitalPoints.OrbitalPointsIntegrator
import ballistics.orbitalPoints.OrbitalPointsTLE
import ballistics.orbitalPoints.timeStepper.AbstractStepper
import ballistics.orbitalPoints.timeStepper.TLEStepper
import ballistics.types.BallisticsError
import ballistics.types.EarthType
import ballistics.types.FleghtLineSector
import ballistics.types.FlightLine
import ballistics.types.InitialConditions
import ballistics.types.IntegrationType
import ballistics.types.KeplerParams
import ballistics.types.ModDVType
import ballistics.types.OPKatObj
import ballistics.types.OrbitalPoint
import ballistics.types.PPI
import ballistics.types.PointViewParams
import ballistics.types.RevolutionParameter
import ballistics.types.TLE
import ballistics.types.TLEParams
import ballistics.types.TangageType
import ballistics.types.WorkCSType
import ballistics.types.ZRV
import ballistics.utils.astro.AstronomerJ2000
import ballistics.utils.toDateTime
import ballistics.zrv.ZRVStepperCalculator
import ballistics.zrv.ZRVStepperCalculatorAsync
import org.nstart.dep265.tletools.zeptomoby.core.Globals.sqr
import reactor.core.publisher.Flux
import kotlin.math.PI
import kotlin.math.asin
import kotlin.math.atan
import kotlin.math.atan2
import kotlin.math.sin
import kotlin.math.sqrt

class Ballistics {
    var kaId: Int = -1

    var kaUmn: Int = -1

    val ic : MutableList<InitialConditions> = mutableListOf()

    /**
     * Модель движения
     */
    var modDVType: ModDVType = ModDVType.FOTO

    /**
     * Метод интегрирования
     */
    var integrationType: IntegrationType = IntegrationType.ADAMS7

    /**
     * Тип модели Земли
     */
    var earthType: EarthType = EarthType.PZ90d02

    /**
     * тип базовой системы координат (для расчета трасс полета, полособзора и матрицы планирования)
     */
    var workCoordinateSystem: WorkCSType = WorkCSType.WCSOrbit

    /**
     * Минимальный угол крена (для расчета полосы обзора)
     */
    var rollMin: Double = 0.0

    /**
     * Максимальный угол крена (для расчета полосы обзора и покрытия земной поверхности)
     */
    var rollMax: Double = PI / 6.0


    var rollByModule : Boolean = true

    /**
     * Граничный угол Солнца для КА (для определения попадания момента наблюдения объекта в матрицу планирования)
     */
    var sunAngleMin: Double = 0.0

    /**
     * Признак использования ограничений на попадание в матрицу планирования из параметров объекта
     */
    var useObjConstraints: Boolean = false

    /**
     * Объект класса расчета точек орбиты
     */
    private var orbitCalculator: AbstractOrbPointsCalculator? = null

    /**
     * Объект класса расчета параметров ЗРВ
     */
    private var zrvCalculator: ZRVStepperCalculator? = null

    /**
     * Объект класса расчета параметров трассы полета и полосы обзора
     */
    private var flightLineCalculator: FlightLineCalculator? = null

    /**
     * Объект класса расчета матрицы планирования
     */
    private var mplCalculator: MPLCalculator? = null

    /**
     * Массив точек орбиты
     */
    val points: Iterable<OrbitalPoint>
        get() = if (orbitCalculator != null) orbitCalculator!!.points else listOf<OrbitalPoint>()

    val revolutions: Iterable<RevolutionParameter>
        get() = if (orbitCalculator != null) orbitCalculator!!.revolutions else listOf<RevolutionParameter>()

    /**
     * Массив результатов расчета параметров ЗРВ
     */
    val zrv: Iterable<ZRV>
        get() = if (zrvCalculator != null) zrvCalculator!!.zrv else listOf<ZRV>()

    val flightLine: Iterable<FlightLine>
        get() = if (flightLineCalculator != null) flightLineCalculator!!.flightLine else listOf<FlightLine>()

    val mpl: Iterable<PointViewParams>
        get() = if (mplCalculator != null) mplCalculator!!.mpl else listOf<PointViewParams>()

    val coverings : Map<Int, MutableList<FleghtLineSector>>
        get() = flightLineCalculator?.earthCoveringCalculator?.coverings?:mapOf()


    /**
     * Расчет точек орбиты методом интегрирования
     * @param nu начальные условия движения центра масс КА
     * @param tn время начала расчета
     * @param tk время конца расчета
     * @return код завершения расчета
     */
    fun calculateOrbPoints(
        nu: InitialConditions,
        tn: Double,
        tk: Double,
    ): BallisticsError {
        orbitCalculator = OrbitalPointsIntegrator(nu, modDVType, integrationType, earthType)
        return orbitCalculator!!.calculate(tn, tk)
    }

    fun setOrbitalPoints(
        points: Iterable<OrbitalPoint>,
        earthType: EarthType = EarthType.PZ90d02,
    ) {
        orbitCalculator = OrbitalPointsIntegrator(arrayOf(), ModDVType.FOTO, IntegrationType.ADAMS7, earthType)
        orbitCalculator?.points?.addAll(points)
    }


    fun setEarthCoverage(covs : Map<Int, MutableList<FleghtLineSector>>){
        if (orbitCalculator != null){
            flightLineCalculator = FlightLineCalculator(orbitCalculator!!, rollMin, rollMax, workCoordinateSystem)
            flightLineCalculator?.earthCoveringCalculator?.coverings?.putAll(covs)
        }
    }

    /**
     * Расчет точек орбиты методом интегрирования
     * @param nu массив начальных условий движения центра масс КА
     * @param tn время начала расчета
     * @param tk время конца расчета
     * @return код завершения расчета
     */
    fun calculateOrbPoints(
        nu: Array<InitialConditions>,
        tn: Double,
        tk: Double,
    ): BallisticsError {
        nu.sortBy { it.point.t }

        orbitCalculator = OrbitalPointsIntegrator(nu, modDVType, integrationType, earthType)

        return orbitCalculator!!.calculate(tn, tk)
    }

    /**
     * Расчет параметров ЗРВ
     * @param ppi список ППИ
     * @param tn время начала расчета
     * @param tk время конца расчета
     * @return код завершения расчета
     */
    fun calculateZRV(
        ppi: List<PPI>,
        tn: Double,
        tk: Double,
    ): BallisticsError {
        return orbitCalculator?.let {
            zrvCalculator = ZRVStepperCalculator(it, ppi)
            zrvCalculator?.calculate(tn, tk)
        } ?: BallisticsError.EMPTY_ORBITAL_POINTS
    }

    fun calculateZRVAsync(
        ppi: List<PPI>,
        tn: Double,
        tk: Double,
    ): Flux<ZRV> =
        if (orbitCalculator != null) {
            ZRVStepperCalculatorAsync(orbitCalculator!!, ppi).calculate(tn, tk)
        } else {
            Flux.empty()
        }

    fun calculateFlightLine(
        tn: Double,
        tk: Double,
    ): BallisticsError {
        return orbitCalculator?.let {
            flightLineCalculator = FlightLineCalculator(it, rollMin, rollMax, workCoordinateSystem)
            flightLineCalculator?.calculate(tn, tk)
        } ?: BallisticsError.EMPTY_ORBITAL_POINTS
    }

    fun calculateMPL(
        tn: Double,
        tk: Double,
        objs: Iterable<OPKatObj>,
        tangage: Double = 0.0,
        tangageType: TangageType = TangageType.TTProactive,
    ): BallisticsError {
        return flightLineCalculator?.let {
            mplCalculator =
                MPLCalculator(it, tangage, tangageType).apply {
                    this.sunAngleMin = this@Ballistics.sunAngleMin
                    this.useObjConstraints = this@Ballistics.useObjConstraints
                }
            mplCalculator?.krenMax = rollMax
            mplCalculator?.krenMin = rollMin
            mplCalculator?.krenByModule = rollByModule
            mplCalculator?.calculate(tn, tk, objs)
        } ?: BallisticsError.EMPTY_FLIGHTLINE_POINTS
    }

    /**
     * Объект расчета выхода на заданное время
     */
    fun getStepper(): AbstractStepper? {
        return orbitCalculator?.getStepper()
    }

    fun parseTLE(
        tle1: String,
        tle2: String,
    ): OrbitalPoint {
        try {
            val stepper = TLEStepper(tle1, tle2, EarthType.PZ90d02)
            return stepper.calculate(stepper.baseEpoch)
        }catch (ex : Exception){
            throw ex
        }
    }

    fun parseTLE(tle: TLE): OrbitalPoint {
        val stepper = TLEStepper(tle.tle1, tle.tle2, EarthType.PZ90d02)
        return stepper.calculate(stepper.baseEpoch)
    }

    fun getTLEParams(tle : TLE, capt : String = "") : TLEParams{
        try {
            val stepper = TLEStepper(tle.tle1, tle.tle2, EarthType.PZ90d02)
            val orb = stepper.calculate(stepper.baseEpoch)
            return TLEParams(
                capt, stepper.satellite.orbit.satId.toLong(),
                stepper.satellite.orbit.orbitNum.toLong(),
                toDateTime(orb.t),
                stepper.satellite.orbit.inclination,
                stepper.satellite.orbit.perigee,
                stepper.satellite.orbit.apogee,
                stepper.satellite.orbit.argPerigee,
                stepper.satellite.orbit.eccentricity,
                stepper.satellite.orbit.major,
                stepper.satellite.orbit.minor,
                stepper.satellite.orbit.meanAnomaly,
                stepper.satellite.orbit.period,
                stepper.satellite.orbit.semiMajor,
                stepper.satellite.orbit.semiMinor,
                stepper.satellite.orbit.raan,
                stepper.satellite.orbit.meanMotion,
                stepper.satellite.orbit.meanMotionTle,
            )
        }catch (ex : Exception){
            throw ex
        }
    }

    fun calculateOrbPoints(
        tle: TLE,
        tn: Double,
        tk: Double,
    ): BallisticsError {
        orbitCalculator = OrbitalPointsTLE(tle.tle1, tle.tle2, earthType)
        return orbitCalculator!!.calculate(tn, tk)
    }

    fun calculateOrbPoints(
        tle: TLE,
        duration: Double,
    ): BallisticsError {
        orbitCalculator = OrbitalPointsTLE(tle.tle1, tle.tle2, earthType)
        val tnu = (orbitCalculator as OrbitalPointsTLE).stepper.baseEpoch
        return orbitCalculator!!.calculate(tnu, tnu + duration)
    }

    fun calculateKeplerParams(point: OrbitalPoint): KeplerParams {
        val res = KeplerParams()

        val aL00 = 6.25648106E+7
        val astro = AstronomerJ2000(earthType)
        val ask = astro.grinvToASK(point)

        val r = sqrt(sqr(ask.r.x) + sqr(ask.r.y) + sqr(ask.r.z))
        val v = sqrt(sqr(ask.v.x) + sqr(ask.v.y) + sqr(ask.v.z))

        val mu = astro.earth.fM
        val k = (r * sqr(v)) / mu

        val sinO = (ask.r.x * ask.v.x + ask.r.y * ask.v.y + ask.r.z * ask.v.z) / (r * v)
        val cosO = sqrt(1 - sqr(sinO))

        val c1 = ask.r.y * ask.v.z - ask.r.z * ask.v.y
        val c2 = ask.r.z * ask.v.x - ask.r.x * ask.v.z
        val c3 = ask.r.x * ask.v.y - ask.r.y * ask.v.x
        val c = sqrt(sqr(c1) + sqr(c2) + sqr(c3))

        res.ael = r / (2 - k)
        val lambda = (1 / res.ael) * sqrt(mu / res.ael)

        res.t = (2 * PI * res.ael * sqrt(res.ael)) / sqrt(astro.earth.middleRadius * aL00)
        res.e = sqrt(1 - k * (2 - k) * (1 - sinO * sinO))
        res.v = atan((k * sinO * cosO) / (k * sqr(cosO) - 1))
        res.u = atan2((ask.r.z * c), (ask.r.y * c1 - ask.r.x * c2))
        res.eA = atan((sqrt(1 - sqr(res.e)) * k * sinO * cosO) / (sqr(res.e) + (k * sqr(cosO) - 1)))
        res.tau = ask.t + ((res.e * sin(res.eA) - res.eA) / lambda)
        res.nakl = 0.5 * PI - asin(c3 / c)
        res.omegab = atan2(c1, -c2)
        res.omegam = res.u - res.v
        res.o = asin(sinO)
        res.rA = res.ael * (1 + res.e)
        res.rP = res.ael * (1 - res.e)
        res.dmv = point.t

        if (res.omegam < 0) res.omegam += 2 * PI
        if (res.tau < 0) res.tau += 1

        return res
    }


    fun clear(){
        orbitCalculator?.clear()
        flightLineCalculator?.clear()
        flightLineCalculator?.clear()
        zrvCalculator?.clear()
        mplCalculator?.clear()
    }
}
