package basic

import (
	"math"
	"sort"
	"time"

	. "b612.me/astro/tools"
)

const (
	moonMaxDeclinationMeanMonthDays = 27.321582247
	moonMaxDeclinationBaseCycle     = 1336.86
	moonMaxDeclinationSearchSpan    = 3
)

// DeclinationEvent 赤纬极值事件 / declination extremum event.
type DeclinationEvent struct {
	// JDE 是事件发生时刻对应的世界时儒略日 / event time as UTC-based Julian day.
	JDE float64
	// Declination 是该时刻月心地心赤纬，单位度 / geocentric lunar declination at the event, in degrees.
	Declination float64
}

type moonMaxDeclinationCoefficients struct {
	D0   float64
	M0   float64
	MP0  float64
	F0   float64
	JDE0 float64
	sign float64
	tc   [44]float64
	dc   [37]float64
}

var moonMaxDeclinationNorthCoefficients = moonMaxDeclinationCoefficients{
	D0:   152.2029,
	M0:   14.8591,
	MP0:  4.6881,
	F0:   325.8867,
	JDE0: 2451562.5897,
	sign: 1,
	tc: [44]float64{
		0.8975, -0.4726, -0.1030, -0.0976, -0.0462, -0.0461, -0.0438, 0.0162,
		-0.0157, 0.0145, 0.0136, -0.0095, -0.0091, -0.0089, 0.0075, -0.0068,
		0.0061, -0.0047, -0.0043, -0.0040, -0.0037, 0.0031, 0.0030, -0.0029,
		-0.0029, -0.0027, 0.0024, -0.0021, 0.0019, 0.0018, 0.0018, 0.0017,
		0.0017, -0.0014, 0.0013, 0.0013, 0.0012, 0.0011, -0.0011, 0.0010,
		0.0010, -0.0009, 0.0007, -0.0007,
	},
	dc: [37]float64{
		5.1093, 0.2658, 0.1448, -0.0322, 0.0133, 0.0125, -0.0124, -0.0101,
		0.0097, -0.0087, 0.0074, 0.0067, 0.0063, 0.0060, -0.0057, -0.0056,
		0.0052, 0.0041, -0.0040, 0.0038, -0.0034, -0.0029, 0.0029, -0.0028,
		-0.0028, -0.0023, -0.0021, 0.0019, 0.0018, 0.0017, 0.0015, 0.0014,
		-0.0012, -0.0012, -0.0010, -0.0010, 0.0006,
	},
}

var moonMaxDeclinationSouthCoefficients = moonMaxDeclinationCoefficients{
	D0:   345.6676,
	M0:   1.3951,
	MP0:  186.21,
	F0:   145.1633,
	JDE0: 2451548.9289,
	sign: -1,
	tc: [44]float64{
		-0.8975, -0.4726, -0.1030, -0.0976, 0.0541, 0.0516, -0.0438, 0.0112,
		0.0157, 0.0023, -0.0136, 0.0110, 0.0091, 0.0089, 0.0075, -0.0030,
		-0.0061, -0.0047, -0.0043, 0.0040, -0.0037, -0.0031, 0.0030, 0.0029,
		-0.0029, -0.0027, 0.0024, -0.0021, -0.0019, -0.0006, -0.0018, -0.0017,
		0.0017, 0.0014, -0.0013, -0.0013, 0.0012, 0.0011, 0.0011, 0.0010,
		0.0010, -0.0009, -0.0007, -0.0007,
	},
	dc: [37]float64{
		-5.1093, 0.2658, -0.1448, 0.0322, 0.0133, 0.0125, -0.0015, 0.0101,
		-0.0097, 0.0087, 0.0074, 0.0067, -0.0063, -0.0060, 0.0057, -0.0056,
		-0.0052, -0.0041, -0.0040, -0.0038, 0.0034, -0.0029, 0.0029, 0.0028,
		-0.0028, 0.0023, 0.0021, 0.0019, 0.0018, -0.0017, 0.0015, 0.0014,
		0.0012, -0.0012, 0.0010, -0.0010, 0.0037,
	},
}

// MoonMaximumNorthDeclinations 指定年月内的所有月球最大北赤纬事件 / all maximum northern lunar declination events in the given Gregorian month.
func MoonMaximumNorthDeclinations(year int, month time.Month) []DeclinationEvent {
	return moonMaximumDeclinationsInMonth(year, month, moonMaxDeclinationNorthCoefficients)
}

// MoonMaximumSouthDeclinations 指定年月内的所有月球最大南赤纬事件 / all maximum southern lunar declination events in the given Gregorian month.
func MoonMaximumSouthDeclinations(year int, month time.Month) []DeclinationEvent {
	return moonMaximumDeclinationsInMonth(year, month, moonMaxDeclinationSouthCoefficients)
}

// LastMoonMaximumNorthDeclination 指定时刻之前最近一次月球最大北赤纬 / last maximum northern lunar declination at or before jd.
func LastMoonMaximumNorthDeclination(jd float64) DeclinationEvent {
	return moonMaximumDeclinationSearch(jd, moonMaxDeclinationNorthCoefficients, -1, true)
}

