package again

import (
	"fmt"
	"time"
)

type Schedule struct {
	NextRunAt time.Time
}

// https://yourbasic.org/golang/time-change-convert-location-timezone/
// https://sebest.github.io/post/create-a-small-docker-image-for-a-golang-binary/
// https://github.com/FKSE/docker-golang-base
// tar cfz zoneinfo.tar.gz /usr/share/zoneinfo
// git clone https://github.com/eggert/tz
// grep '^Rule' -r tz/ | cut -f8-10 | egrep -iv 'Rule|SAVE'
// egrep '\s0:30' -r tz/
func Run() {
	// blacklist "", "Local"
	// UTC to TZ should always be correct
	// TZ to UTC may not be correct
	now := time.Now()
	fmt.Println("Now", now.Format(time.RFC3339))
	loc, err := time.LoadLocation("America/Phoenix")
	if nil != err {
		panic(err)
	}
	fmt.Println("Loc", now.In(loc))

	/* boundary checks */
	for _, st := range [][]int{
		[]int{2019, 11, 10, 01, 02, 56, 0},
		[]int{2019, 11, 10, 01, 02, 60, 0},
		[]int{2019, 11, 10, 01, 60, 59, 0},
		[]int{2019, 11, 10, 24, 59, 59, 0},
		[]int{2019, 11, 10, 25, 59, 59, 0},
		[]int{2019, 11, 10, 23, 59, 59, 0},
		[]int{2019, 11, 31, 23, 0, 0, 0},
	} {
		err := Exists(st, "America/Denver")
		if nil != err {
			fmt.Println(err)
		}
	}

	/* funky times */
	tz := "America/Denver"
	fmt.Println("funky")
	for _, st := range [][]int{
		[]int{2019, 3, 10, 01, 59, 00, 0},
		[]int{2019, 3, 10, 02, 00, 00, 0},
		[]int{2019, 3, 10, 02, 01, 00, 0},
		[]int{2019, 3, 10, 02, 59, 00, 0},
		[]int{2019, 3, 10, 03, 00, 00, 0},
		[]int{2019, 3, 10, 03, 01, 00, 0},
		[]int{2019, 11, 03, 0, 59, 00, 0},
		[]int{2019, 11, 03, 01, 59, 00, 0},
		[]int{2019, 11, 03, 02, 00, 00, 0},
		[]int{2019, 11, 03, 02, 01, 00, 0},
		[]int{2019, 11, 03, 02, 59, 00, 0},
		[]int{2019, 11, 03, 03, 00, 00, 0},
		[]int{2019, 11, 03, 03, 01, 00, 0},
		[]int{2019, 11, 03, 04, 01, 00, 0},
		[]int{2019, 11, 03, 05, 01, 00, 0},
	} {
		err := IsAmbiguous(st, tz)
		if nil != err {
			fmt.Println(err)
		}
	}
}

type ErrNoExist struct {
	e string
	t []int
	z string
}

func (err ErrNoExist) Error() string {
	return fmt.Sprintf("E_INVALID_TIME: '%#v' is not a valid timestamp at '%s': %s", err.t, err.z, err.e)
}

// Check if the time is a real time in the given timezone.
//
// For example: 2:30am doesn't happen on 2019 March 10th according
// to America/Denver local time, due to the end of Daylight Savings Time,
// but it does happen in America/Phoenix.
//
// Also rejects times that are parsable and return a valid date object,
// but are not canonical, such as 24:60:75 November 31st, 2020.
// (which would be represented as `2020-12-02 02:01:15 +0000 UTC`)
//
// Example of parsable, but non-canonical time:
//
//     var loc *time.Location
//     loc, _ = time.LoadLocation("America/Denver")
//     t := time.Date(2019, time.March, 10, 1, 30, 0, 0, loc)
//     fmt.Println(t, "==", t.UTC())
//     // 2019-03-10 01:30:00 -0700 MST == 2019-03-10 08:30:00 +0000 UTC
//     t = t.Add(time.Duration(1) * time.Hour)
//     fmt.Println(t, "==", t.UTC())
//     // 2019-03-10 03:30:00 -0600 MDT == 2019-03-10 09:30:00 +0000 UTC
//
// Example of a canonical, but non-parsable time (the 2016 leap second):
//
//     fmt.Println(time.Date(2016, time.December, 31, 23, 59, 60, 0, time.UTC))
//     "2020-12-02 02:00:00 +0000 UTC" // should be "2016-12-31 23:59:60 +0000 UTC"
//
func Exists(st []int, tzstr string) error {
	tz, err := time.LoadLocation(tzstr)
	if nil != err {
		return err
	}

	m := time.Month(st[1])
	t1 := time.Date(st[0], m, st[2], st[3], st[4], st[5], st[6], tz)
	if st[5] != t1.Second() {
		return ErrNoExist{
			t: st,
			z: tzstr,
			e: "invalid second, probably just bad math on your part",
		}
	}
	if st[4] != t1.Minute() {
		return ErrNoExist{
			t: st,
			z: tzstr,
			e: "invalid minute, probably just bad math on your part, but perhaps a half-hour daylight savings or summer time",
		}
	}
	if st[3] != t1.Hour() {
		return ErrNoExist{
			t: st,
			z: tzstr,
			e: "invalid hour, possibly a Daylight Savings or Summer Time error, or perhaps bad math on your part",
		}
	}
	if st[2] != t1.Day() {
		return ErrNoExist{
			t: st,
			z: tzstr,
			e: "invalid day of month, most likely bad math on your part. Remember: 31 28¼ 31 30 31 30 31 31 30 31 30 31",
		}
	}
	if st[1] != int(t1.Month()) {
		return ErrNoExist{
			t: st,
			z: tzstr,
			e: "invalid month, most likely bad math on your part. Remember: Decemberween isn't until next year",
		}
	}
	if st[0] != t1.Year() {
		return ErrNoExist{
			t: st,
			z: tzstr,
			e: "invalid year, must have reached the end of time...",
		}
	}

	return nil
}

