package basic

import (
	"math"

	"b612.me/astro/planet"
	. "b612.me/astro/tools"
)

const (
	VENUS_S_PERIOD    = 1 / ((1 / 224.701) - (1 / 365.256363004))
	venusEventSearchN = 16
)

func venusSunLongitudeDelta(jde float64) float64 {
	sub := Limit360(VenusApparentLo(jde) - HSunApparentLo(jde))
	if sub > 180 {
		sub -= 360
	}
	if sub < -180 {
		sub += 360
	}
	return sub
}

func venusSunLongitudeDeltaN(jde float64, n int) float64 {
	sub := Limit360(VenusApparentLoN(jde, n) - HSunApparentLoN(jde, n))
	if sub > 180 {
		sub -= 360
	}
	if sub < -180 {
		sub += 360
	}
	return sub
}

func venusConjunctionAngleDelta(diff float64) float64 {
	diff = Limit360(diff)
	if diff > 180 {
		diff -= 360
	}
	if diff < -180 {
		diff += 360
	}
	return diff
}

func venusConjunctionHeliocentricDelta(jd, targetDeg float64, n int) float64 {
	planetLo := planet.WherePlanetN(2, 0, jd, n)
	earthLo := planet.WherePlanetN(-1, 0, jd, n)
	return venusConjunctionAngleDelta(planetLo - earthLo - targetDeg)
}

func venusSunRADelta(jde float64) float64 {
	sub := Limit360(VenusApparentRa(jde) - SunApparentRa(jde))
	if sub > 180 {
		sub -= 360
	}
	if sub < -180 {
		sub += 360
	}
	return sub
}

func venusRADerivative(jde, val float64) float64 {
	sub := VenusApparentRa(jde+val) - VenusApparentRa(jde-val)
	if sub > 180 {
		sub -= 360
	}
	if sub < -180 {
		sub += 360
	}
	return sub / (2 * val)
}

func venusRADerivativeN(jde, val float64, n int) float64 {
	sub := VenusApparentRaN(jde+val, n) - VenusApparentRaN(jde-val, n)
	if sub > 180 {
		sub -= 360
	}
	if sub < -180 {
		sub += 360
	}
	return sub / (2 * val)
}

func venusRAContinuousForMax(jde float64) float64 {
	ra := VenusApparentRa(jde)
	if ra < 180 {
		return ra + 360
	}
	return ra
}

func venusRAContinuousForMaxN(jde float64, n int) float64 {
	ra := VenusApparentRaN(jde, n)
	if ra < 180 {
		return ra + 360
	}
	return ra
}

func venusRAContinuousForMin(jde float64) float64 {
	ra := VenusApparentRa(jde)
	if ra > 180 {
		return ra - 360
	}
	return ra
}

func venusRAContinuousForMinN(jde float64, n int) float64 {
	ra := VenusApparentRaN(jde, n)
	if ra > 180 {
		return ra - 360
	}
	return ra
}

func venusRAExtremumRefine(seed, start, end, step float64, fn func(float64) float64) float64 {
	centerJD := clampFloat64(seed, start, end)
	halfStep := step
	bestJD := centerJD
	bestVal := fn(centerJD)
	for i := 0; i < 8; i++ {
		leftJD := clampFloat64(centerJD-halfStep, start, end)
		rightJD := clampFloat64(centerJD+halfStep, start, end)
		leftVal := fn(leftJD)
		centerVal := fn(centerJD)
		rightVal := fn(rightJD)
		if leftVal > bestVal {
			bestVal = leftVal
			bestJD = leftJD
		}
		if centerVal > bestVal {
			bestVal = centerVal
			bestJD = centerJD
		}
		if rightVal > bestVal {
			bestVal = rightVal
			bestJD = rightJD
		}
		denominator := leftVal - 2*centerVal + rightVal
		if denominator == 0 {
			centerJD = bestJD
			halfStep /= 2
			continue
		}
		vertexJD := centerJD + 0.5*halfStep*(leftVal-rightVal)/denominator
		vertexJD = clampFloat64(vertexJD, leftJD, rightJD)
		vertexVal := fn(vertexJD)
		if vertexVal > bestVal {
			bestVal = vertexVal
			bestJD = vertexJD
		}
		centerJD = bestJD
		halfStep /= 2
	}
	return bestJD
}

