package parse

import (
	"time"

	"git.makaay.nl/mauricem/go-parsekit/parse"
	"git.makaay.nl/mauricem/go-parsekit/tokenize"
	"git.makaay.nl/mauricem/go-toml/ast"
)

var (
	// Note: in the definitions below, the token types are chosen based on the
	// formatting definitions as used by https://golang.org/src/time/format.go

	// To unambiguously represent a specific instant in time, you may use an
	// RFC 3339 formatted date-time with offset.
	//
	//     odt1 = 1979-05-27T07:32:00Z
	//     odt2 = 1979-05-27T00:32:00-07:00
	//     odt3 = 1979-05-27T00:32:00.999999-07:00
	//
	// If you include only the time portion of an RFC 3339 formatted date-time,
	// it will represent that time of day without any relation to a specific
	// day or any offset or timezone.
	//
	//    lt1 = 07:32:00
	//    lt2 = 00:32:00.999999

	year     = a.Digit.Times(4)
	month    = a.Digit.Times(2)
	day      = a.Digit.Times(2)
	yyyymmdd = c.Seq(year, a.Minus, month, a.Minus, day)
	dateTok  = tok.Str("2006-01-02", yyyymmdd)

	hour    = a.Digit.Times(2)
	minute  = a.Digit.Times(2)
	seconds = a.Digit.Times(2)
	hhmmss  = c.Seq(hour, a.Colon, minute, a.Colon, seconds)
	timeTok = tok.Str("15:04:05", hhmmss)

	// The precision of fractional seconds is implementation-specific, but at
	// least millisecond precision is expected. If the value contains greater
	// precision than the implementation can support, the additional precision
	// must be truncated, not rounded.

	micro    = a.Dot.Then(c.MinMax(1, 9, a.Digit).Then(m.Drop(c.ZeroOrMore(a.Digit))))
	microTok = c.Optional(tok.Str(".999999999", micro))

	// For the sake of readability, you may replace the T delimiter between
	// date and time with a space (as permitted by RFC 3339 section 5.6).
	// Note that RFC 3339 also allows the use of a lower case delimiter.
	//
	//    odt4 = 1979-05-27 07:32:00Z

	tdelimTok = c.Any(
		tok.Str("T", a.Rune('T')),
		tok.Str("t", a.Rune('t')),
		tok.Str(" ", a.Rune(' ')))

	// If you omit the offset from an RFC 3339 formatted date-time, it will
	// represent the given date-time without any relation to an offset or
	// timezone.
	//
	//     ldt1 = 1979-05-27T07:32:00
	//     ldt2 = 1979-05-27T00:32:00.999999
	//
	// It cannot be converted to an instant in time without additional
	// information. Conversion to an instant, if required, is
	// implementation-specific.
	//
	// Note that RFC 3339 also allows the use of a lower case 'z'.
	// Here we replace it with a capital 'Z' to make the Go date parser work.

	zulu   = m.Replace(a.Runes('Z', 'z'), "Z")
	offset = c.Seq(a.Runes('+', '-'), hour, a.Colon, minute)
	tzTok  = tok.Str("Z07:00", zulu.Or(offset))

	// The full date/time parse format, based on the above definitions.
	// The token denotes the type of date/time value.
	// The contained tokens contain layout fragments for time.Parse().

	offsetDateTime = tok.Group(ast.TypeOffsetDateTime, c.Seq(dateTok, tdelimTok, timeTok, microTok, tzTok))
	localDateTime  = tok.Group(ast.TypeLocalDateTime, c.Seq(dateTok, tdelimTok, timeTok, microTok))
	localDate      = tok.Group(ast.TypeLocalDate, dateTok)
	localTime      = tok.Group(ast.TypeLocalTime, c.Seq(timeTok, microTok))
	datetime       = c.Any(offsetDateTime, localDateTime, localDate, localTime)
)

func (t *parser) parseDateTime(p *parse.API) (*ast.Value, bool) {
	if !p.Accept(datetime) {
		p.Expected("a date and/or time")
		return nil, false
	}
	result := p.Result()
	token := result.Token(0)

	layout := ""
	for _, l := range token.Value.([]tokenize.Token) {
		layout += l.Type.(string)
	}
	value, err := time.Parse(layout, string(token.Runes))
	if err != nil {
		p.Error("invalid date/time value %s: %s", string(token.Runes), err)
		return nil, false
	}

	return ast.NewValue(token.Type.(ast.ValueType), value), true
}
