package basic

import (
	"math"

	. "b612.me/astro/tools"
)

const moonPhysicalInclinationDeg = 1.54242
const moonPhysicalAstronomicalUnitKM = 149597870.7

// MoonPhysicalInfo 月球物理观测参数 / physical observing parameters of the Moon.
type MoonPhysicalInfo struct {
	// OpticalLongitude 光学经度天平动，单位度 / optical libration in longitude, degrees.
	OpticalLongitude float64
	// OpticalLatitude 光学纬度天平动，单位度 / optical libration in latitude, degrees.
	OpticalLatitude float64
	// PhysicalLongitude 物理经度天平动，单位度 / physical libration in longitude, degrees.
	PhysicalLongitude float64
	// PhysicalLatitude 物理纬度天平动，单位度 / physical libration in latitude, degrees.
	PhysicalLatitude float64
	// LibrationLongitude 总经度天平动，单位度 / total libration in longitude, degrees.
	LibrationLongitude float64
	// LibrationLatitude 总纬度天平动，单位度 / total libration in latitude, degrees.
	LibrationLatitude float64
	// PositionAngle 月球自转轴位置角，单位度 / position angle of the lunar rotation axis, degrees.
	PositionAngle float64
}

// MoonPhysical 月球物理观测参数 / physical observing parameters of the Moon.
func MoonPhysical(jd float64) MoonPhysicalInfo {
	return MoonPhysicalN(jd, -1)
}

// MoonPhysicalN 月球物理观测参数（截断版） / truncated physical observing parameters of the Moon.
func MoonPhysicalN(jd float64, n int) MoonPhysicalInfo {
	return moonPhysicalNFromCoordinates(jd, n, HMoonApparentLoN(jd, n), HMoonTrueBoN(jd, n), HMoonTrueRaN(jd, n))
}

// MoonTopocentricPhysical 月球站心物理观测参数 / topocentric physical observing parameters of the Moon.
func MoonTopocentricPhysical(jd, observerLon, observerLat, height float64) MoonPhysicalInfo {
	return MoonTopocentricPhysicalN(jd, observerLon, observerLat, height, -1)
}

// MoonTopocentricPhysicalN 月球站心物理观测参数（截断版） / truncated topocentric physical observing parameters of the Moon.
func MoonTopocentricPhysicalN(jd, observerLon, observerLat, height float64, n int) MoonPhysicalInfo {
	lambda, beta, alpha := moonTopocentricPhysicalCoordinatesN(jd, observerLon, observerLat, height, n)
	return moonPhysicalNFromCoordinates(jd, n, lambda, beta, alpha)
}

func moonPhysicalNFromCoordinates(jd float64, n int, lambda, beta, alpha float64) MoonPhysicalInfo {
	t := (jd - 2451545.0) / 36525.0
	epsilon := TrueObliquity(jd)
	deltaPsi := Nutation2000Bi(jd)

	D := Limit360(SunMoonAngle(jd))
	sunMeanAnomaly := Limit360(SunM(jd))
	moonMeanAnomaly := Limit360(MoonM(jd))
	F := Limit360(MoonLonX(jd))
	omega := moonPhysicalMeanAscendingNode(t)
	E := 1 - 0.002516*t - 0.0000074*t*t
	K1 := 119.75 + 131.849*t
	K2 := 72.56 + 20.186*t

	W := Limit360(lambda - deltaPsi - omega)
	A := ArcTan2(Sin(W)*Cos(beta)*Cos(moonPhysicalInclinationDeg)-Sin(beta)*Sin(moonPhysicalInclinationDeg), Cos(W)*Cos(beta))
	opticalLongitude := wrapSignedAngle180(A - F)
	opticalLatitude := ArcSin(-Sin(W)*Cos(beta)*Sin(moonPhysicalInclinationDeg) - Sin(beta)*Cos(moonPhysicalInclinationDeg))

	rho, sigma, tau := moonPhysicalLibrationSeries(D, sunMeanAnomaly, moonMeanAnomaly, F, omega, E, K1, K2)
	physicalLongitude := -tau + (rho*Cos(A)+sigma*Sin(A))*Tan(opticalLatitude)
	physicalLatitude := sigma*Cos(A) - rho*Sin(A)

	librationLongitude := wrapSignedAngle180(opticalLongitude + physicalLongitude)
	librationLatitude := opticalLatitude + physicalLatitude

	V := Limit360(omega + deltaPsi + sigma/Sin(moonPhysicalInclinationDeg))
	X := Sin(moonPhysicalInclinationDeg+rho) * Sin(V)
	Y := Sin(moonPhysicalInclinationDeg+rho)*Cos(V)*Cos(epsilon) - Cos(moonPhysicalInclinationDeg+rho)*Sin(epsilon)
	littleOmega := ArcTan2(X, Y)
	positionAngle := ArcSin(clampUnit((sqrtXY(X, Y) * Cos(alpha-littleOmega)) / Cos(librationLatitude)))

	return MoonPhysicalInfo{
		OpticalLongitude:   opticalLongitude,
		OpticalLatitude:    opticalLatitude,
		PhysicalLongitude:  physicalLongitude,
		PhysicalLatitude:   physicalLatitude,
		LibrationLongitude: librationLongitude,
		LibrationLatitude:  librationLatitude,
		PositionAngle:      positionAngle,
	}
}