func venusSunElongationN(jde float64, n int) float64 {
	lo1, bo1 := VenusApparentLoBoN(jde, n)
	lo2 := HSunApparentLoN(jde, n)
	bo2 := HSunTrueBoN(jde, n)
	return StarAngularSeparation(lo1, bo1, lo2, bo2)
}

func venusTrueElongationN(jde float64, n int) float64 {
	earth := mercuryHelioN(-1, jde, n)
	planetPos := mercuryHelioN(2, jde, n)
	geo := mercuryGeocentric(planetPos, earth)
	return StarAngularSeparation(geo.lo, geo.bo, HSunTrueLoN(jde, n), HSunTrueBoN(jde, n))
}

func venusElongationDerivative(jde, val float64) float64 {
	sub := VenusSunElongation(jde+val) - VenusSunElongation(jde-val)
	if sub > 180 {
		sub -= 360
	}
	if sub < -180 {
		sub += 360
	}
	return sub / (2 * val)
}

func venusElongationDerivativeN(jde, val float64, n int) float64 {
	sub := venusSunElongationN(jde+val, n) - venusSunElongationN(jde-val, n)
	if sub > 180 {
		sub -= 360
	}
	if sub < -180 {
		sub += 360
	}
	return sub / (2 * val)
}

func venusConjunction(jde float64, next uint8) float64 {
	if !isFiniteFloat(jde) {
		return math.NaN()
	}
	queryTT := jde
	direction := -1.0
	if next == 1 {
		direction = 1
	}
	left := queryTT
	leftVal := venusSunLongitudeDeltaN(left, venusEventSearchN)
	if math.Abs(venusSunLongitudeDelta(queryTT)) <= 30.0/86400.0 {
		if exact, ok := venusConjunctionRefine(left, 1.0); ok {
			eventUT := TD2UT(exact, false)
			if next == 0 && eventUTQueryBeforeOrEqual(eventUT, queryTT) {
				return eventUT
			}
			if next == 1 && eventUTQueryAfterOrEqual(eventUT, queryTT) {
				return eventUT
			}
		}
	}
	const step = 8.0
	for i := 0; i < 80; i++ {
		right := queryTT + direction*step*float64(i+1)
		rightVal := venusSunLongitudeDeltaN(right, venusEventSearchN)
		if leftVal == 0 || rightVal == 0 || leftVal*rightVal <= 0 {
			center := (left + right) / 2.0
			halfWindow := math.Abs(right-left) / 2.0
			if exact, ok := venusConjunctionRefine(center, halfWindow); ok {
				return TD2UT(exact, false)
			}
			return math.NaN()
		}
		left = right
		leftVal = rightVal
	}
	// 640 天已经覆盖一个金星会合周期；仍无括号通常表示输入超出解析项的可靠范围。
	// 继续按 5 微日扫描整个周期会产生数亿次星历计算，因此在这里有界失败。
	// The 640-day directional scan already exceeds one Venus synodic period. If it
	// still finds no bracket, fail in a bounded way instead of scanning hundreds
	// of millions of five-microday samples across the full fallback window.
	return math.NaN()
}

func venusConjunctionRefine(seed, halfWindow float64) (float64, bool) {
	leftJD := seed - halfWindow
	centerJD := seed
	rightJD := seed + halfWindow
	leftVal := venusSunLongitudeDelta(leftJD)
	centerVal := venusSunLongitudeDelta(centerJD)
	rightVal := venusSunLongitudeDelta(rightJD)
	if !isFiniteFloat(leftVal) || !isFiniteFloat(centerVal) || !isFiniteFloat(rightVal) {
		return math.NaN(), false
	}
	if _, _, _, _, ok := eventZeroBracket(leftJD, leftVal, centerJD, centerVal, rightJD, rightVal); !ok {
		return math.NaN(), false
	}
	return eventZeroRefine(seed, halfWindow, 0.000005, venusSunLongitudeDelta), true
}

func venusConjunctionTypeAt(eventUT float64) bool {
	return EarthVenusAway(eventUT) <= EarthAway(eventUT)
}