// Check if the time happens more than once in a given timezone.
//
// For example: 1:30am happens only once on 2019 Nov 3rd according to
// America/Phoenix time but due to the start of Daylight Savings Time,
// it happens twice in America/Denver.
//
// Example of duplicate, non-canonical time:
//
//     var loc *time.Location
//     loc, _ = time.LoadLocation("America/Denver")
//     t = time.Date(2019, time.November, 3, 1, 30, 0, 0, loc)
//     fmt.Println(t, "==", t.UTC())
//     // 2019-11-03 01:30:00 -0600 MDT == 2019-11-03 07:30:00 +0000 UTC
//     t = t.Add(time.Duration(1) * time.Hour)
//     fmt.Println(t, "==", t.UTC())
//     // 2019-11-03 01:30:00 -0700 MST == 2019-11-03 08:30:00 +0000 UTC
//     t = t.Add(time.Duration(1) * time.Hour)
//     fmt.Println(t, "==", t.UTC())
//     // 2019-11-03 02:30:00 -0700 MST == 2019-11-03 09:30:00 +0000 UTC
//
func IsAmbiguous(st []int, tzstr string) error {
	// Does the time exist twice?
	// (if I change the time in UTC, do I still get the same time)
	// Note: Some timezones change by half or quarter hour
	//       However, it seems that DST always changes by one or two whole hours
	// Oh, and then there's Luthuania...
	// Rule	LH	2008	max	-	Oct	Sun>=1	2:00	0:30	-
	//
	// https://en.wikipedia.org/wiki/Daylight_saving_time_by_country
	// https://en.wikipedia.org/wiki/Winter_time_(clock_lag)
	// https://en.wikipedia.org/wiki/Summer_time_in_Europe
	// https://en.wikipedia.org/wiki/Daylight_saving_time_in_the_Americas
	// If I change the time iadd or subtract time in UTC, do I see the same difference in TZ?
	tz, err := time.LoadLocation(tzstr)
	if nil != err {
		return err
	}
	m := time.Month(st[1])
	t1 := time.Date(st[0], m, st[2], st[3], st[4], st[5], st[6], tz)
	u1 := t1.UTC()
	// A better way to do this would probably be to parse the timezone database, but... yeah...
	for _, n := range []int{ /*-120, -60,*/ 30, 60, 120} {
		t2 := time.Date(st[0], m, st[2], st[3], st[4]+n, st[5], st[6], tz)
		u2 := t2.UTC()
		if u1.Equal(u2) {
			fmt.Println("Ambiguous Time")
			fmt.Printf("%s, %s, %+d\n", t1, u1, n)
			fmt.Printf("%s, %s, %+d\n", t2, u2, n)
			return fmt.Errorf("Ambiguous")
		}
	}
	//ta :=
	return nil
}

/*

//////
////// 9:01 twice
//////

var d = new Date("3/10/2019, 01:59:00");
console.log("tzUTC:" + tzUTC(d, 'America/Denver'));
// tzUTC:Sun, 10 Mar 2019 08:59:00 GMT

var d = new Date("3/10/2019, 02:01:00");
console.log("tzUTC:" + tzUTC(d, 'America/Denver'));
// tzUTC:Sun, 10 Mar 2019 09:01:00 GMT

var d = new Date("3/10/2019, 02:59:00");
console.log("tzUTC:" + tzUTC(d, 'America/Denver'));
// tzUTC:Sun, 10 Mar 2019 09:59:00 GMT

var d = new Date("3/10/2019, 03:01:00");
console.log("tzUTC:" + tzUTC(d, 'America/Denver'));
// tzUTC:Sun, 10 Mar 2019 09:01:00 GMT

//////
////// 8:01 never
//////

var d = new Date("11/03/2019, 01:59:00");
console.log("tzUTC:" + tzUTC(d, 'America/Denver'));
// tzUTC:Sun, 03 Nov 2019 07:59:00 GMT

var d = new Date("11/03/2019, 02:01:00");
console.log("tzUTC:" + tzUTC(d, 'America/Denver'));
// tzUTC:Sun, 03 Nov 2019 09:01:00 GMT

var d = new Date("11/03/2019, 02:59:00");
console.log("tzUTC:" + tzUTC(d, 'America/Denver'));
// tzUTC:Sun, 03 Nov 2019 09:59:00 GMT

var d = new Date("11/03/2019, 03:01:00");
console.log("tzUTC:" + tzUTC(d, 'America/Denver'));
tzUTC:Sun, 03 Nov 2019 10:01:00 GMT

*/
