package utils

import (
	"fmt"
	"time"
)

func StartOfDay(date time.Time) time.Time {
	return FloorDate(date)
}

func StartOfToday(tz *time.Location) time.Time {
	return StartOfDay(FloorDate(time.Now().In(tz)))
}

func StartOfThisWeek(tz *time.Location) time.Time {
	return StartOfWeek(time.Now().In(tz))
}

func StartOfWeek(date time.Time) time.Time {
	year, week := date.ISOWeek()
	return firstDayOfISOWeek(year, week, date.Location())
}

func StartOfThisMonth(tz *time.Location) time.Time {
	return StartOfMonth(time.Now().In(tz))
}

func StartOfMonth(date time.Time) time.Time {
	return time.Date(date.Year(), date.Month(), 1, 0, 0, 0, 0, date.Location())
}

func StartOfThisYear(tz *time.Location) time.Time {
	return StartOfYear(time.Now().In(tz))
}

func StartOfYear(date time.Time) time.Time {
	return time.Date(date.Year(), time.January, 1, 0, 0, 0, 0, date.Location())
}

// FloorDate rounds date down to the start of the day and keeps the time zone
func FloorDate(date time.Time) time.Time {
	return time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
}

// CeilDate rounds date up to the start of next day if date is not already a start (00:00:00)
func CeilDate(date time.Time) time.Time {
	floored := FloorDate(date)
	if floored == date {
		return floored
	}
	return floored.Add(24 * time.Hour)
}

// SetLocation resets the time zone information of a date without converting it, i.e. 19:00 UTC will result in 19:00 CET, for instance
func SetLocation(date time.Time, tz *time.Location) time.Time {
	return time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, tz)
}

// WithOffset adds the time zone difference between Local and tz to a date, i.e. 19:00 UTC will result in 21:00 CET (or 22:00 CEST), for instance
func WithOffset(date time.Time, tz *time.Location) time.Time {
	now := time.Now()
	_, localOffset := now.Zone()
	_, targetOffset := now.In(tz).Zone()
	dateTz := date.Add(time.Duration((targetOffset - localOffset) * int(time.Second)))
	return time.Date(dateTz.Year(), dateTz.Month(), dateTz.Day(), dateTz.Hour(), dateTz.Minute(), dateTz.Second(), dateTz.Nanosecond(), dateTz.Location()).In(tz)
}

// SplitRangeByDays creates a slice of intervals between from and to, each of which is at max of 24 hours length and has its split at midnight
func SplitRangeByDays(from time.Time, to time.Time) [][]time.Time {
	intervals := make([][]time.Time, 0)

	for t1 := from; t1.Before(to); {
		t2 := StartOfDay(t1).Add(24 * time.Hour)
		if t2.After(to) {
			t2 = to
		}
		intervals = append(intervals, []time.Time{t1, t2})
		t1 = t2
	}

	return intervals
}

func FmtWakatimeDuration(d time.Duration) string {
	d = d.Round(time.Minute)
	h := d / time.Hour
	d -= h * time.Hour
	m := d / time.Minute
	return fmt.Sprintf("%d hrs %d mins", h, m)
}

// https://stackoverflow.com/a/18632496
func firstDayOfISOWeek(year int, week int, timezone *time.Location) time.Time {
	date := time.Date(year, 0, 0, 0, 0, 0, 0, timezone)
	isoYear, isoWeek := date.ISOWeek()
	for date.Weekday() != time.Monday { // iterate back to Monday
		date = date.AddDate(0, 0, -1)
		isoYear, isoWeek = date.ISOWeek()
	}
	for isoYear < year { // iterate forward to the first day of the first week
		date = date.AddDate(0, 0, 1)
		isoYear, isoWeek = date.ISOWeek()
	}
	for isoWeek < week { // iterate forward to the first day of the given week
		date = date.AddDate(0, 0, 1)
		isoYear, isoWeek = date.ISOWeek()
	}
	return date
}