func nextVenusTypedConjunctionFromEvent(jde float64, inferior bool) float64 {
	date := NextVenusConjunctionStrict(jde)
	if venusConjunctionTypeAt(date) == inferior {
		return date
	}
	return NextVenusConjunctionStrict(eventUTNextQueryTT(date))
}

func lastVenusTypedConjunctionFromEvent(jde float64, inferior bool) float64 {
	date := LastVenusConjunctionStrict(jde)
	if venusConjunctionTypeAt(date) == inferior {
		return date
	}
	return LastVenusConjunctionStrict(eventUTLastQueryTT(date))
}

func LastVenusConjunction(jde float64) float64 {
	return inclusiveLastSimpleEvent(jde, LastVenusConjunctionStrict, NextVenusConjunctionStrict)
}

func NextVenusConjunction(jde float64) float64 {
	return inclusiveNextSimpleEvent(jde, LastVenusConjunctionStrict, NextVenusConjunctionStrict)
}

func LastVenusConjunctionStrict(jde float64) float64 {
	return venusConjunction(jde, 0)
}

func NextVenusConjunctionStrict(jde float64) float64 {
	return venusConjunction(jde, 1)
}

func nextVenusTypedConjunction(jde float64, inferior bool) float64 {
	return nextVenusTypedConjunctionFromEvent(jde, inferior)
}

func lastVenusTypedConjunction(jde float64, inferior bool) float64 {
	return lastVenusTypedConjunctionFromEvent(jde, inferior)
}

func NextVenusInferiorConjunction(jde float64) float64 {
	return nextVenusTypedConjunction(jde, true)
}

func NextVenusSuperiorConjunction(jde float64) float64 {
	return nextVenusTypedConjunction(jde, false)
}

func LastVenusInferiorConjunction(jde float64) float64 {
	return lastVenusTypedConjunction(jde, true)
}

func LastVenusSuperiorConjunction(jde float64) float64 {
	return lastVenusTypedConjunction(jde, false)
}

func venusRetrograde(jde float64) float64 {
	//0=last 1=next
	if !isFiniteFloat(jde) {
		return math.NaN()
	}
	lastHe := LastVenusConjunctionStrict(jde)
	nextHe := NextVenusConjunctionStrict(jde)
	nowSub := venusSunRADelta(jde)
	if nowSub > 0 {
		jde = lastHe + ((nextHe - lastHe) / 5.0 * 3.5)
	} else {
		jde = lastHe + 10
	}
	found := false
	for i := 0; i < eventDirectionalSearchIterations; i++ {
		nowSub := venusRADerivativeN(jde, 1.0/86400.0, venusEventSearchN)
		if !isFiniteFloat(nowSub) {
			return math.NaN()
		}
		if math.Abs(nowSub) > 0.5 {
			jde += 5
			continue
		}
		found = true
		break
	}
	if !found {
		return math.NaN()
	}
	JD1 := jde
	var ok bool
	JD1, ok = eventNewtonRefine(JD1, 20.0/86400.0, func(JD0 float64) float64 {
		stDegree := venusRADerivative(JD0, 0.5/86400.0)
		stDegreep := (venusRADerivative(JD0+10.0/86400.0, 0.5/86400.0) - venusRADerivative(JD0-10.0/86400.0, 0.5/86400.0)) / (20.0 / 86400.0)
		return stDegree / stDegreep
	})
	if !ok {
		return math.NaN()
	}
	min := eventZeroRefine(JD1, 10.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
		return venusRADerivative(jd, 0.5/86400.0)
	})
	//fmt.Println((min - lastHe) / (nextHe - lastHe))
	return TD2UT(min, false)
}

func NextVenusRetrograde(jde float64) float64 {
	p2r := NextVenusProgradeToRetrograde(jde)
	r2p := NextVenusRetrogradeToPrograde(jde)
	if sameEventJD(p2r, r2p) {
		return p2r
	}
	if p2r < r2p {
		return p2r
	}
	return r2p
}

func LastVenusRetrograde(jde float64) float64 {
	p2r := LastVenusProgradeToRetrograde(jde)
	r2p := LastVenusRetrogradeToPrograde(jde)
	if sameEventJD(p2r, r2p) {
		return p2r
	}
	if p2r > r2p {
		return p2r
	}
	return r2p
}

