package ballistics.utils.astro

import ballistics.types.EarthType
import ballistics.types.OrbitalPoint
import ballistics.utils.earth.getEarth
import ballistics.utils.math.Matrix3D
import ballistics.utils.math.Vector3D
import java.time.LocalDateTime
import java.time.ZoneOffset
import kotlin.math.PI
import kotlin.math.atan
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
import kotlin.math.truncate

class AstronomerJ2000(var earthType: EarthType) {
    var ase = 149600000.0
    var earth = getEarth(earthType)
    var baseDate =
        LocalDateTime.of(2000, 1, 1, 12, 0, 0, 0).toEpochSecond(ZoneOffset.UTC)
            .toDouble() / 86400

    fun si2000(tDtTm: Double): Double {
        var d = tDtTm / 86400.0
        var dat = truncate(d)
        var tt = d - dat
//        if (tt < 0.125) {
//            dat--
//        }

        var t = (dat - baseDate) / (36525.0)
        var t2 = t * t
        var t3 = t2 * t

        var lt =
            2.355555743494e0 + 8328.691425719e0 * t +
                1.545547e-4 * t2 + 2.50e-7 * t3
        var llt =
            6.240060126913e0 + 628.301955171e0 * t - 2.681989e-6 * t2 -
                6.593e-10 * t3
        var ft =
            1.6279050815375e0 + 8433.466156916e0 * t - 6.181956e-5 * t2 +
                5.0275e-9 * t3
        var dt =
            5.1984665886602e0 + 7771.377145594e0 * t - 5.559397e-4 * t2 +
                3.196e-8 * t3
        var omt =
            2.1824391966156e0 - 33.757044613e0 * t + 3.622625e-5 * t2 +
                3.734e-8 * t3

        var psi =
            (-17.1996e0 - 0.01742 * t) * sin(omt) +
                (0.2062e0 + 0.00002 * t) * sin(2 * omt) +
                (-1.3187e0 - 0.00016 * t) * sin(2 * (ft - dt + omt)) +
                (0.1426e0 - 0.00034 * t) * sin(llt) +
                (-0.0517 + 0.00012 * t) * sin(llt + 2 * ft - 2 * dt + 2 * omt) +
                (0.0217 - 0.00005 * t) * sin(-llt + 2 * ft - 2 * dt + 2 * omt) +
                (0.0129 + 0.00001 * t) * sin(2 * ft - 2 * dt + omt)

        var r =
            (-0.2274e0 - 0.00002 * t) * sin(2 * ft + 2 * omt) +
                (0.0712e0 + 0.00001 * t) * sin(lt) +
                (-0.0386e0 - 0.00004 * t) * sin(2 * ft + omt) -
                0.0301e0 * sin(lt + 2 * ft + 2 * omt) -
                0.0158 * sin(lt - 2 * dt) +
                0.0123 * sin(-lt + 2 * ft + 2 * omt)

        psi += r

//        if (tt < 0.125) {
//            tt += 0.875
//        } else {
//            tt -= 0.125
//        }

        r = 84381.448e0 - 46.84024e0 * t - 0.00059e0 * t2 + 0.001813e0 * t3
        r = r * PI / 180.0 / 3600.0
        psi = psi * PI / 180.0 / 3600.0
        r = cos(r)
        r = psi * r
        var s =
            (
                24110.54841 + 8640184.812866 * t + 0.093104 * t2 -
                    6.2e-6 * t3
            ) * 2 * PI / 86400.0e0

        s = s + r + 2 * PI * tt * 1.002737811906E0

        var ds = s / (PI + PI)
        var ts = truncate(ds)

        s = (ds - ts) * (PI + PI)

        return s
    }

    fun grinvToASK(gsk: OrbitalPoint): OrbitalPoint {
        var m = Matrix3D()
        m.makeOzMatrix(si2000(gsk.t))

        var ra = m * gsk.r
        var va = m * gsk.v

        va.x -= earth.wEarth * ra.y
        va.y += earth.wEarth * ra.x

        return OrbitalPoint(gsk.t, gsk.vit, ra, va)
    }

    fun grinvToASK(
        gsk: OrbitalPoint,
        si: Double,
    ): OrbitalPoint {
        var m = Matrix3D()
        m.makeOzMatrix(si)
        var ra = m * gsk.r
        var va = m * gsk.v

        va.x -= earth.wEarth * ra.y
        va.y += earth.wEarth * ra.x

        return OrbitalPoint(gsk.t, gsk.vit, ra, va)
    }

    fun grinvToASK(
        gsk: Vector3D,
        t: Double,
    ): Vector3D {
        var m = Matrix3D()
        m.makeOzMatrix(si2000(t))
        return m * gsk
    }

    fun askToGrinvich(
        ask: Vector3D,
        t: Double,
    ): Vector3D {
        var m = Matrix3D()
        m.makeOzMatrix(-si2000(t))
        return m * ask
    }

