package parse

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

var (
	// Opener and closer for [table].
	tableOpen  = c.Seq(whitespace, a.SquareOpen, whitespace)
	tableClose = c.Seq(whitespace, a.SquareClose, whitespace)

	// Opener and closer for [[array.of.tables]].
	tableArrayOpen  = c.Seq(whitespace, a.SquareOpen, a.SquareOpen, whitespace)
	tableArrayClose = c.Seq(whitespace, a.SquareClose, a.SquareClose, whitespace)

	// Opener, separator and closer for { inline: "tables" }.
	inlineTableOpen      = c.Seq(whitespace, a.CurlyOpen, whitespace)
	inlineTableSeparator = c.Seq(whitespace, a.Comma, whitespace)
	inlineTableClose     = c.Seq(whitespace, a.CurlyClose, whitespace)
)

func (t *parser) startTable(p *parse.API) {
	switch {
	case p.Accept(tableArrayOpen):
		p.Handle(t.startArrayOfTables)
	case p.Accept(tableOpen):
		p.Handle(t.startPlainTable)
	default:
		p.Expected("a table")
	}
}

// Arrays of tables can be expressed by using a table name in double brackets.
// Each table with the same double bracketed name will be an element in the
// array. The tables are inserted in the order encountered. A double bracketed
// table without any key/value pairs will be considered an empty table.
//
//     [[products]]
//     name = "Hammer"
//     sku = 738594937
//
//     [[products]]
//
//     [[products]]
//     name = "Nail"
//     sku = 284758393
//     color = "gray"
//
// You can create nested arrays of tables as well. Just use the same double
// bracket syntax on sub-tables. Each double-bracketed sub-table will belong
// to the most recently defined table element above it.
//
// 	    [[fruit]]
// 	    name = "apple"
//
//        [fruit.physical]
//        color = "red"
// 	      shape = "round"
//
// 	      [[fruit.variety]]
// 	      name = "red delicious"
//
// 	      [[fruit.variety]]
// 	      name = "granny smith"
//
//      [[fruit]]
// 	    name = "banana"
//
// 	      [[fruit.variety]]
// 	      name = "plantain"
func (t *parser) startArrayOfTables(p *parse.API) {
	if key, ok := t.parseKey(p, []string{}); ok {
		if !p.Accept(tableArrayClose) {
			p.Expected("closing ']]' for array of tables name")
			return
		}
		if !p.Accept(endOfLineOrComment) {
			p.Expected("end of line or comment")
			return
		}
		if err := t.doc.OpenArrayOfTables(key); err != nil {
			p.Error("%s", err)
			return
		}
		p.Handle(t.startDocument)
	}
}

// Tables (also known as hash tables or dictionaries) are collections of
// key/value pairs. They appear in square brackets on a line by themselves.
// You can tell them apart from arrays because arrays are only ever values.
//
// Under that, and until the next table or EOF are the key/values of that
// table. Key/value pairs within tables are not guaranteed to be in any
// specific order.
//
//     [table-1]
//     key1 = "some string"
//     key2 = 123
//
//     [table-2]
//     key1 = "another string"
//     key2 = 456
//
// Naming rules for tables are the same as for keys.
//
//     [dog."tater.man"]
//     type.name = "pug"
//
// Whitespace around the key is ignored, however, best practice is to not
// use any extraneous whitespace.
//
//     [a.b.c]            # this is best practice
//     [ d.e.f ]          # same as [d.e.f]
//     [ g .  h  . i ]    # same as [g.h.i]
//     [ j . "ʞ" . 'l' ]  # same as [j."ʞ".'l']
//
// You don't need to specify all the super-tables if you don't want to.
// TOML knows how to do it for you.
//
//     # [x] you
//     # [x.y] don't
//     # [x.y.z] need these
//     [x.y.z.w] # for this to work
//
// Empty tables are allowed and simply have no key/value pairs within them.
func (t *parser) startPlainTable(p *parse.API) {
	if key, ok := t.parseKey(p, []string{}); ok {
		if !p.Accept(tableClose) {
			p.Expected("closing ']' for table name")
			return
		}
		if !p.Accept(endOfLineOrComment) {
			p.Expected("end of line or comment")
			return
		}
		if err := t.doc.OpenTable(key); err != nil {
			p.Error("%s", err)
			return
		}
		p.Handle(t.startDocument)
	}
}

// Inline tables provide a more compact syntax for expressing tables.
// They are especially useful for grouped data that can otherwise quickly
// become verbose. Inline tables are enclosed in curly braces { and }.
// Within the braces, zero or more comma separated key/value pairs may appear.
// Key/value pairs take the same form as key/value pairs in standard tables.
// All value types are allowed, including inline tables.
//
// Inline tables are intended to appear on a single line. No newlines are
// allowed between the curly braces unless they are valid within a value.
// Even so, it is strongly discouraged to break an inline table onto multiple
// lines. If you find yourself gripped with this desire, it means you should
// be using standard tables.
//
//     name = { first = "Tom", last = "Preston-Werner" }
//     point = { x = 1, y = 2 }
//     animal = { type.name = "pug" }
func (t *parser) parseInlineTable(p *parse.API) (*ast.Value, bool) {
	// Check for the start of the array.
	if !p.Accept(inlineTableOpen) {
		p.Expected("an inline table")
		return nil, false
	}

	subdoc := newParser()

	// Check for an empty inline table.
	if p.Accept(inlineTableClose) {
		return ast.NewValue(ast.TypeTable, subdoc.doc.Root), true
	}

	// Not an empty table, parse the table data.
	for {
		key, ok := subdoc.parseKey(p, []string{})
		if !ok {
			return nil, false
		}
		if !p.Handle(subdoc.startAssignment) {
			return nil, false
		}
		value, ok := subdoc.parseValue(p)
		if !ok {
			return nil, false
		}
		err := subdoc.doc.SetKeyValuePair(key, value)
		if err != nil {
			p.Error("%s", err)
			return nil, false
		}

		// Check for the end of the inline table.
		if p.Accept(inlineTableClose) {
			return ast.NewValue(ast.TypeTable, subdoc.doc.Root), true
		}

		// Not the end of the inline table? Then we should find a key/value pair separator.
		if !p.Accept(inlineTableSeparator) {
			p.Expected("an array separator")
			return nil, false
		}
	}
}