func venusStationInWindow(start, end float64, progradeToRetrograde bool) float64 {
	var best float64
	if progradeToRetrograde {
		guess := scanWindowForMax(start, end, 2.0, func(jd float64) float64 {
			return venusRAContinuousForMaxN(jd, venusEventSearchN)
		})
		best = venusRAExtremumRefine(guess, start, end, 1.0, func(jd float64) float64 {
			return venusRAContinuousForMax(jd)
		})
	} else {
		guess := scanWindowForMax(start, end, 2.0, func(jd float64) float64 {
			return -venusRAContinuousForMinN(jd, venusEventSearchN)
		})
		best = venusRAExtremumRefine(guess, start, end, 1.0, func(jd float64) float64 {
			return -venusRAContinuousForMin(jd)
		})
	}
	return TD2UT(best, false)
}

func venusProgradeToRetrogradeAroundInferior(inferior float64) float64 {
	return venusStationInWindow(inferior-30.0, inferior-14.0, true)
}

func venusRetrogradeToProgradeAroundInferior(inferior float64) float64 {
	return venusStationInWindow(inferior+14.0, inferior+24.0, false)
}

func NextVenusProgradeToRetrograde(jde float64) float64 {
	inferior := NextVenusInferiorConjunction(jde)
	for i := 0; i < eventDirectionalSearchIterations; i++ {
		date := venusProgradeToRetrogradeAroundInferior(inferior)
		if !isFiniteFloat(date) {
			return math.NaN()
		}
		if eventUTQueryAfterOrEqual(date, jde) {
			return date
		}
		inferior = NextVenusInferiorConjunction(eventUTNextQueryTT(inferior))
	}
	return math.NaN()
}

func NextVenusRetrogradeToPrograde(jde float64) float64 {
	inferior := LastVenusInferiorConjunction(jde)
	for i := 0; i < eventDirectionalSearchIterations; i++ {
		date := venusRetrogradeToProgradeAroundInferior(inferior)
		if !isFiniteFloat(date) {
			return math.NaN()
		}
		if eventUTQueryAfterOrEqual(date, jde) {
			return date
		}
		inferior = NextVenusInferiorConjunction(eventUTNextQueryTT(inferior))
	}
	return math.NaN()
}

func LastVenusProgradeToRetrograde(jde float64) float64 {
	inferior := NextVenusInferiorConjunction(jde)
	for i := 0; i < eventDirectionalSearchIterations; i++ {
		date := venusProgradeToRetrogradeAroundInferior(inferior)
		if !isFiniteFloat(date) {
			return math.NaN()
		}
		if eventUTQueryBeforeOrEqual(date, jde) {
			return date
		}
		inferior = LastVenusInferiorConjunction(eventUTLastQueryTT(inferior))
	}
	return math.NaN()
}

func LastVenusRetrogradeToPrograde(jde float64) float64 {
	inferior := LastVenusInferiorConjunction(jde)
	for i := 0; i < eventDirectionalSearchIterations; i++ {
		date := venusRetrogradeToProgradeAroundInferior(inferior)
		if !isFiniteFloat(date) {
			return math.NaN()
		}
		if eventUTQueryBeforeOrEqual(date, jde) {
			return date
		}
		inferior = LastVenusInferiorConjunction(eventUTLastQueryTT(inferior))
	}
	return math.NaN()
}

func VenusSunElongation(jde float64) float64 {
	lo1, bo1 := VenusApparentLoBo(jde)
	lo2 := HSunApparentLo(jde)
	bo2 := HSunTrueBo(jde)
	return StarAngularSeparation(lo1, bo1, lo2, bo2)
}

func venusGreatestElongationInWindow(start, end float64) float64 {
	best := maximizeInWindow(start, end, 5.0, func(jd float64) float64 {
		return venusTrueElongationN(jd, venusEventSearchN)
	}, func(jd float64) float64 {
		return venusTrueElongationN(jd, -1)
	})
	return TD2UT(best, false)
}

func venusEastElongationWindowEndingAt(inferior float64) (float64, float64) {
	lastSuperior := LastVenusSuperiorConjunction(eventUTLastQueryTT(inferior))
	return lastSuperior + innerEventWindowPadding, inferior - innerEventWindowPadding
}

