package basic

import (
	"math"

	. "b612.me/astro/tools"
)

/*
 * 月球方位角
 */
func MoonAzimuth(jd, lon, lat, tz float64) float64 {

	//tmp := (tz*15 - lon) * 4 / 60
	calcjd := TD2UT(jd-tz/24, true)
	ra := MoonTrueRa(calcjd)
	dec := MoonTrueDec(calcjd)
	away := MoonAway(calcjd) / 149597870.7
	ndec := TopocentricDec(ra, dec, lat, lon, jd-tz/24, away, 0)
	nra := TopocentricRa(ra, dec, lat, lon, jd-tz/24, away, 0)
	calcjd = jd - tz/24
	st := Limit360(ApparentSiderealTime(calcjd)*15 + lon)
	hourAngle := Limit360(st - nra)
	tmp2 := Sin(hourAngle) / (Cos(hourAngle)*Sin(lat) - Tan(ndec)*Cos(lat))
	azimuth := ArcTan(tmp2)
	if azimuth < 0 {
		if hourAngle/15 < 12 {
			return azimuth + 360
		} else {
			return azimuth + 180
		}
	} else {
		if hourAngle/15 < 12 {
			return azimuth + 180
		} else {
			return azimuth
		}
	}

}

func MoonHeight(jd, lon, lat, tz float64) float64 {
	//	tmp := (tz*15 - lon) * 4 / 60
	//truejd=jd-tmp/24;
	calcjd := TD2UT(jd-tz/24, true)
	ra := MoonTrueRa(calcjd)
	dec := MoonTrueDec(calcjd)
	away := MoonAway(calcjd) / 149597870.7
	ndec := TopocentricDec(ra, dec, lat, lon, jd-tz/24, away, 0)
	nra := TopocentricRa(ra, dec, lat, lon, jd-tz/24, away, 0)
	calcjd = jd - tz/24
	st := Limit360(ApparentSiderealTime(calcjd)*15 + lon)
	hourAngle := Limit360(st - nra)
	tmp2 := Sin(lat)*Sin(ndec) + Cos(ndec)*Cos(lat)*Cos(hourAngle)
	return ArcSin(tmp2)
}

func HMoonAzimuth(jd, lon, lat, tz float64) float64 {
	return HMoonAzimuthN(jd, lon, lat, tz, -1)
}

func HMoonAzimuthN(jd, lon, lat, tz float64, n int) float64 {
	calcjd := TD2UT(jd-tz/24, true)
	ra := HMoonTrueRaN(calcjd, n)
	dec := HMoonTrueDecN(calcjd, n)
	away := HMoonAwayN(calcjd, n) / 149597870.7
	ndec := TopocentricDec(ra, dec, lat, lon, jd-tz/24, away, 0)
	nra := TopocentricRa(ra, dec, lat, lon, jd-tz/24, away, 0)
	calcjd = jd - tz/24
	st := Limit360(ApparentSiderealTime(calcjd)*15 + lon)
	hourAngle := Limit360(st - nra)
	tmp2 := Sin(hourAngle) / (Cos(hourAngle)*Sin(lat) - Tan(ndec)*Cos(lat))
	azimuth := ArcTan(tmp2)
	if azimuth < 0 {
		if hourAngle/15 < 12 {
			return azimuth + 360
		} else {
			return azimuth + 180
		}
	} else {
		if hourAngle/15 < 12 {
			return azimuth + 180
		} else {
			return azimuth
		}
	}
}
func HMoonHeight(jd, lon, lat, tz float64) float64 {
	return HMoonHeightN(jd, lon, lat, tz, -1)
}

