package basic

import (
	"bytes"
	"errors"
	"fmt"
	"math"
	"strconv"
	"strings"
	"sync"
	"time"
)

// 本文件包含约 9100 颗亮星 / this file contains bright 9100 stars
// 9100颗亮星列表

type InnerStarData struct {
	HR     uint16  //Bright Star Number;[1/9110]+ Harvard Revised Number;亮星编号
	Name   string  //Name, generally Bayer(如天狼星：Alpha CMA) and/or Flamsteed(如天狼星：9 CMA) name
	HD     uint32  //Henry Draper Catalog Number;HD星表编号
	Ra     float64 //Ra J2000;J2000历元赤经
	Dec    float64 //De J2000;J2000历元赤纬
	Mag    float64 //视星等
	PmRA   float64 //赤经投影年自行 cos(赤纬)*d赤经/dt，单位角秒/年
	PmDec  float64 //赤纬年自行，单位角秒/年
	RadVel float64 //径向速度 km/s
	RotVel float64 //自行速度 km/s
	Pc     float64 //秒差距
	HIP    uint32  //HIP星表编号
}
type StarData struct {
	InnerStarData
	ChineseName      string
	ChineseAlias     string
	ChineseBayerName string
	CommonName       string
	CommonAliasName  string
	Cst              string
	CstChinese       string
}

func parseStarData(star []byte) (InnerStarData, error) {
	var err error
	var stardata InnerStarData
	if len(star) < 160 {
		return stardata, errors.New("invalid stardat")
	}
	for i := 0; i < 4; i++ {
		if star[i] == ' ' {
			continue
		}
		stardata.HR = stardata.HR*10 + uint16(star[i]-48)
	}
	stardata.Name = string(bytes.TrimSpace(star[4:14]))
	for i := 25; i < 31; i++ {
		if star[i] == ' ' {
			continue
		}
		stardata.HD = stardata.HD*10 + uint32(star[i]-48)
	}
	stardata.Ra, err = parseRa(star)
	if err != nil {
		return stardata, fmt.Errorf("parse ra failed:%v", err)
	}
	stardata.Dec, err = parseDec(star)
	if err != nil {
		return stardata, fmt.Errorf("parse dec failed:%v", err)
	}
	magOri := string(bytes.TrimSpace(star[102:107]))
	if magOri != "" {
		stardata.Mag, err = strconv.ParseFloat(magOri, 64)
		if err != nil {
			return stardata, fmt.Errorf("parse mag failed:%v", err)
		}
	} else {
		stardata.Mag = 9999.9999
	}
	stardata.PmRA, _ = strconv.ParseFloat(string(bytes.TrimSpace(star[148:154])), 64)
	stardata.PmDec, _ = strconv.ParseFloat(string(bytes.TrimSpace(star[154:160])), 64)
	if len(star) >= 170 {
		stardata.RadVel, _ = strconv.ParseFloat(string(bytes.TrimSpace(star[166:170])), 64)
	}
	if len(star) >= 179 {
		stardata.RotVel, _ = strconv.ParseFloat(string(bytes.TrimSpace(star[176:179])), 64)
	}
	if len(star) > 161 {
		rc, _ := strconv.ParseFloat(string(bytes.TrimSpace(star[162:166])), 64)
		if rc != 0 {
			stardata.Pc = 1 / rc
		}
	}
	return stardata, nil
}

func parseRa(star []byte) (float64, error) {
	var sec float64
	var err error
	ra := float64(0)
	for i := 75; i < 77; i++ {
		if star[i] == ' ' {
			continue
		}
		ra = ra*10 + float64(star[i]-48)
	}
	minute := uint8(0)
	for i := 77; i < 79; i++ {
		if star[i] == ' ' {
			continue
		}
		minute = minute*10 + (star[i] - 48)
	}
	ori := string(bytes.TrimSpace(star[79:83]))
	if ori != "" {
		sec, err = strconv.ParseFloat(ori, 64)
		if err != nil {
			return ra, err
		}
	}
	ra += float64(minute)/60 + sec/3600
	return ra * 15, nil
}