func moonTopocentricPhysicalCoordinatesN(jd, observerLon, observerLat, height float64, n int) (lambda, beta, alpha float64) {
	geocentricRA := HMoonTrueRaN(jd, n)
	geocentricDec := HMoonTrueDecN(jd, n)
	distanceAU := HMoonAwayN(jd, n) / moonPhysicalAstronomicalUnitKM
	utJD := TD2UT(jd, false)

	var topocentricDec float64
	alpha, topocentricDec = TopocentricRaDec(geocentricRA, geocentricDec, observerLat, observerLon, utJD, distanceAU, height)
	lambda, beta = RaDecToLoBo(jd, alpha, topocentricDec)
	return
}

func moonPhysicalLibrationSeries(D, M, MP, F, omega, E, K1, K2 float64) (rho, sigma, tau float64) {
	rho = -0.02752*Cos(MP) -
		0.02245*Sin(F) +
		0.00684*Cos(MP-2*F) -
		0.00293*Cos(2*F) -
		0.00085*Cos(2*F-2*D) -
		0.00054*Cos(MP-2*D) -
		0.00020*Sin(MP+F) -
		0.00020*Cos(MP+2*F) -
		0.00020*Cos(MP-F) +
		0.00014*Cos(MP+2*F-2*D)

	sigma = -0.02816*Sin(MP) +
		0.02244*Cos(F) -
		0.00682*Sin(MP-2*F) -
		0.00279*Sin(2*F) -
		0.00083*Sin(2*F-2*D) +
		0.00069*Sin(MP-2*D) +
		0.00040*Cos(MP+F) -
		0.00025*Sin(2*MP) -
		0.00023*Sin(MP+2*F) +
		0.00020*Cos(MP-F) +
		0.00019*Sin(MP-F) +
		0.00013*Sin(MP+2*F-2*D) -
		0.00010*Cos(MP-3*F)

	tau = 0.02520*E*Sin(M) +
		0.00473*Sin(2*MP-2*F) -
		0.00467*Sin(MP) +
		0.00396*Sin(K1) +
		0.00276*Sin(2*MP-2*D) +
		0.00196*Sin(omega) -
		0.00183*Cos(MP-F) +
		0.00115*Sin(MP-2*D) -
		0.00096*Sin(MP-D) +
		0.00046*Sin(2*F-2*D) -
		0.00039*Sin(MP-F) -
		0.00032*Sin(MP-M-D) +
		0.00027*Sin(2*MP-M-2*D) +
		0.00023*Sin(K2) -
		0.00014*Sin(2*D) +
		0.00014*Cos(2*MP-2*F) -
		0.00012*Sin(MP-2*F) -
		0.00012*Sin(2*MP) +
		0.00011*Sin(2*MP-2*M-2*D)
	return
}

func moonPhysicalMeanAscendingNode(t float64) float64 {
	return Limit360(125.04452222222222 - 1934.136261111111*t + 0.0020708333333333334*t*t + 0.0000022222222222222222*t*t*t)
}

func wrapSignedAngle180(angle float64) float64 {
	angle = Limit360(angle)
	if angle > 180 {
		angle -= 360
	}
	return angle
}

func sqrtXY(x, y float64) float64 {
	return math.Sqrt(x*x + y*y)
}
