package org.nstart.dep265.tletools.zeptomoby.orbit

import org.nstart.dep265.tletools.zeptomoby.core.Globals
import org.nstart.dep265.tletools.zeptomoby.core.Globals.sqr
import org.nstart.dep265.tletools.zeptomoby.core.Julian
import org.nstart.dep265.tletools.zeptomoby.core.TLE
import org.nstart.dep265.tletools.zeptomoby.core.eci.EciTime
import org.nstart.dep265.tletools.zeptomoby.orbit.norad.NoradBase
import org.nstart.dep265.tletools.zeptomoby.orbit.norad.NoradSDP4
import org.nstart.dep265.tletools.zeptomoby.orbit.norad.NoradSGP4
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sqrt

@Suppress("unused")
open class Orbit(t: TLE) {

    val inclination: Double
    val eccentricity: Double
    val raan: Double
    val argPerigee: Double
    val bStar: Double
    val drag: Double
    val meanMotionTle: Double
    val meanAnomaly: Double

    val epoch: Julian
    val satId: String

    val satName: String
        get() = tle.header
    val tleFirstLine: String
        get() = tle.first
    val tleSecondLine: String
        get() = tle.second

    val semiMajor: Double
    val semiMinor: Double
    val meanMotion: Double
    val perigee: Double
    val apogee: Double
    val orbitNum: Int
    val period: Double
        get() {
            if (secPeriod < 0.0) {
                secPeriod = if (meanMotion == 0.0) 0.0
                            else Globals.twoPi / meanMotion * 60.0
            }
            return secPeriod
        }
    val major: Double
        get() = semiMajor * 2.0
    val minor: Double
        get() = semiMinor * 2.0

    private val tle: TLE = t
    private val noradModel: NoradBase
    private var secPeriod: Double

    init {
        satId = tle.getField(TLE.Fields.FLD_NORADNUM).second
        inclination   = radGet(TLE.Fields.FLD_I).first
        eccentricity  = tle.getField(TLE.Fields.FLD_E).first
        raan          = radGet(TLE.Fields.FLD_RAAN).first
        argPerigee    = radGet(TLE.Fields.FLD_ARGPER).first
        bStar         = tle.getField(TLE.Fields.FLD_BSTAR).first / Globals.ae
        drag          = tle.getField(TLE.Fields.FLD_MMOTIONDT).first
        meanMotionTle = tle.getField(TLE.Fields.FLD_MMOTION).first
        meanAnomaly   = radGet(TLE.Fields.FLD_M).first
        var epochYear = tle.getField(TLE.Fields.FLD_EPOCHYEAR).first.toInt()
        val epochDay = tle.getField(TLE.Fields.FLD_EPOCHDAY).first
        orbitNum = tle.getField(TLE.Fields.FLD_ORBITNUM).first.toInt()
        epochYear += if (epochYear < 57) 2000 else 1900
        epoch = Julian(epochYear, epochDay)
        secPeriod = -1.0
        val mm = meanMotionTle
        val rpmin = mm * Globals.twoPi / Globals.minPerDay
        val a1 = (Globals.xke / rpmin).pow(2.0 / 3.0)
        val e = eccentricity
        val i = inclination
        val temp = 1.5 * Globals.ck2 * (3.0 * sqr(cos(i)) - 1.0) /
                (1.0 - e * e).pow(1.5)
        val delta1 = temp / (a1 * a1)
        val a0 = a1 *
                (1.0 - delta1 *
                        ((1.0 / 3.0) + delta1 *
                                (1.0 + 134.0 / 81.0 * delta1)))
        val delta0 = temp / (a0 * a0)
        meanMotion = rpmin / (1.0 + delta0)
        semiMajor = a0 / (1.0 - delta0)
        semiMinor = semiMajor * sqrt(1.0 - (e * e))
        perigee = Globals.wgs.xkmPer * (semiMajor * (1.0 - e) - Globals.ae)
        apogee = Globals.wgs.xkmPer * (semiMajor * (1.0 + e) - Globals.ae)
        noradModel = if (Globals.twoPi / meanMotion >= 225) NoradSDP4(this)
        else NoradSGP4(this)
    }

    fun tPlusEpoch(t: Julian) = t.spanSec(epoch)

    @Deprecated("", ReplaceWith("positionEci(mpe)"))
    fun position(mpe: Double) = positionEci(mpe)

    fun positionEci(mpe: Double): EciTime {
        val eci = noradModel.position(mpe)
        val radiusAe: Double = Globals.wgs.xkmPer / Globals.ae

        eci.scalePosVector(radiusAe) // km
        eci.scaleVelVector(radiusAe * (Globals.minPerDay / 86400)) // km/sec

        return eci
    }

    protected fun radGet(fld: TLE.Fields) = tle.getField(fld, TLE.Units.U_RAD)
    protected fun degGet(fld: TLE.Fields) = tle.getField(fld, TLE.Units.U_DEG)
}