func parseDec(star []byte) (float64, error) {
	var sec float64
	var err error
	underZero := false
	if star[83] == '-' {
		underZero = true
	}
	dec := float64(0)
	for i := 84; i < 86; i++ {
		if star[i] == ' ' {
			continue
		}
		dec = dec*10 + float64(star[i]-48)
	}
	minute := uint8(0)
	for i := 86; i < 88; i++ {
		if star[i] == ' ' {
			continue
		}
		minute = minute*10 + (star[i] - 48)
	}
	ori := string(bytes.TrimSpace(star[88:90]))
	if ori != "" {
		sec, err = strconv.ParseFloat(ori, 64)
		if err != nil {
			return dec, err
		}
	}
	dec += float64(minute)/60 + sec/3600
	if underZero {
		dec = -dec
	}
	return dec, nil
}

var stardat [][]byte
var starhdindex map[string]int
var hr2detail map[uint16][]string
var hr2hip map[uint16]uint32
var chnidx map[string]uint16
var parsedStarData []InnerStarData
var cachedStarData []StarData
var starDataOnce sync.Once
var starDataErr error

func LoadStarData() error {
	starDataOnce.Do(func() {
		starDataErr = initStarData()
	})
	return starDataErr
}

func initStarData() error {
	data := initStarCatalogData()
	stardat = bytes.Split(data, []byte("\n"))
	parsedStarData = make([]InnerStarData, len(stardat))
	cachedStarData = make([]StarData, len(stardat))
	for i, row := range stardat {
		parsed, err := parseStarData(row)
		if err != nil {
			return fmt.Errorf("parse star %d failed: %w", i+1, err)
		}
		parsedStarData[i] = parsed
		cachedStarData[i] = fullStarData(parsed)
	}
	chnidx = make(map[string]uint16, len(hr2detail)*2)
	for hr := 1; hr <= len(cachedStarData); hr++ {
		info, ok := hr2detail[uint16(hr)]
		if !ok {
			continue
		}
		registerStarChineseIndex(uint16(hr), info[0])
		registerStarChineseIndex(uint16(hr), info[1])
	}
	return nil
}

func registerStarChineseIndex(hr uint16, name string) {
	if name == "" {
		return
	}
	chnidx[name] = hr
	if strings.HasSuffix(name, "星") {
		trimmed := strings.TrimSuffix(name, "星")
		if trimmed != "" {
			chnidx[trimmed] = hr
		}
	}
}

func fullStarData(star InnerStarData) StarData {
	star.HIP = hr2hip[star.HR]
	if info, ok := hr2detail[star.HR]; ok {
		return StarData{
			InnerStarData:    star,
			ChineseName:      info[0],
			ChineseAlias:     info[1],
			ChineseBayerName: info[2],
			CommonName:       info[3],
			CommonAliasName:  "",
			Cst:              info[5],
			CstChinese:       info[4],
		}
	}

	return StarData{InnerStarData: star}
}

func StarDataByChinese(name string) (StarData, error) {
	if err := LoadStarData(); err != nil {
		return StarData{}, err
	}
	if strings.HasSuffix(name, "星") {
		name = strings.TrimSuffix(name, "星")
	}
	hr, ok := chnidx[name]
	if !ok || hr == 0 || int(hr) > len(cachedStarData) {
		return StarData{}, errors.New("no such star")
	}
	return cachedStarData[hr-1], nil
}

func StarDataByHR(hr int) (StarData, error) {
	if err := LoadStarData(); err != nil {
		return StarData{}, err
	}
	if hr <= 0 || hr > len(cachedStarData) {
		return StarData{}, errors.New("no such star")
	}
	return cachedStarData[hr-1], nil
}

func (s InnerStarData) RaDecByJde(jde float64) (float64, float64) {
	// BSC 的 pmRA 是投影自行 cos(Dec)*dRA/dt，而不是 dRA/dt / BSC pmRA is the projected motion cos(Dec)*dRA/dt, not dRA/dt.
	year := ((jde - 2451545.0) / 365.2422)
	dec := s.Dec + year*s.PmDec/3600
	cosDec := math.Cos(s.Dec * math.Pi / 180)
	ra := s.Ra
	if math.Abs(cosDec) > 1e-12 {
		ra += year * s.PmRA / (3600 * cosDec)
	}
	return Precess(ra, dec, 2451545.0, jde)
}

func (s StarData) RaDecByDate(date time.Time) (float64, float64) {
	jde := Date2JDE(date.UTC())
	return s.RaDecByJde(jde)
}
