package calendar

import (
	"fmt"
	"time"
)

//Solar structure
type Solar struct {
	time.Time
}

// NewSolarNow creates current solar time.
func NewSolarNow() *Solar {
	return &Solar{time.Now().In(CST)}
}

// NewSolarTime creates a solar time from time.Time.
func NewSolarTime(t time.Time) *Solar {
	return &Solar{t.In(CST)}
}

// NewSolar creates a solar time.
func NewSolar(year, month, day, hour, min, sec int, nsec int, loc *time.Location) *Solar {
	if !isYearValid(year) {
		return nil
	}
	t := time.Date(year, time.Month(month), day, hour, min, sec, nsec, loc)
	return &Solar{t.In(CST)}
}

// DiffWithYMD returns the number of days past the current time.
func (s *Solar) DiffWithYMD(year, month, day int) int {
	return int((time.Date(year, time.Month(month), day, 0, 0, 0, 0, CST).Unix() - s.Time.Unix()) / 86400)
}

// DiffYMD returns the number of days that the target time exceeds the specified time.
func DiffYMD(year, month, day, targetYear, targetMonth, targetDay int) int {
	return int((time.Date(targetYear, time.Month(targetMonth), targetDay, 0, 0, 0, 0, CST).Unix() - time.Date(year, time.Month(month), day, 0, 0, 0, 0, CST).Unix()) / 86400)
}

// GetTime returns the time.Time
func (s *Solar) GetTime() time.Time {
	return s.Time
}

// String formats time.
func (s *Solar) String() string {
	return fmt.Sprintf("%d年%02d月%02d日 %2d时%2d分%2d秒",
		s.Year(), s.Month(), s.Day(), s.Hour(), s.Minute(), s.Second())
}

// Equal returns whether it is equal to the solar time.
func (s *Solar) Equal(solar *Solar) bool {
	return s.Time.Equal(solar.Time)
}

// Festival returns festival.
func (s *Solar) Festival(fm FestivalMap) (string, error) {
	m := fmt.Sprintf("%2d", int(s.Month()))
	d := fmt.Sprintf("%2d", s.Day())

	return fm.Get(m + d)
}

// GanzhiYMD returns the year, month and day of "天干地支".
func (s *Solar) GanzhiYMD() (year, month, day string) {
	year, month, day = GanZhiYMD(s.Year(), int(s.Month()), s.Day())
	return
}

// Convert converts to a lunar calendar time.
func (s *Solar) Convert() *Lunar {
	var i int
	var leap int
	var isLeap bool
	var temp int

	var day int
	var month int
	var year int

	//offset days
	offset := int(s.Sub(base).Seconds() / 86400)

	for i = MinYear; i < MaxYear && offset > 0; i++ {
		temp = LunarYearDays(i)
		offset -= temp
	}

	if offset < 0 {
		offset += temp
		i--
	}

	year = i
	leap = LeapMonth(i)
	isLeap = false

	for i = 1; i < 13 && offset > 0; i++ {
		//leap month
		if leap > 0 && i == (leap+1) && isLeap == false {
			i--
			isLeap = true
			temp = LeapDays(year)
		} else {
			temp = LunarMonthDays(year, i)
		}
		//reset leap month
		if isLeap == true && i == (leap+1) {
			isLeap = false
		}
		offset -= temp
	}

	if offset == 0 && leap > 0 && i == (leap+1) {
		if isLeap {
			isLeap = false
		} else {
			isLeap = true
			i--
		}
	}

	if offset < 0 {
		offset += temp
		i--
	}

	month = i
	day = offset + 1

	return &Lunar{year, month, day, s.Hour(), s.Minute(), s.Second(), s.Nanosecond(), LeapMonth(year), isLeap}
}

// Truncate returns the result of rounding t down to a multiple of d (since the zero time).
// If d <= 0, Truncate returns t unchanged.
//
// Truncate operates on the time as an absolute duration since the
// zero time; it does not operate on the presentation form of the
// time. Thus, Truncate(Hour) may return a time with a non-zero
// minute, depending on the time's Location.
func (s Solar) Truncate(d time.Duration) *Solar {
	return &Solar{s.Time.Truncate(d)}
}

// AddMonth add months
func (s Solar) AddMonth(months int) *Solar {
	if months == 0 {
		return &s
	}
	y, m, d := s.Date()
	m = m + time.Month(months)
	y += int(m) / 12
	if m < 0 {
		y -= 1
	}
	if m > 12 {
		m = m % 12
	} else if m < 1 {
		m = 12 + m%12
	}
	n := SolarMonthDays(y, int(m))
	if d > n {
		d = n
	}
	return &Solar{time.Date(y, m, d, s.Hour(), s.Minute(), s.Second(), s.Nanosecond(), s.Location())}
}

// IsLeapYear determines whether it is a leap year.
func IsLeapYear(year int) bool {
	if year%4 == 0 && (year%100 != 0 || year%400 == 0) {
		return true
	}
	return false
}

// SolarMonthDays the days of the m-th month of this year.
func SolarMonthDays(year int, month int) int {
	switch month {
	case 1, 3, 5, 7, 8, 10, 12:
		return 31
	case 2:
		if IsLeapYear(year) {
			return 29
		}
		return 28
	case 4, 6, 9, 11:
		return 30
	}
	return 0
}