func venusWestElongationWindowEndingAt(superior float64) (float64, float64) {
	lastInferior := LastVenusInferiorConjunction(eventUTLastQueryTT(superior))
	return lastInferior + innerEventWindowPadding, superior - innerEventWindowPadding
}

func venusEastElongationWindowContaining(jde float64) (float64, float64) {
	nextInferior := NextVenusInferiorConjunction(jde)
	start, end := venusEastElongationWindowEndingAt(nextInferior)
	if eventUTQueryBeforeOrEqual(start, jde) && eventUTQueryAfterOrEqual(end, jde) {
		return start, end
	}
	currentInferior := LastVenusInferiorConjunction(jde)
	return venusEastElongationWindowEndingAt(currentInferior)
}

func venusWestElongationWindowContaining(jde float64) (float64, float64) {
	nextSuperior := NextVenusSuperiorConjunction(jde)
	start, end := venusWestElongationWindowEndingAt(nextSuperior)
	if eventUTQueryBeforeOrEqual(start, jde) && eventUTQueryAfterOrEqual(end, jde) {
		return start, end
	}
	currentSuperior := LastVenusSuperiorConjunction(jde)
	return venusWestElongationWindowEndingAt(currentSuperior)
}

func nextVenusGreatestElongationTyped(jde float64, east bool) float64 {
	if !isFiniteFloat(jde) {
		return math.NaN()
	}
	if east {
		start, windowEnd := venusEastElongationWindowContaining(jde)
		for i := 0; i < eventDirectionalSearchIterations; i++ {
			date := venusGreatestElongationInWindow(start, windowEnd)
			if !isFiniteFloat(date) {
				return math.NaN()
			}
			if eventUTQueryAfterOrEqual(date, jde) {
				return date
			}
			nextInferior := NextVenusInferiorConjunction(eventUTNextQueryTT(windowEnd))
			start, windowEnd = venusEastElongationWindowEndingAt(nextInferior)
		}
		return math.NaN()
	}
	start, windowEnd := venusWestElongationWindowContaining(jde)
	for i := 0; i < eventDirectionalSearchIterations; i++ {
		date := venusGreatestElongationInWindow(start, windowEnd)
		if !isFiniteFloat(date) {
			return math.NaN()
		}
		if eventUTQueryAfterOrEqual(date, jde) {
			return date
		}
		nextSuperior := NextVenusSuperiorConjunction(eventUTNextQueryTT(windowEnd))
		start, windowEnd = venusWestElongationWindowEndingAt(nextSuperior)
	}
	return math.NaN()
}

func lastVenusGreatestElongationTyped(jde float64, east bool) float64 {
	if !isFiniteFloat(jde) {
		return math.NaN()
	}
	if east {
		start, windowEnd := venusEastElongationWindowContaining(jde)
		for i := 0; i < eventDirectionalSearchIterations; i++ {
			date := venusGreatestElongationInWindow(start, windowEnd)
			if !isFiniteFloat(date) {
				return math.NaN()
			}
			if eventUTQueryBeforeOrEqual(date, jde) {
				return date
			}
			prevInferior := LastVenusInferiorConjunction(eventUTLastQueryTT(start))
			start, windowEnd = venusEastElongationWindowEndingAt(prevInferior)
		}
		return math.NaN()
	}
	start, windowEnd := venusWestElongationWindowContaining(jde)
	for i := 0; i < eventDirectionalSearchIterations; i++ {
		date := venusGreatestElongationInWindow(start, windowEnd)
		if !isFiniteFloat(date) {
			return math.NaN()
		}
		if eventUTQueryBeforeOrEqual(date, jde) {
			return date
		}
		prevSuperior := LastVenusSuperiorConjunction(eventUTLastQueryTT(start))
		start, windowEnd = venusWestElongationWindowEndingAt(prevSuperior)
	}
	return math.NaN()
}

func venusGreatestElongation(jde float64) float64 {
	if !isFiniteFloat(jde) {
		return math.NaN()
	}
	east := venusSunRADelta(jde) > 0
	if east {
		return nextVenusGreatestElongationTyped(jde, true)
	}
	return nextVenusGreatestElongationTyped(jde, false)
}

