package timex

import "time"

// RangeDate returns a function which returns a time
// between "start" and "end". When the iteration finishes
// the returned time is zero.
func RangeDate(start, end time.Time) func() time.Time {
	y, m, d := start.Date()
	start = time.Date(y, m, d, 0, 0, 0, 0, start.Location())
	y, m, d = end.Date()
	end = time.Date(y, m, d, 0, 0, 0, 0, end.Location())

	return func() time.Time {
		if start.After(end) {
			return time.Time{}
		}
		date := start
		start = start.AddDate(0, 0, 1)
		return date
	}
}

type DateRangeType string

const (
	DayRange   DateRangeType = "day"
	MonthRange DateRangeType = "month"
	WeekRange  DateRangeType = "week"
	YearRange  DateRangeType = "year"
)

// Between returns the dates from "start" to "end".
func Between(start, end time.Time) []time.Time {
	var dates []time.Time
	for df := RangeDate(start, end); ; {
		d := df()
		if d.IsZero() {
			break
		}
		dates = append(dates, d)
	}
	return dates
}

// Backwards returns a list of dates between "end" and -n (years, months, weeks or days).
func Backwards(typ DateRangeType, end time.Time, n int) []time.Time {
	var start time.Time

	switch typ {
	case DayRange:
		n = n - 1 // -7 should be -6 to get the week from today.
		start = end.AddDate(0, 0, -n)
	case WeekRange:
		// 7 should be 6 to get the week.
		start = end.AddDate(0, 0, -n*6)
	case MonthRange:
		start = end.AddDate(0, -n, 0)
	case YearRange:
		start = end.AddDate(-n, 0, 0)
	}

	return Between(start, end)
}

// BackwardsN returns the dates from back to "n" years, months, weeks or days from today.
func BackwardsN(typ DateRangeType, n int) []time.Time {
	end := time.Now()
	return Backwards(typ, end, n)
}

// BackwardsToMonday returns the dates between "end" (including "end")
// until the previous monday of the current week (including monday).
func BackwardsToMonday(end time.Time) []time.Time {
	dates := []time.Time{end}
	for end.Weekday() != time.Monday {
		end = end.AddDate(0, 0, -1)
		dates = append(dates, end)
	}
	return dates
}

// GetWeekDate returns the date of the given weekday (monday, tuesday, etc.) of the current week.
func GetWeekDate(now time.Time, weekday, start, end time.Weekday) time.Time {
	dates := GetWeekdays(now, start, end)
	for _, d := range dates {
		if d.Weekday() == weekday {
			return d
		}
	}

	return time.Time{}
}

// GetWeekStart returns the date of the first week day (startWeekday) of the current now's week.
func GetWeekStart(now time.Time, startWeekday time.Weekday) time.Time {
	offset := (int(startWeekday) - int(now.Weekday()) - 7) % 7
	date := now.Add(time.Duration(offset*24) * time.Hour)
	return date
}

// GetWeekEnd returns the date of the last week day (endWeekday) of the current now's week.
func GetWeekEnd(now time.Time, endWeekday time.Weekday) time.Time {
	offset := (int(endWeekday) - int(now.Weekday()) + 7) % 7
	date := now.Add(time.Duration(offset*24) * time.Hour)
	return date
}

// GetWeekdays returns the range between "startWeekday" and "endWeekday" of the current week.
func GetWeekdays(now time.Time, startWeekday, endWeekday time.Weekday) (dates []time.Time) {
	return Between(GetWeekStart(now, startWeekday), GetWeekEnd(now, endWeekday))
}

// GetMonthStart returns the date of the first month day of the current now's month.
func GetMonthStart(now time.Time) time.Time {
	return time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
}

// GetYearStart returns the date of the first year of the current now's year.
func GetYearStart(now time.Time) time.Time {
	return time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location())
}