package ballistics.orbitalPoints.timeStepper

import ballistics.types.EarthType
import ballistics.types.OrbitalPoint
import ballistics.utils.astro.AstronomerJ2000
import ballistics.utils.math.Vector3D
import org.nstart.dep265.tletools.zeptomoby.core.Julian
import org.nstart.dep265.tletools.zeptomoby.core.TLE
import org.nstart.dep265.tletools.zeptomoby.orbit.Satellite
import java.util.Calendar
import java.util.GregorianCalendar
import java.util.TimeZone

class TLEStepper(str1: String, str2: String, earthType: EarthType) : AbstractStepper {
    val astro = AstronomerJ2000(earthType)
    val tleParser: TLE = TLE("", str1, str2)
    val satellite: Satellite = Satellite(tleParser)
    val baseEpoch: Double

    init {
        baseEpoch = extractUTCMillis(satellite) / 1000.0 + 10800
    }

    override fun clear() {

    }

    private fun extractUTCMillis(
        year: Int,
        month: Int,
        dayOfMonth: Double,
    ): Long {
        val gc = GregorianCalendar()
        gc[Calendar.YEAR] = year
        gc[Calendar.MONTH] = month - 1
        gc[Calendar.DAY_OF_MONTH] = dayOfMonth.toInt()

        gc.timeZone = TimeZone.getTimeZone("UTC")

        var dfrac = dayOfMonth - dayOfMonth.toLong()
        dfrac *= 24.0

        gc[Calendar.HOUR_OF_DAY] = dfrac.toInt()
        dfrac = 60.0 * (dfrac - dfrac.toInt())

        gc[Calendar.MINUTE] = dfrac.toInt()
        dfrac = 60.0 * (dfrac - dfrac.toInt())

        gc[Calendar.SECOND] = dfrac.toInt()
        dfrac = 1000.0 * (dfrac - dfrac.toInt())

        gc[Calendar.MILLISECOND] = dfrac.toInt()

        return gc.timeInMillis
    }

    private fun extractUTCMillis(date: Julian.DateComponent): Long = extractUTCMillis(date.year, date.mon ?: 1, date.dom ?: 1.0)

    private fun extractUTCMillis(sat: Satellite): Long =
        extractUTCMillis(sat.orbit.epoch.getComponent())

    override fun calculate(t: Double): OrbitalPoint {

        try {
            val utcMillis = baseEpoch
            val dt = t - utcMillis
            val pos = satellite.positionEci(dt / 60)
            val ask =
                OrbitalPoint(
                    t,
                    satellite.orbit.orbitNum + (dt / satellite.orbit.period).toInt(),
                    Vector3D(
                        pos.position.x * 1000.0,
                        pos.position.y * 1000.0,
                        pos.position.z * 1000.0,
                    ),
                    Vector3D(
                        pos.velocity.x * 1000.0,
                        pos.velocity.y * 1000.0,
                        pos.velocity.z * 1000.0,
                    ),
                )

            return astro.askToGrinvich(ask)
        } catch (ex : Exception){
            throw ex
        }
    }

    override fun calculate(
        t: Double,
        p: OrbitalPoint,
    ): OrbitalPoint? {
        return calculate(t)
    }
}