    fun askToGrinvich(ask: OrbitalPoint): OrbitalPoint {
        var m = Matrix3D()
        m.makeOzMatrix(-si2000(ask.t))

        var ra = m * ask.r
        var va = m * ask.v

        va.x += earth.wEarth * ra.y
        va.y -= earth.wEarth * ra.x

        return OrbitalPoint(ask.t, ask.vit, ra, va)
    }

    fun askToGrinvich(
        ask: OrbitalPoint,
        si: Double,
    ): OrbitalPoint {
        var m = Matrix3D()
        m.makeOzMatrix(-si)
        var ra = m * ask.r
        var va = m * ask.v

        va.x += earth.wEarth * ra.y
        va.y -= earth.wEarth * ra.x

        return OrbitalPoint(ask.t, ask.vit, ra, va)
    }

    fun moonCoordinates(tm: Double): Vector3D {
        var d: Double = tm / 86400.0
//        d = d - 0.125
        val t: Double = (d - baseDate) / 36525.0 // Юлианский период ot 12h 1.1.2000
        val t2 = t * t
        val t3 = t2 * t
        // (* Средняя аномалия ЛУНЫ*)
        val lL = 2.355555743494e0 + 8328.691425719e0 * t + 1.545547e-4 * t2 + 2.50e-7 * t3
        // (* Средняя аномалия СОЛНЦА*)
        val lS = 6.240060126913e0 + 628.301955171e0 * t - 2.681989e-6 * t2 - 6.593e-10 * t3
        // (* Средний аргумент широты ЛУНЫ *)
        val f = 1.6279050815375e0 + 8433.466156916e0 * t - 6.181956e-5 * t2 + 5.0275e-9 * t3
        // (* Разность средних долгот ЛУНЫ и СОЛНЦА *)
        d = 5.1984665886602e0 + 7771.377145594e0 * t - 5.559397e-4 * t2 + 3.196e-8 * t3
        val d2 = d + d
        // (*  Вычисление горизонтального паралакса и радиуса ЛУНЫ *)
        val pl1 =
            4.8481368110953599e-6 * (
                3422.7 + 28.233 * cos(d2) + 3.086 * cos(lL + d2) + 186.539 * cos(lL) + 34.311 *
                    cos(
                        lL - d2,
                    ) + 0.6 * cos(lL - 4 * d) - 0.399 * cos(lS) +
                    1.917 * cos(lS - d2) - 0.978 * cos(d) + 10.165 * cos(2 * lL) -
                    0.949 * cos(lL + lS) + 1.443 * cos(lL + lS - d2) + 1.152 * cos(lL - lS) + 0.621 * cos(3 * lL) - 0.713 *
                    cos(
                        lL - 2 * f,
                    )
            )
        var rl = 6378.14 / pl1
        rl = rl * 1.0e3
        // (* Средняя долгота ЛУНЫ *)
        val vL =
            3.8103405583329555 + 8399.7091123847003500 * t -
                0.0000281288897780 * t2 + 0.0000000921145994 * t3
        // (* Вычисление эклиптической долготы ЛУНЫ*)
        val vSin =
            4.8481368110953599e-6 * (
                2369.9 * sin(d2) + 191.95 * sin(lL + d2) + 22639.5 * sin(lL) - 4586.42 * sin(lL - d2) - 668.11 *
                    sin(
                        lS,
                    ) - 165.14 * sin(lS - d2) - 125.15 * sin(d) + 769.01 * sin(2 * lL) - 211.65 * sin(2 * lL - d2) - 109.66 *
                    sin(
                        lL + lS,
                    ) - 205.96 * sin(lL + lS - d2) + 147.69 * sin(lL - lS) - 411.6 * sin(2 * f)
            )
        val arg = vL + vSin
        val cl = cos(arg)
        val sl = sin(arg)
        // (* Вычисление эклиптической широты Луны *)
        val betta =
            4.8481368110953599e-6 * (
                117.26 * sin(f + d2) + 18461.48 * sin(f) -
                    623.65 * sin(f - d2) + 1010.18 * sin(lL + f) -
                    166.57 * sin(lL + f - d2) + 199.48 * sin(-lL + f + d2) -
                    999.69 * sin(-lL + f)
            )
        val cb = cos(betta)
        val sb = sin(betta)
        // (* Средний наклон эклиптики  *)
        val ep = 0.4090928042223290 - 0.0002270878917845 * t - 0.0000000028604007 * t2 + 0.0000000087896720 * t3
        val ce = cos(ep)
        val se = sin(ep)
        var moon = Vector3D()
        moon.x = rl * cl * cb
        moon.y = rl * (sl * cb * ce - sb * se)
        moon.z = rl * (sb * ce + sl * cb * se)

        return moon
    }