func HMoonHeightN(jd, lon, lat, tz float64, n int) float64 {
	calcjd := TD2UT(jd-tz/24, true)
	ra, dec := HMoonTrueRaDecN(calcjd, n)
	away := HMoonAwayN(calcjd, n) / 149597870.7
	nra, ndec := TopocentricRaDec(ra, dec, lat, lon, calcjd, away, 0)
	calcjd = jd - tz/24
	st := Limit360(ApparentSiderealTime(calcjd)*15 + lon)
	hourAngle := Limit360(st - nra)
	tmp2 := Sin(lat)*Sin(ndec) + Cos(ndec)*Cos(lat)*Cos(hourAngle)
	return ArcSin(tmp2)
}

// 废弃
func GetMoonTZTime(jd, lon, lat, tz float64) float64 { //实际中天时间{
	jd = math.Floor(jd) + 0.5
	ttm := MoonTimeAngle(jd, lon, lat, tz)
	if ttm > 0 && ttm < 180 {
		jd += 0.5
	}
	estimateJD := jd
	for {
		prevJD := estimateJD
		stDegree := MoonTimeAngle(prevJD, lon, lat, tz) - 359.599
		stDegreep := (MoonTimeAngle(prevJD+0.000005, lon, lat, tz) - MoonTimeAngle(prevJD-0.000005, lon, lat, tz)) / 0.00001
		estimateJD = prevJD - stDegree/stDegreep
		if math.Abs(estimateJD-prevJD) <= 0.00001 {
			break
		}
	}
	return estimateJD
}

func MoonCulminationTime(jde, lon, lat, timezone float64) float64 {
	//jde 世界时，非力学时，当地时区 0时，无需转换力学时
	//ra,dec 瞬时天球座标，非J2000等时间天球坐标
	jde = math.Floor(jde) + 0.5
	estimateJD := jde + Limit360(360-MoonTimeAngle(jde, lon, lat, timezone))/15.0/24.0/0.9
	limitHA := func(jde, lon, timezone float64) float64 {
		ha := MoonTimeAngle(jde, lon, lat, timezone)
		if ha < 180 {
			ha += 360
		}
		return ha
	}
	for {
		prevJD := estimateJD
		stDegree := limitHA(prevJD, lon, timezone) - 360
		stDegreep := (limitHA(prevJD+0.000005, lon, timezone) - limitHA(prevJD-0.000005, lon, timezone)) / 0.00001
		estimateJD = prevJD - stDegree/stDegreep
		if math.Abs(estimateJD-prevJD) <= 0.00001 {
			break
		}
	}
	return estimateJD
}

func MoonTimeAngle(jd, lon, lat, tz float64) float64 {
	startime := Limit360(ApparentSiderealTime(jd-tz/24)*15 + lon)
	timeangle := startime - HMoonApparentRa(jd, lon, lat, tz)
	if timeangle < 0 {
		timeangle += 360
	}
	return timeangle
}

