// Package ast implements an Abstract Syntax Tree that represents a TOML document.
package ast

import (
	"fmt"
	"regexp"
	"strings"
)

// Document is a struct holds the data for a TOML Document.
//
// Methods on this struct provide functionality to construct a full data
// structure that represents a TOML document.
type Document struct {
	Root       Table // the root-level TOML Table (each TOML doc is implicitly a Table)
	Current    Table // the currently active TOML Table
	CurrentKey Key   // the key for the currently active TOML Table
}

// NewDocument instantiates a new TOML Document.
func NewDocument() *Document {
	doc := &Document{Root: make(Table)}
	doc.Current = doc.Root
	return doc
}

// Table represents a TOML table: a set of key/value pairs.
type Table map[string]*Value

// Array represents a TOML array: a list of values.
type Array struct {
	First    *ArrayItem
	Last     *ArrayItem
	ItemType ValueType
	Length   int
}

// ArrayItem represents a single item from a TOML array.
type ArrayItem struct {
	Value *Value
	Next  *ArrayItem
}

// NewArray initializes a new Array.
func NewArray() *Array {
	return &Array{}
}

// Append add a new value to an Array. The values must all be of the same type.
// It returns an error when values of different types are added, nil otherwise.
func (a *Array) Append(value *Value) error {
	item := &ArrayItem{Value: value}
	if a.Length == 0 {
		a.ItemType = value.Type
		a.First = item
		a.Last = item
	} else {
		if value.Type != a.ItemType {
			return fmt.Errorf("type mismatch in array of %ss: found an item of type %s", a.ItemType, value.Type)
		}
		a.Last.Next = item
		a.Last = item
	}
	a.Length++
	return nil
}

// Key represents a TOML table key: one or more strings, where multiple strings
// can be used to represent nested tables.
type Key []string

// NewKey instantiates a new Key.
func NewKey(key ...string) Key {
	return key
}

// Value represents a TOML value.
type Value struct {
	Type ValueType
	Data []interface{}
}

// NewValue instantiates a new Value.
func NewValue(valueType ValueType, data ...interface{}) *Value {
	return &Value{
		Type: valueType,
		Data: data,
	}
}

// ValueType identifies the type of a TOML value, as specified by the
// TOML specification. Because these types do not map Go types one-on-one,
// we have to keep track of the TOML type ourselves.
type ValueType string

const (
	// TypeString identifies a string value ("various", 'types', """of""", '''strings''').
	TypeString ValueType = "string"

	// TypeInteger identifies an integer number value (12345, 0xffee12a0, 0o0755, 0b101101011).
	TypeInteger ValueType = "integer"

	// TypeFloat identifies a floating point number (10.1234, 143E-12, 43.28377e+4, +inf, -inf, nan).
	TypeFloat ValueType = "float"

	// TypeBool identifies a boolean value (true or false).
	TypeBool ValueType = "bool"

	// TypeOffsetDateTime identifies a date/time value, including timezone info (2019-06-18 10:32:15.173645362+0200).
	TypeOffsetDateTime ValueType = "datetime"

	// TypeLocalDateTime identifies a date/time value, without timezone info (2018-12-25 12:12:18.876772533).
	TypeLocalDateTime ValueType = "datetime-local"

	// TypeLocalDate identifies a date value (2017-05-17).
	TypeLocalDate ValueType = "date"

	// TypeLocalTime identifies a time value (23:01:22).
	TypeLocalTime ValueType = "time"

	// TypeArrayOfTables identifies an [[array.of.tables]].
	TypeArrayOfTables ValueType = "array-of-tables"

	// TypeArray identifies ["an", "inline", "static", "array"].
	TypeArray ValueType = "array"

	// TypeTable identifies an { "inline" = "table" } or [standard.table].
	TypeTable ValueType = "table"

	// TypeImplicitTable identifies an intermediate table that was created
	// using a table definition that looks like [a.b.c.d] (where a, b and c
	// would be implicit tables when they hadn't been created explicitly before).
	TypeImplicitTable ValueType = "implicit-table"
)

// SetKeyValuePair is used to set a key and an accompanying value in the
// currently active TOML table of the TOML Document.
func (doc *Document) SetKeyValuePair(key Key, value *Value) error {
	// First make sure the table structure for storing the value exists.
	node, lastKeyPart, err := doc.makeTablePath(key)
	if err != nil {
		return fmt.Errorf("invalid key/value pair: %s", err)
	}
	// Check if the key is still free for use.
	if existing, ok := node[lastKeyPart]; ok {
		path := doc.formatKeyPath(key, len(key)-1)
		return fmt.Errorf("invalid key/value pair: %s value already exists at key %s", existing.Type, path)
	}
	// It is, store the value in the table.
	node[lastKeyPart] = value
	return nil
}