    fun sunCoordinates(tm: Double): Vector3D {
        var dd = tm / 86400.0
//        var dd = d - 0.125
        var t = (dd - baseDate) / 36525.0
        var t2 = t * t
        var t3 = t2 * t

        //  Средняя долгота
        var l =
            4.8481368110953599e-6 * (
                1009677.850 +
                    (100 * 1296000.0 + 2771.270) * t + 1.089 * t2
            )

        //  Долгота перигелия
        var p =
            4.8481368110953599e-6 * (
                1018578.046 + 6190.046 * t +
                    1.666 * t2 + 0.012 * t3
            )

        //  Эксцентриситет земной орбиты
        var ek = 0.0167086342 - 0.00004203654 * t - 0.00000012673 * t2

        // Средний наклон эклиптики
        var e1 =
            4.8481368110953599e-6 * (
                84381.448 - 46.84024 * t -
                    0.00059 * t2 + 0.001813 * t3
            )

        // Истинный наклон эклиптики
        var oMT =
            2.1824391966156e0 - 33.757044613e0 * t + 3.622625e-5 * t2 +
                3.734e-8 * t3

        var psi = 4.8481368110953599e-6 * 9.2025e0 * cos(oMT)

        e1 += psi

        //  Средняя аномалия Земли
        var g = l - p
        var e = g + ek

        for (i in 1..5)
            e = g + ek * sin(e)

        // Радиус Земли *)
        var r = ase * (1.0 - ek * cos(e)) * 1.0e3

        // Истинная аномалия
        var sinv = ase / r * sqrt(1.0 - ek * ek) * sin(e)
        var cosv = ase / r * (cos(e) - ek)
        var v = atan2(sinv, cosv)

        // Эклиптическая истинная долгота *)
        var la = v + p

        var sun = Vector3D()
        // Собственно вычисления координат *)
        sun.x = r * cos(la)
        var y = r * sin(la)
        sun.y = y * cos(e1)
        sun.z = y * sin(e1)

        return sun
    }

    fun sunCoordinatesGSK(t: Double): Vector3D {
        var m = Matrix3D()
        m.makeOzMatrix(-si2000(t))
        var sun = sunCoordinates(t)
        return m * sun
    }

    fun sunAngle(
        t: Double,
        currentObj: Vector3D,
    ): Double {
        var sun = sunCoordinatesGSK(t)

        var blh = earth.xyz2blh(currentObj)

        var m =
            Matrix3D(
                Vector3D(cos(blh.lat) * cos(blh.long), cos(blh.lat) * sin(blh.long), sin(blh.lat)),
                Vector3D(-sin(blh.long), cos(blh.long), 0.0),
                Vector3D(-sin(blh.lat) * cos(blh.long), -sin(blh.lat) * sin(blh.long), cos(blh.lat)),
            )
        var pp = m * currentObj
        var sp = m * sun

        var r = sp - pp

        return atan(r.x / sqrt(r.z * r.z + r.y * r.y))
    }

    fun sunMoonCorrection(
        t3: Double,
        x: Double,
        y: Double,
        z: Double,
    ): Vector3D {
        var corrections = Vector3D()
        val lm = 4902.7854
        val mss = 132712518017.51
        val kapa = 0.0

        var sun = sunCoordinates(t3)
        sun = sun * (1 / 1000.0)
        var moon = moonCoordinates(t3)
        moon = moon * (1 / 1000.0)

        // Учет влияния Луны
        var dpx: Double
        var dpy: Double
        var dpz: Double

        dpx = moon.x - x
        dpy = moon.y - y
        dpz = moon.z - z

        var r = dpx * dpx + dpy * dpy + dpz * dpz
        var dr = sqrt(r)
        var pp1 = r * dr

        r = moon.x * moon.x + moon.y * moon.y + moon.z * moon.z
        dr = sqrt(r)
        var pp2 = r * dr

        corrections.x = lm * (dpx / pp1 - moon.x / pp2)
        corrections.y = lm * (dpy / pp1 - moon.y / pp2)
        corrections.z = lm * (dpz / pp1 - moon.z / pp2)

        dpx = sun.x - x
        dpy = sun.y - y
        dpz = sun.z - z

        r = dpx * dpx + dpy * dpy + dpz * dpz
        dr = sqrt(r)
        pp1 = r * dr

        dr = sun.module()
        r = dr * dr
        pp2 = r * dr

        var kap = kapa
        val rap = sqrt(x * x + y * y + z * z)
        val cf = (sun.x * x + sun.y * y + sun.z * z) / (dr * rap)
        if (cf <= 0) {
            val rab1 = rap * sqrt(1 - cf * cf)
            if (rab1 < 6378.388) kap = 0.0
        }

        val p1 = mss / pp1 * (1 - kap)
        val p2 = mss / pp2
        // Закоментарить для неучета Солнца
        corrections.x += (dpx * p1 - sun.x * p2)
        corrections.y += (dpy * p1 - sun.y * p2)
        corrections.z += (dpz * p1 - sun.z * p2)

        return corrections
    }
}
