package parse2

import "git.makaay.nl/mauricem/go-parsekit/tokenize"

var (
	c, a, m, tok = tokenize.C, tokenize.A, tokenize.M, tokenize.T

	// Overall Structure

	toml = c.Seq(expression, c.ZeroOrMore(newline.Then(expression)))

	expression = c.Any(
		c.Seq(ws, comment.Optional()),
		c.Seq(ws, keyval, ws, comment.Optional()),
		c.Seq(ws, table, ws, comment.Optional()))

	// ABNF definitions

	alpha = a.Letter
	digit = a.Digit

	// Whitespace, Newline

	ws     = c.ZeroOrMore(whitespaceChar)
	tab    = a.Tab
	space  = a.Space
	whitespaceChar = tab.Or(space)

	newline = a.Newline

	whitespace = ws.Or(newline)

	// Comment

	comment = commentStartSymbol.Then(c.ZeroOrMore(nonEOL))

	commentStartSymbol = a.Hash
	nonEOL             = c.Any(a.Rune(0x09), printableASCII, nonASCII)
	printableASCII     = a.RuneRange(0x20, 0x7E)
	nonASCII           = a.RuneRange(0x80, 0xD7FF).Or(a.RuneRange(0xE000, 0x10FFFF))

	// Key-Value Pairs

	keyval = c.Seq(key, keyvalSep, val)

	key       = simpleKey.Or(dottedKey)
	simpleKey = quotedKey.Or(unquotedKey)

	unquotedKey = c.OneOrMore(c.Any(alpha, digit, minus, underscore))
	quotedKey   = basicString.Or(literalString)
	dottedKey   = c.Seq(simpleKey, c.OneOrMore(dotSep.Then(simpleKey)))

	dotSep    = c.Seq(ws, a.Dot, ws)
	keyvalSep = c.Seq(ws, a.Equal, ws)

	val = c.Any(string, boolean, array, inlineTable, dateTime, float, integer)

	// String

	string = c.Any(mlBasicString, basicString, mlLiteralString, literalString)

	// Basic String

	basicString = c.Seq(quotationMark, c.ZeroOrMore(basicChar), quotationMark)

	quotationMark = a.DoubleQuote

	basicChar      = escaped.Or(basicUnescaped)
	basicUnescaped = c.Any(printableASCII.Except(quotationMark.Or(escape)), nonASCII)
	escaped        = escape.Then(escapeSeqChar)

	escape        = a.Backslash
	escapeSeqChar = c.Any(
		a.Runes('"', '\\', 'b', 'f', 'n', 'r', 't'),
		a.Rune('u').Then(a.HexDigit.Times(4)),
		a.Rune('U').Then(a.HexDigit.Times(8)))

	// Multiline Basic String

	mlBasicString = c.Seq(mlBasicStringDelim, mlBasicBody, mlBasicStringDelim)

	mlBasicStringDelim = quotationMark.Times(3)

	mlBasicBody       = c.ZeroOrMore(c.Any(mlBasicChar, newline, mlBasicBodyConcat))
	mlBasicChar       = mlBasicUnescaped.Or(escaped)
	mlBasicUnescaped  = c.Any(printableASCII.Except(a.Backslash), nonASCII)
	mlBasicBodyConcat = c.Seq(escape, ws, newline, c.ZeroOrMore(whitespace))

	// Literal String

	literalString = c.Seq(apostrophe, c.ZeroOrMore(literalChar), apostrophe)

	apostrophe = a.SingleQuote

	literalChar = c.Any(a.Tab, printableASCII.Except(apostrophe), nonASCII)

	// Multiline Literal String

	mlLiteralString = c.Seq(mlLiteralStringDelim, mlLiteralBody, mlLiteralStringDelim)

	mlLiteralStringDelim = apostrophe.Times(3)

	mlLiteralBody = c.ZeroOrMore(mlLiteralChar.Or(newline))
	mlLiteralChar = c.Any(a.Tab, printableASCII, nonASCII)

	// Integer

	integer = c.Any(decInt, hexInt, octInt, binInt)

	minus      = a.Minus
	plus       = a.Plus
	underscore = a.Underscore

	decInt                       = c.Optional(plus.Or(minus)).Then(unsignedDecInt)
	unsignedDecInt               = c.Any(digit, digit1to9.Then(c.OneOrMore(intDigitOrUnderscoreIntDigit)))
	digit1to9                    = a.DigitNotZero
	intDigitOrUnderscoreIntDigit = c.Any(digit, underscore.Then(digit))

	hexInt                       = c.Seq(hexPrefix, hexDigit, c.ZeroOrMore(hexDigitOrUnderscoreHexDigit))
	hexPrefix                    = a.Zero.Then(a.Rune('x'))
	hexDigit                     = a.HexDigit
	hexDigitOrUnderscoreHexDigit = c.Any(hexDigit, underscore.Then(hexDigit))

	octInt                       = c.Seq(octPrefix, digit0to7, c.ZeroOrMore(octDigitOrUnderscoreOctDigit))
	octPrefix                    = a.Zero.Then(a.Rune('o'))
	digit0to7                    = a.RuneRange('0', '7')
	octDigitOrUnderscoreOctDigit = c.Any(digit0to7, underscore.Then(digit0to7))

	binInt                       = c.Seq(binPrefix, digit0to1, c.ZeroOrMore(binDigitOrUnderscoreBinDigit))
	binPrefix                    = a.Zero.Then(a.Rune('b'))
	digit0to1                    = a.Runes('0', '1')
	binDigitOrUnderscoreBinDigit = c.Any(digit0to1, underscore.Then(digit0to1))

	// Float

	float = standardFloat.Or(specialFloat)

	standardFloat     = c.Seq(floatIntPart, exp.Or(frac.Then(c.Optional(exp))))
	floatIntPart      = decInt
	exp               = a.StrNoCase("e").Then(floatIntPart)
	frac              = c.Seq(decimalPoint, zeroPrefixableInt)
	decimalPoint      = a.Dot
	zeroPrefixableInt = c.Seq(digit, c.ZeroOrMore(intDigitOrUnderscoreIntDigit))

	specialFloat = c.Optional(plus.Or(minus)).Then(inf.Or(nan))
	inf          = a.Str("inf")
	nan          = a.Str("nan")

	// Boolean

	boolean = boolTrue.Or(boolFalse)

	boolTrue  = a.Str("true")
	boolFalse = a.Str("false")

	// Date and time (as defined in RFC 3339)

	dateTime = c.Any(offsetDateTime, localDateTime, localDate, localTime)

	offsetDateTime = c.Seq(fullDate, timeDelim, fullTime)
	localDateTime  = c.Seq(fullDate, timeDelim, partialTime)
	localDate      = fullDate
	localTime      = partialTime

	dateFullYear  = digit.Times(4)
	dateMonth     = digit.Times(2)
	dateMday      = digit.Times(2)
	timeDelim     = a.Runes('T', 't', ' ')
	timeHour      = digit.Times(2)
	timeMinute    = digit.Times(2)
	timeSecond    = digit.Times(2)
	timeSecfrac   = a.Dot.Then(c.OneOrMore(digit))
	timeNumOffset = c.Seq(plus.Or(minus), timeHour, a.Colon, timeMinute)
	timeOffset    = c.Any(a.Runes('Z', 'z'), timeNumOffset)
	partialTime   = c.Seq(timeHour, a.Colon, timeMinute, a.Colon, timeSecond, timeSecfrac.Optional())
	fullTime      = c.Seq(partialTime, timeOffset)
	fullDate      = c.Seq(dateFullYear, minus, dateMonth, minus, dateMday)

	// Array

	array = c.Seq(arrayOpen, arrayvalues.Optional(), wsCommentNewline, arrayClose)

	arrayOpen        = a.SquareOpen
	arrayClose       = a.SquareClose
	arrayvalues      = c.Seq(arrayValue, c.ZeroOrMore(c.Seq(arraySep, arrayValue)), arraySep.Optional())
	arraySep         = ws.Then(a.Comma)
	arrayValue       = wsCommentNewline.Then(val)
	wsCommentNewline = c.ZeroOrMore(whitespaceChar.Or(comment.Optional().Then(newline)))

	// Table

	table = stdTable.Or(arrayTable)

	// Standard Table

	stdTable = c.Seq(stdTableOpen, key, stdTableClose)

	stdTableOpen  = a.SquareOpen.Then(ws)
	stdTableClose = ws.Then(a.SquareClose)

	// Inline Table

	inlineTable = c.Seq(inlineTableOpen, inlineTableKeyvals, inlineTableClose)

	inlineTableOpen    = a.CurlyOpen.Then(ws)
	inlineTableClose   = ws.Then(a.CurlyClose)
	inlineTableKeyvals = c.Seq(inlineTableKeyval, c.ZeroOrMore(c.Seq(inlineTableSep, inlineTableKeyval)))
	inlineTableKeyval  = c.Seq(key, keyvalSep, val)
	inlineTableSep     = c.Seq(ws, a.Comma, ws)

	// Array Table

	arrayTable = c.Seq(arrayTableOpen, key, arrayTableClose)

	arrayTableOpen  = a.SquareOpen.Times(2).Then(ws)
	arrayTableClose = ws.Then(a.SquareClose.Times(2))
)

func init() {

}