func NextVenusGreatestElongation(jde float64) float64 {
	east := NextVenusGreatestElongationEast(jde)
	west := NextVenusGreatestElongationWest(jde)
	if sameEventJD(east, west) {
		return east
	}
	if east < west {
		return east
	}
	return west
}

func LastVenusGreatestElongation(jde float64) float64 {
	east := LastVenusGreatestElongationEast(jde)
	west := LastVenusGreatestElongationWest(jde)
	if sameEventJD(east, west) {
		return east
	}
	if east > west {
		return east
	}
	return west
}

func LastVenusInferiorConjunctionInclusive(jde float64) float64 {
	return inclusiveLastSimpleEvent(jde, LastVenusInferiorConjunction, NextVenusInferiorConjunction)
}

func NextVenusInferiorConjunctionInclusive(jde float64) float64 {
	return inclusiveNextSimpleEvent(jde, LastVenusInferiorConjunction, NextVenusInferiorConjunction)
}

func LastVenusSuperiorConjunctionInclusive(jde float64) float64 {
	return inclusiveLastSimpleEvent(jde, LastVenusSuperiorConjunction, NextVenusSuperiorConjunction)
}

func NextVenusSuperiorConjunctionInclusive(jde float64) float64 {
	return inclusiveNextSimpleEvent(jde, LastVenusSuperiorConjunction, NextVenusSuperiorConjunction)
}

func LastVenusRetrogradeInclusive(jde float64) float64 {
	return inclusiveLastSimpleEvent(jde, LastVenusRetrograde, NextVenusRetrograde)
}

func NextVenusRetrogradeInclusive(jde float64) float64 {
	return inclusiveNextSimpleEvent(jde, LastVenusRetrograde, NextVenusRetrograde)
}

func LastVenusProgradeToRetrogradeInclusive(jde float64) float64 {
	return inclusiveLastSimpleEvent(jde, LastVenusProgradeToRetrograde, NextVenusProgradeToRetrograde)
}

func NextVenusProgradeToRetrogradeInclusive(jde float64) float64 {
	return inclusiveNextSimpleEvent(jde, LastVenusProgradeToRetrograde, NextVenusProgradeToRetrograde)
}

func LastVenusRetrogradeToProgradeInclusive(jde float64) float64 {
	return inclusiveLastSimpleEvent(jde, LastVenusRetrogradeToPrograde, NextVenusRetrogradeToPrograde)
}

func NextVenusRetrogradeToProgradeInclusive(jde float64) float64 {
	return inclusiveNextSimpleEvent(jde, LastVenusRetrogradeToPrograde, NextVenusRetrogradeToPrograde)
}

func LastVenusGreatestElongationInclusive(jde float64) float64 {
	return inclusiveLastSimpleEvent(jde, LastVenusGreatestElongation, NextVenusGreatestElongation)
}

func NextVenusGreatestElongationInclusive(jde float64) float64 {
	return inclusiveNextSimpleEvent(jde, LastVenusGreatestElongation, NextVenusGreatestElongation)
}

func LastVenusGreatestElongationEastInclusive(jde float64) float64 {
	return inclusiveLastSimpleEvent(jde, LastVenusGreatestElongationEast, NextVenusGreatestElongationEast)
}

func NextVenusGreatestElongationEastInclusive(jde float64) float64 {
	return inclusiveNextSimpleEvent(jde, LastVenusGreatestElongationEast, NextVenusGreatestElongationEast)
}

func LastVenusGreatestElongationWestInclusive(jde float64) float64 {
	return inclusiveLastSimpleEvent(jde, LastVenusGreatestElongationWest, NextVenusGreatestElongationWest)
}

func NextVenusGreatestElongationWestInclusive(jde float64) float64 {
	return inclusiveNextSimpleEvent(jde, LastVenusGreatestElongationWest, NextVenusGreatestElongationWest)
}

func NextVenusGreatestElongationEast(jde float64) float64 {
	return nextVenusGreatestElongationTyped(jde, true)
}

func NextVenusGreatestElongationWest(jde float64) float64 {
	return nextVenusGreatestElongationTyped(jde, false)
}

func LastVenusGreatestElongationEast(jde float64) float64 {
	return lastVenusGreatestElongationTyped(jde, true)
}

func LastVenusGreatestElongationWest(jde float64) float64 {
	return lastVenusGreatestElongationTyped(jde, false)
}