// NextMoonMaximumNorthDeclination 指定时刻之后最近一次月球最大北赤纬 / next maximum northern lunar declination after jd.
func NextMoonMaximumNorthDeclination(jd float64) DeclinationEvent {
	return moonMaximumDeclinationSearch(jd, moonMaxDeclinationNorthCoefficients, 1, false)
}

// ClosestMoonMaximumNorthDeclination 离指定时刻最近一次月球最大北赤纬 / closest maximum northern lunar declination to jd.
func ClosestMoonMaximumNorthDeclination(jd float64) DeclinationEvent {
	return moonClosestMaximumDeclination(jd, moonMaxDeclinationNorthCoefficients)
}

// LastMoonMaximumSouthDeclination 指定时刻之前最近一次月球最大南赤纬 / last maximum southern lunar declination at or before jd.
func LastMoonMaximumSouthDeclination(jd float64) DeclinationEvent {
	return moonMaximumDeclinationSearch(jd, moonMaxDeclinationSouthCoefficients, -1, true)
}

// NextMoonMaximumSouthDeclination 指定时刻之后最近一次月球最大南赤纬 / next maximum southern lunar declination after jd.
func NextMoonMaximumSouthDeclination(jd float64) DeclinationEvent {
	return moonMaximumDeclinationSearch(jd, moonMaxDeclinationSouthCoefficients, 1, false)
}

// ClosestMoonMaximumSouthDeclination 离指定时刻最近一次月球最大南赤纬 / closest maximum southern lunar declination to jd.
func ClosestMoonMaximumSouthDeclination(jd float64) DeclinationEvent {
	return moonClosestMaximumDeclination(jd, moonMaxDeclinationSouthCoefficients)
}

func moonMaximumDeclinationsInMonth(year int, month time.Month, coeffs moonMaxDeclinationCoefficients) []DeclinationEvent {
	startUTC := time.Date(year, month, 1, 0, 0, 0, 0, time.UTC)
	endUTC := startUTC.AddDate(0, 1, 0)
	startTT := TD2UT(Date2JDE(startUTC), true)
	endTT := TD2UT(Date2JDE(endUTC), true)

	kStart := int(math.Floor((startTT-coeffs.JDE0)/moonMaxDeclinationMeanMonthDays)) - 1
	kEnd := int(math.Ceil((endTT-coeffs.JDE0)/moonMaxDeclinationMeanMonthDays)) + 1

	cfg := apsisSearchConfig{
		bracketHalfWidth: moonApsisBracketHalfWidth,
		sampleStep:       moonApsisSampleStep,
		derivativeStep:   moonApsisDerivativeStep,
		toleranceDays:    moonApsisToleranceDays,
		maxIterations:    moonApsisMaxIterations,
		maximize:         coeffs.sign > 0,
	}

	events := make([]DeclinationEvent, 0, 2)
	for k := kStart; k <= kEnd; k++ {
		event := moonMaximumDeclinationEvent(k, coeffs, cfg)
		eventTimeUTC := JDE2DateByZone(event.JDE, time.UTC, false)
		if eventTimeUTC.Before(startUTC) || !eventTimeUTC.Before(endUTC) {
			continue
		}
		events = append(events, event)
	}

	sort.Slice(events, func(i, j int) bool {
		return events[i].JDE < events[j].JDE
	})
	return events
}

func moonMaximumDeclinationEvent(k int, coeffs moonMaxDeclinationCoefficients, cfg apsisSearchConfig) DeclinationEvent {
	seedTT := moonMaximumDeclinationSeedTT(k, coeffs)
	eventTT, declination := refineDistanceExtremum(seedTT, cfg, func(sampleTT float64) float64 {
		return HMoonTrueDecN(sampleTT, -1)
	})
	return DeclinationEvent{
		JDE:         TD2UT(eventTT, false),
		Declination: declination,
	}
}

func moonMaximumDeclinationSearch(jd float64, coeffs moonMaxDeclinationCoefficients, direction int, includeCurrent bool) DeclinationEvent {
	cfg := apsisSearchConfig{
		bracketHalfWidth: moonApsisBracketHalfWidth,
		sampleStep:       moonApsisSampleStep,
		derivativeStep:   moonApsisDerivativeStep,
		toleranceDays:    moonApsisToleranceDays,
		maxIterations:    moonApsisMaxIterations,
		maximize:         coeffs.sign > 0,
	}
	targetTT := TD2UT(jd, true)
	centerK := int(math.Round((targetTT - coeffs.JDE0) / moonMaxDeclinationMeanMonthDays))

	found := false
	bestDistance := math.Inf(1)
	var best DeclinationEvent
	for offset := -moonMaxDeclinationSearchSpan; offset <= moonMaxDeclinationSearchSpan; offset++ {
		event := moonMaximumDeclinationEvent(centerK+offset, coeffs, cfg)
		delta := event.JDE - jd
		if !moonMaximumDeclinationMatchesDirection(delta, direction, includeCurrent) {
			continue
		}
		distance := math.Abs(delta)
		if !found || distance < bestDistance || (distance == bestDistance && moonMaximumDeclinationEarlier(event, best)) {
			best = event
			bestDistance = distance
			found = true
		}
	}
	return best
}