func GetMoonRiseTime(julianDay, longitude, latitude, timeZone, zenithShift, height float64) (float64, error) {
	originalTimeZone := timeZone
	timeZone = longitude / 15
	var timeToMeridian float64
	julianDayZero := math.Floor(julianDay) + 0.5
	//julianDay = math.Floor(julianDay) + 0.5 - originalTimeZone/24 + timeZone/24 // 求0时JDE
	//fix:这里时间分界线应当以传入的时区为准，不应当使用当地时区，否则在0时的判断会出错
	julianDay = math.Floor(julianDay) + 0.5
	estimatedTime := julianDay
	moonHeight := MoonHeight(julianDay, longitude, latitude, originalTimeZone) // 求此时月亮高度

	moonAngle := StandardAltitudeMoon(zenithShift, height, latitude)

	moonAngleTime := MoonTimeAngle(julianDay, longitude, latitude, originalTimeZone)

	if moonHeight-moonAngle > 0 { // 月亮在地平线上或在落下与下中天之间
		if moonAngleTime > 180 {
			timeToMeridian = (180 + 360 - moonAngleTime) / 15
		} else {
			timeToMeridian = (180 - moonAngleTime) / 15
		}
		estimatedTime += (timeToMeridian/24 + (timeToMeridian/24*12.0)/15.0/24.0)
	}

	if moonHeight-moonAngle < 0 && moonAngleTime > 180 {
		timeToMeridian = (180 - moonAngleTime) / 15
		estimatedTime += (timeToMeridian/24 + (timeToMeridian/24*12.0)/15.0/24.0)
	} else if moonHeight-moonAngle < 0 && moonAngleTime < 180 {
		timeToMeridian = (180 - moonAngleTime) / 15
		estimatedTime += (timeToMeridian/24 + (timeToMeridian/24*12.0)/15.0/24.0)
	}

	currentAngle := MoonTimeAngle(estimatedTime, longitude, latitude, timeZone)
	if math.Abs(currentAngle-180) > 0.5 {
		estimatedTime += (180 - currentAngle) * 4.0 / 60.0 / 24.0
	}

	currentHeight := HMoonHeight(estimatedTime, longitude, latitude, timeZone)
	if !(currentHeight < -10 && math.Abs(latitude) < 60) {
		if currentHeight > moonAngle {
			return 0, ErrNeverSet
		}
		checkTime := estimatedTime + 12.0/24.0 + 6.0/15.0/24.0
		checkAngle := MoonTimeAngle(checkTime, longitude, latitude, timeZone)
		if checkAngle < 90 {
			checkAngle += 360
		}
		checkTime += (360 - checkAngle) * 4.0 / 60.0 / 24.0
		if HMoonHeight(checkTime, longitude, latitude, timeZone) < moonAngle {
			return 0, ErrNeverRise
		}
	}

	moonDeclination := MoonApparentDec(estimatedTime, longitude, latitude, timeZone)
	tmp := (Sin(moonAngle) - Sin(moonDeclination)*Sin(latitude)) / (Cos(moonDeclination) * Cos(latitude))

	if math.Abs(tmp) <= 1 && latitude < 85 {
		hourAngle := (180 - ArcCos(tmp)) / 15
		estimatedTime += hourAngle/24.00 + hourAngle/33.00/15.00
	} else {
		i := 0
		for MoonHeight(estimatedTime, longitude, latitude, timeZone) < moonAngle {
			i++
			estimatedTime += 15.0 / 60.0 / 24.0
			if i > 48 {
				break
			}
		}
	}

	// 使用牛顿迭代法求精确解
	estimatedTime = moonRiseSetNewtonRaphsonIteration(estimatedTime, longitude, latitude, timeZone, moonAngle, HMoonHeight, 0.00002)

	estimatedTime = estimatedTime - timeZone/24 + originalTimeZone/24

	if estimatedTime > julianDayZero+1 || estimatedTime < julianDayZero {
		return 0, ErrNotOnThisDate
	}
	return estimatedTime, nil
}

