package svg

import (
	"math"
	"time"

	"b612.me/astro/basic"
	eclipsecore "b612.me/astro/eclipse"
)

type SolarEclipseRadiusModel = eclipsecore.SolarEclipseRadiusModel

type SolarEclipseType = eclipsecore.SolarEclipseType

type LocalSolarEclipseContactPoint = eclipsecore.LocalSolarEclipseContactPoint

type LocalSolarEclipseInfo = eclipsecore.LocalSolarEclipseInfo

const (
	SolarEclipseModelIAUSingleK         = eclipsecore.SolarEclipseModelIAUSingleK
	SolarEclipseModelNASABulletinSplitK = eclipsecore.SolarEclipseModelNASABulletinSplitK

	SolarEclipseNone    = eclipsecore.SolarEclipseNone
	SolarEclipsePartial = eclipsecore.SolarEclipsePartial
	SolarEclipseAnnular = eclipsecore.SolarEclipseAnnular
	SolarEclipseTotal   = eclipsecore.SolarEclipseTotal
	SolarEclipseHybrid  = eclipsecore.SolarEclipseHybrid
)

func localSolarEclipseInfoFromDiagram(
	diagram basic.LocalSolarEclipseDiagramResult,
	lon, lat, height float64,
	location *time.Location,
) LocalSolarEclipseInfo {
	info := localSolarEclipseInfoFieldsFromBasic(diagram.Eclipse, lon, lat, height, location)
	info.ContactPoints = localSolarEclipseContactPointsFromFrames(diagram.Frames, location)
	return info
}

func localSolarEclipseInfoFieldsFromBasic(
	result basic.LocalSolarEclipseResult,
	lon, lat, height float64,
	location *time.Location,
) LocalSolarEclipseInfo {
	visibleThreshold := localSolarEclipseVisibilityThreshold(height, lat)
	return LocalSolarEclipseInfo{
		Model:             mapBasicSolarEclipseModel(result.Model),
		Type:              mapBasicSolarEclipseType(result.Type),
		Longitude:         lon,
		Latitude:          lat,
		Height:            height,
		GreatestEclipse:   solarEclipseTTJDEToTime(result.GreatestEclipse, location),
		PartialStart:      solarEclipseTTJDEToTime(result.PartialStart, location),
		PartialEnd:        solarEclipseTTJDEToTime(result.PartialEnd, location),
		CentralStart:      solarEclipseTTJDEToTime(result.CentralStart, location),
		CentralEnd:        solarEclipseTTJDEToTime(result.CentralEnd, location),
		Magnitude:         result.Magnitude,
		Obscuration:       result.Obscuration,
		Separation:        result.Separation,
		SunAltitude:       result.SunAltitude,
		SunAzimuth:        result.SunAzimuth,
		VisibleAtGreatest: result.SunAltitude > visibleThreshold,
		HasPartial:        result.HasPartial,
		HasCentral:        result.HasCentral,
		HasAnnular:        result.HasAnnular,
		HasTotal:          result.HasTotal,
	}
}

func localSolarEclipseContactPointsFromFrames(
	frames []basic.LocalSolarEclipseDiagramFrame,
	location *time.Location,
) []LocalSolarEclipseContactPoint {
	contacts := make([]LocalSolarEclipseContactPoint, 0, 4)
	for _, frame := range frames {
		for _, label := range localSolarEclipseFrameLabels(frame) {
			switch label {
			case "C1", "C2", "C3", "C4":
				contactPA := frame.PositionAngle
				if (label == "C2" || label == "C3") && frame.MoonRadius >= frame.SunRadius {
					contactPA = normalizeSolarEclipseDegree360(contactPA + 180)
				}
				contacts = append(contacts, LocalSolarEclipseContactPoint{
					Label:                   label,
					Time:                    solarEclipseTTJDEToTime(frame.JDE, location),
					ContactPositionAngle:    contactPA,
					ContactClockwiseAngle:   normalizeSolarEclipseDegree360(360 - contactPA),
					MoonCenterPositionAngle: frame.PositionAngle,
				})
			}
		}
	}
	return contacts
}

func localSolarEclipseFrameLabels(frame basic.LocalSolarEclipseDiagramFrame) []string {
	if len(frame.Labels) > 0 {
		return frame.Labels
	}
	if frame.Label == "" {
		return nil
	}
	return []string{frame.Label}
}

func mapBasicSolarEclipseModel(model basic.SolarEclipseRadiusModel) SolarEclipseRadiusModel {
	switch model {
	case basic.SolarEclipseModelIAUSingleK:
		return SolarEclipseModelIAUSingleK
	default:
		return SolarEclipseModelNASABulletinSplitK
	}
}

func mapBasicSolarEclipseType(eclipseType basic.SolarEclipseType) SolarEclipseType {
	switch eclipseType {
	case basic.SolarEclipsePartial:
		return SolarEclipsePartial
	case basic.SolarEclipseAnnular:
		return SolarEclipseAnnular
	case basic.SolarEclipseTotal:
		return SolarEclipseTotal
	case basic.SolarEclipseHybrid:
		return SolarEclipseHybrid
	default:
		return SolarEclipseNone
	}
}

func solarEclipseTTJDEToTime(ttJDE float64, location *time.Location) time.Time {
	if ttJDE == 0 {
		return time.Time{}
	}
	utcJDE := basic.TD2UT(ttJDE, false)
	return basic.JDE2DateByZone(utcJDE, location, false)
}

func solarEclipseTimeToTTJDE(date time.Time) float64 {
	utcJDE := basic.Date2JDE(date.UTC())
	return basic.TD2UT(utcJDE, true)
}

func localSolarEclipseVisibilityThreshold(height, latitude float64) float64 {
	if height <= 0 {
		return 0
	}
	return -basic.HeightDegreeByLat(height, latitude)
}

func normalizeSolarEclipseDegree360(angle float64) float64 {
	angle = math.Mod(angle, 360)
	if angle < 0 {
		angle += 360
	}
	return angle
}