func moonClosestMaximumDeclination(jd float64, coeffs moonMaxDeclinationCoefficients) DeclinationEvent {
	last := moonMaximumDeclinationSearch(jd, coeffs, -1, true)
	next := moonMaximumDeclinationSearch(jd, coeffs, 1, false)
	lastDistance := math.Abs(jd - last.JDE)
	nextDistance := math.Abs(next.JDE - jd)
	if lastDistance <= nextDistance {
		return last
	}
	return next
}

func moonMaximumDeclinationMatchesDirection(delta float64, direction int, includeCurrent bool) bool {
	switch direction {
	case -1:
		if includeCurrent {
			return delta <= 0
		}
		return delta < 0
	case 1:
		if includeCurrent {
			return delta >= 0
		}
		return delta > 0
	default:
		return true
	}
}

func moonMaximumDeclinationEarlier(a, b DeclinationEvent) bool {
	return a.JDE < b.JDE
}

func moonMaximumDeclinationSeedTT(k int, coeffs moonMaxDeclinationCoefficients) float64 {
	cycle := float64(k)
	T := cycle / moonMaxDeclinationBaseCycle
	D := Limit360(coeffs.D0 + 333.0705546*cycle - 0.0004214*T*T + 0.00000011*T*T*T)
	M := Limit360(coeffs.M0 + 26.9281592*cycle - 0.0000355*T*T - 0.0000001*T*T*T)
	MP := Limit360(coeffs.MP0 + 356.9562794*cycle + 0.0103066*T*T + 0.00001251*T*T*T)
	F := Limit360(coeffs.F0 + 1.4467807*cycle - 0.0020690*T*T - 0.00000215*T*T*T)
	E := 1 - 0.002516*T - 0.0000074*T*T

	return coeffs.JDE0 +
		moonMaxDeclinationMeanMonthDays*cycle +
		0.000119804*T*T -
		0.000000141*T*T*T +
		coeffs.tc[0]*Cos(F) +
		coeffs.tc[1]*Sin(MP) +
		coeffs.tc[2]*Sin(2*F) +
		coeffs.tc[3]*Sin(2*D-MP) +
		coeffs.tc[4]*Cos(MP-F) +
		coeffs.tc[5]*Cos(MP+F) +
		coeffs.tc[6]*Sin(2*D) +
		coeffs.tc[7]*Sin(M)*E +
		coeffs.tc[8]*Cos(3*F) +
		coeffs.tc[9]*Sin(MP+2*F) +
		coeffs.tc[10]*Cos(2*D-F) +
		coeffs.tc[11]*Cos(2*D-MP-F) +
		coeffs.tc[12]*Cos(2*D-MP+F) +
		coeffs.tc[13]*Cos(2*D+F) +
		coeffs.tc[14]*Sin(2*MP) +
		coeffs.tc[15]*Sin(MP-2*F) +
		coeffs.tc[16]*Cos(2*MP-F) +
		coeffs.tc[17]*Sin(MP+3*F) +
		coeffs.tc[18]*Sin(2*D-M-MP)*E +
		coeffs.tc[19]*Cos(MP-2*F) +
		coeffs.tc[20]*Sin(2*(D-MP)) +
		coeffs.tc[21]*Sin(F) +
		coeffs.tc[22]*Sin(2*D+MP) +
		coeffs.tc[23]*Cos(MP+2*F) +
		coeffs.tc[24]*Sin(2*D-M)*E +
		coeffs.tc[25]*Sin(MP+F) +
		coeffs.tc[26]*Sin(M-MP)*E +
		coeffs.tc[27]*Sin(MP-3*F) +
		coeffs.tc[28]*Sin(2*MP+F) +
		coeffs.tc[29]*Cos(2*(D-MP)-F) +
		coeffs.tc[30]*Sin(3*F) +
		coeffs.tc[31]*Cos(MP+3*F) +
		coeffs.tc[32]*Cos(2*MP) +
		coeffs.tc[33]*Cos(2*D-MP) +
		coeffs.tc[34]*Cos(2*D+MP+F) +
		coeffs.tc[35]*Cos(MP) +
		coeffs.tc[36]*Sin(3*MP+F) +
		coeffs.tc[37]*Sin(2*D-MP+F) +
		coeffs.tc[38]*Cos(2*(D-MP)) +
		coeffs.tc[39]*Cos(D+F) +
		coeffs.tc[40]*Sin(M+MP)*E +
		coeffs.tc[41]*Sin(2*(D-F)) +
		coeffs.tc[42]*Cos(2*MP+F) +
		coeffs.tc[43]*Cos(3*MP+F)
}