// OpenTable creates a new table at the provided Key path and makes this the
// active table for the TOML Document. This means that subsequent calls to
// SetKeyValuePair() will add the key/value pairs to this table.
func (doc *Document) OpenTable(key Key) error {
	doc.CurrentKey = nil
	doc.Current = doc.Root
	// Go over all requested levels of the key. For all levels, except the last
	// one, it is okay if a Table, implicit Table or TableArray already exists.
	// For at least the last level, no table or value must exist, or an implicit
	// table must exist. Otherwise we are overwriting an existing value, which
	// is not allowed.
	node, lastKeyPart, err := doc.makeTablePath(key)
	if err != nil {
		return fmt.Errorf("invalid table: %s", err)
	}
	// Check if the key is already in use.
	if existing, ok := node[lastKeyPart]; ok {
		// It is, but it is an implicit table. Upgrade it to an explicit and use it.
		if existing.Type == TypeImplicitTable {
			existing.Type = TypeTable
			node = existing.Data[0].(Table)
		} else {
			path := doc.formatKeyPath(key, len(key)-1)
			return fmt.Errorf("invalid table: %s value already exists at key %s", existing.Type, path)
		}
	} else {
		// The subtable does not exist yet. Create the subtable.
		subTable := make(Table)
		node[lastKeyPart] = NewValue(TypeTable, subTable)
		node = subTable
	}

	// From here on, key/value pairs are added to the newly defined table.
	doc.Current = node
	doc.CurrentKey = key
	return nil
}

// OpenArrayOfTables creates a new table and adds it to a (possibly newly
// created) table array at the provided Key path. The new table will be
// made the active table for the TOML Document. This means that subsequent
// calls to SetKeyValuePair() will add the key/value pairs to this table.
func (doc *Document) OpenArrayOfTables(key Key) error {
	doc.CurrentKey = nil
	doc.Current = doc.Root
	// Go over all requested levels of the key. For all levels, except the last
	// one, it is okay if a Table or ArrayOfTables already exists. For the last
	// level, either no value must exist (in which case a table array will be
	// created), or a table array must exist.
	// Other cases would mean we are overwriting an existing key/value pair,
	// which is not allowed.
	node, lastKeyPart, err := doc.makeTablePath(key)
	if err != nil {
		return fmt.Errorf("invalid table array: %s", err)
	}
	// At the last key position, there must be either no value yet, or the
	// existing value must be a table array. Other values are invalid.
	if existing, ok := node[lastKeyPart]; ok {
		if existing.Type != TypeArrayOfTables {
			path := doc.formatKeyPath(key, len(key)-1)
			return fmt.Errorf("invalid table array: %s value already exists at key %s", existing.Type, path)
		}
		// An array of tables exists. Add a new table to this array.
		array := node[lastKeyPart].Data[0].(*Array)
		subTable := make(Table)
		array.Append(NewValue(TypeTable, subTable))
		node = subTable
	} else {
		// No value exists at the defined key path. Create a new table array.
		subTable := make(Table)
		array := NewArray()
		array.Append(NewValue(TypeTable, subTable))
		node[lastKeyPart] = NewValue(TypeArrayOfTables, array)
		node = subTable
	}

	// From here on, key/value pairs are added to the newly defined table.
	doc.Current = node
	doc.CurrentKey = key
	return nil
}

func (doc *Document) makeTablePath(key Key) (Table, string, error) {
	node := doc.Current
	for i, keyPart := range key {
		// Arrived at the last key part? Then the path towards that key is
		// setup correctly. Return the last part, so the caller can use it.
		isLast := i == len(key)-1
		if isLast {
			return node, keyPart, nil
		}
		if subValue, ok := node[keyPart]; ok {
			// You cannot overwrite an already defined key, regardless its value.
			// When a value already exists at the current key, this can only be a table,
			// an implicit table or an array of tables. In case of an array of tables,
			// the table that was added last to the array will be used.
			if subValue.Type == TypeTable || subValue.Type == TypeImplicitTable {
				// A table was found, traverse to that table.
				node = subValue.Data[0].(Table)
			} else if subValue.Type == TypeArrayOfTables {
				// An array of tables was found, use the last table in the array.
				lastTable := subValue.Data[0].(*Array).Last.Value.Data[0].(Table)
				node = lastTable
			} else {
				path := doc.formatKeyPath(key, i)
				return nil, "", fmt.Errorf("%s value already exists at key %s", subValue.Type, path)
			}
		} else {
			// The subtable does not exist yet. Create the subtable as an implicit table.
			subTable := make(Table)
			node[keyPart] = NewValue(TypeImplicitTable, subTable)
			node = subTable
		}
	}
	panic("makeTablePath(): empty key provided; a key must have at least one key part")
}

func (doc *Document) formatKeyPath(key Key, end int) string {
	var sb strings.Builder
	sb.WriteRune('[')
	if doc.CurrentKey != nil {
		for i, keyPart := range doc.CurrentKey {
			if i > 0 {
				sb.WriteString("->")
			}
			sb.WriteString(formatKeyName(keyPart))
		}
	}
	for i, keyPart := range key {
		if doc.CurrentKey != nil || i > 0 {
			sb.WriteString("->")
		}
		sb.WriteString(formatKeyName(keyPart))
		if i == end {
			break
		}
	}
	sb.WriteRune(']')
	return sb.String()
}

func formatKeyName(key string) string {
	if ok, _ := regexp.Match(`^\w+$`, []byte(key)); ok {
		return key
	}
	return fmt.Sprintf("%q", key)
}
