package org.nstart.dep265.tletools.zeptomoby.core

@Suppress("unused")
open class Julian {

    object EpochConst {
        const val EPOCH_JAN0_12H_1900 = 2415020.0 // Dec 31.5 1899 = Dec 31 1899 12h UTC
        const val EPOCH_JAN1_00H_1900 = 2415020.5 // Jan  1.0 1900 = Jan  1 1900 00h UTC
        const val EPOCH_JAN1_12H_2000 = 2451545.0 // Jan  1.5 2000 = Jan  1 2000 12h UTC
    }

    object Static {
        fun isLeapYear(y: Int): Boolean =
            (y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)
    }

    data class DateComponent(val year: Int, val mon: Int? = null, val dom: Double? = null)

    protected var date: Double = 0.0

    constructor() {
        initialize(2000, 1.0)
    }

    constructor(year: Int, day: Double) {
        initialize(year, day)
    }

    constructor(year: Int, mon: Int, day: Int, hour: Int, min: Int, sec: Double = 0.0) {
        val f1 = (275.0 * mon / 9.0).toInt()
        val f2 = ((mon + 9.0) / 12.0).toInt()

        val n = if (org.nstart.dep265.tletools.zeptomoby.core.Julian.Static.isLeapYear(year)) f1 - f2 + day - 30
                else f1 - 2 * f2 + day - 30

        val dblDay = n + (hour + (min + sec / 60.0) / 60.0) / 24.0
        initialize(year, dblDay)
    }

    fun toGMST(): Double {
        val ut = org.nstart.dep265.tletools.zeptomoby.core.Globals.fmod(date + 0.5, 1.0)
        val tu = (fromJan1_12h_2000() - ut) / 36525.0
        var gmst = 24110.54841 + tu * (8640184.812866 + tu * (0.093104 - tu * 6.2e-06))

        gmst = org.nstart.dep265.tletools.zeptomoby.core.Globals.fmod(
            gmst + org.nstart.dep265.tletools.zeptomoby.core.Globals.secPerDay * org.nstart.dep265.tletools.zeptomoby.core.Globals.omegaE * ut,
            org.nstart.dep265.tletools.zeptomoby.core.Globals.secPerDay
        )

        if (gmst < 0.0) gmst += org.nstart.dep265.tletools.zeptomoby.core.Globals.secPerDay
        return  org.nstart.dep265.tletools.zeptomoby.core.Globals.twoPi * (gmst / org.nstart.dep265.tletools.zeptomoby.core.Globals.secPerDay)
    }

    fun toLMST(lon: Double): Double = org.nstart.dep265.tletools.zeptomoby.core.Globals.fmod(
        toGMST() + lon,
        org.nstart.dep265.tletools.zeptomoby.core.Globals.twoPi
    )

    @Suppress("FunctionName")
    fun fromJan0_12h_1900(): Double = date - org.nstart.dep265.tletools.zeptomoby.core.Julian.EpochConst.EPOCH_JAN0_12H_1900
    @Suppress("FunctionName")
    fun fromJan1_00h_1900(): Double = date - org.nstart.dep265.tletools.zeptomoby.core.Julian.EpochConst.EPOCH_JAN1_00H_1900
    @Suppress("FunctionName")
    fun fromJan1_12h_2000(): Double = date - org.nstart.dep265.tletools.zeptomoby.core.Julian.EpochConst.EPOCH_JAN1_12H_2000

    fun getComponent(): org.nstart.dep265.tletools.zeptomoby.core.Julian.DateComponent {
        val jdAdj = date + 0.5
        val z = jdAdj.toInt()
        val f = jdAdj - z
        val alpha = ((z - 1867216.25) / 36524.25).toInt()
        val a = z + 1 + alpha - (alpha * 0.25).toInt()
        val b = a + 1524.0
        val c = ((b - 122.1) / 365.25).toInt()
        val d = (c * 365.25).toInt()
        val e = ((b - d) / 30.6001).toInt()

        val dom   = b - d - (e * 30.6001).toInt() + f
        val month = if (e < 13.5) e - 1 else e - 13
        val year  = if (month > 2.5) c - 4716 else c - 4715

        return org.nstart.dep265.tletools.zeptomoby.core.Julian.DateComponent(year, month, dom)
    }

    fun addDay(day: Double) {
        date += day
    }

    fun addHour(hr: Double) {
        date += hr / org.nstart.dep265.tletools.zeptomoby.core.Globals.hrPerDay
    }

    fun addMin(min: Double) {
        date += min / org.nstart.dep265.tletools.zeptomoby.core.Globals.minPerDay
    }

    fun addSec(sec: Double) {
        date += sec / org.nstart.dep265.tletools.zeptomoby.core.Globals.secPerDay
    }

    fun spanDay (b: org.nstart.dep265.tletools.zeptomoby.core.Julian): Double = date - b.date
    fun spanHour(b: org.nstart.dep265.tletools.zeptomoby.core.Julian): Double = spanDay(b) * org.nstart.dep265.tletools.zeptomoby.core.Globals.hrPerDay
    fun spanMin (b: org.nstart.dep265.tletools.zeptomoby.core.Julian): Double = spanDay(b) * org.nstart.dep265.tletools.zeptomoby.core.Globals.minPerDay
    fun spanSec (b: org.nstart.dep265.tletools.zeptomoby.core.Julian): Double = spanDay(b) * org.nstart.dep265.tletools.zeptomoby.core.Globals.secPerDay

    protected fun initialize(year: Int, day: Double) {
        assert((year > 1582) && (year < 3000))
        assert((day >= 1.0) && (day < 367.0))

        val iyear = year - 1

        val a: Int = iyear / 100
        val b: Int = 2 - a + a / 4

        val newYears: Double = (365.25 * iyear).toInt() +
                       (30.6001 * 14).toInt() +
                       1720994.5 + b

        date = newYears + day
    }
}