func GetMoonSetTime(julianDay, longitude, latitude, timeZone, zenithShift, height float64) (float64, error) {
	originalTimeZone := timeZone
	timeZone = longitude / 15
	var timeToMeridian float64
	julianDayZero := math.Floor(julianDay) + 0.5
	//julianDay = math.Floor(julianDay) + 0.5 - originalTimeZone/24 + timeZone/24 // 求0时JDE
	//fix:这里时间分界线应当以传入的时区为准，不应当使用当地时区，否则在0时的判断会出错
	julianDay = math.Floor(julianDay) + 0.5
	estimatedTime := julianDay
	moonHeight := MoonHeight(julianDay, longitude, latitude, originalTimeZone) // 求此时月亮高度

	moonAngle := StandardAltitudeMoon(zenithShift, height, latitude)

	moonAngleTime := MoonTimeAngle(julianDay, longitude, latitude, originalTimeZone)

	if moonHeight-moonAngle < 0 {
		timeToMeridian = (360 - moonAngleTime) / 15
		estimatedTime += (timeToMeridian/24 + (timeToMeridian/24.0*12.0)/15.0/24.0)
	}

	// 月亮在地平线上或在落下与下中天之间
	if moonHeight-moonAngle > 0 && moonAngleTime < 180 {
		timeToMeridian = (-moonAngleTime) / 15
		estimatedTime += (timeToMeridian/24.0 + (timeToMeridian/24.0*12.0)/15.0/24.0)
	} else if moonHeight-moonAngle > 0 {
		timeToMeridian = (360 - moonAngleTime) / 15
		estimatedTime += (timeToMeridian/24.0 + (timeToMeridian/24.0*12.0)/15.0/24.0)
	}

	currentAngle := MoonTimeAngle(estimatedTime, longitude, latitude, timeZone)
	if currentAngle < 180 {
		currentAngle += 360
	}
	if math.Abs(currentAngle-360) > 0.5 {
		estimatedTime += (360 - currentAngle) * 4.0 / 60.0 / 24.0
	}

	// estimatedTime = 月球中天时间
	currentHeight := HMoonHeight(estimatedTime, longitude, latitude, timeZone)
	if !(currentHeight > 10 && math.Abs(latitude) < 60) {
		if currentHeight < moonAngle {
			return 0, ErrNeverRise
		}
		checkTime := estimatedTime + 12.0/24.0 + 6.0/15.0/24.0
		angleSubtraction := 180 - MoonTimeAngle(checkTime, longitude, latitude, timeZone)
		checkTime += angleSubtraction * 4.0 / 60.0 / 24.0
		if HMoonHeight(checkTime, longitude, latitude, timeZone) > moonAngle {
			return 0, ErrNeverSet
		}
	}

	moonDeclination := MoonApparentDec(estimatedTime, longitude, latitude, timeZone)
	tmp := (Sin(moonAngle) - Sin(moonDeclination)*Sin(latitude)) / (Cos(moonDeclination) * Cos(latitude))

	if math.Abs(tmp) <= 1 && latitude < 85 {
		hourAngle := (ArcCos(tmp)) / 15.0
		estimatedTime += hourAngle/24 + hourAngle/33.0/15.0
	} else {
		i := 0
		for MoonHeight(estimatedTime, longitude, latitude, timeZone) > moonAngle {
			i++
			estimatedTime += 15.0 / 60.0 / 24.0
			if i > 48 {
				break
			}
		}
	}

	// 使用牛顿迭代法求精确解
	estimatedTime = moonRiseSetNewtonRaphsonIteration(estimatedTime, longitude, latitude, timeZone, moonAngle, HMoonHeight, 0.00002)

	estimatedTime = estimatedTime - timeZone/24 + originalTimeZone/24

	if estimatedTime > julianDayZero+1 || estimatedTime < julianDayZero {
		return 0, ErrNotOnThisDate
	}
	return estimatedTime, nil
}

// heightFunction 高度函数类型定义，用于牛顿迭代法
type heightFunction func(time, longitude, latitude, timeZone float64) float64

// moonRiseSetNewtonRaphsonIteration 牛顿-拉夫逊迭代法求解天体高度方程
func moonRiseSetNewtonRaphsonIteration(initialTime, longitude, latitude, timeZone, targetAngle float64,
	heightFunc heightFunction, tolerance float64) float64 {
	const derivativeStep = 0.000005

	currentTime := initialTime

	for {
		previousTime := currentTime

		// 计算函数值：f(t) = height(t) - targetAngle
		functionValue := heightFunc(previousTime, longitude, latitude, timeZone) - targetAngle

		// 计算导数：f'(t) ≈ (f(t+h) - f(t-h)) / (2h)
		derivative := (heightFunc(previousTime+derivativeStep, longitude, latitude, timeZone) -
			heightFunc(previousTime-derivativeStep, longitude, latitude, timeZone)) / (2 * derivativeStep)

		// 牛顿-拉夫逊公式：t_new = t_old - f(t) / f'(t)
		currentTime = previousTime - functionValue/derivative

		// 检查收敛
		if math.Abs(currentTime-previousTime) <= tolerance {
			break
		}
	}

	return currentTime
}
