package ini

import (
	"fmt"
	"sort"
)

// Visitor is an interface used by walkers that will
// traverse an array of ASTs.
type Visitor interface {
	VisitExpr(AST) error
	VisitStatement(AST) error
}

// DefaultVisitor is used to visit statements and expressions
// and ensure that they are both of the correct format.
// In addition, upon visiting this will build sections and populate
// the Sections field which can be used to retrieve profile
// configuration.
type DefaultVisitor struct {
	scope    string
	Sections Sections
}

// NewDefaultVisitor return a DefaultVisitor
func NewDefaultVisitor() *DefaultVisitor {
	return &DefaultVisitor{
		Sections: Sections{
			container: map[string]Section{},
		},
	}
}

// VisitExpr visits expressions...
func (v *DefaultVisitor) VisitExpr(expr AST) error {
	t := v.Sections.container[v.scope]
	if t.values == nil {
		t.values = values{}
	}

	switch expr.Kind {
	case ASTKindExprStatement:
		opExpr := expr.GetRoot()
		switch opExpr.Kind {
		case ASTKindEqualExpr:
			children := opExpr.GetChildren()
			if len(children) <= 1 {
				return NewParseError("unexpected token type")
			}

			rhs := children[1]

			// The right-hand value side the equality expression is allowed to contain '[', ']', ':', '=' in the values.
			// If the token is not either a literal or one of the token types that identifies those four additional
			// tokens then error.
			if !(rhs.Root.Type() == TokenLit || rhs.Root.Type() == TokenOp || rhs.Root.Type() == TokenSep) {
				return NewParseError("unexpected token type")
			}

			key := EqualExprKey(opExpr)
			v, err := newValue(rhs.Root.ValueType, rhs.Root.base, rhs.Root.Raw())
			if err != nil {
				return err
			}

			t.values[key] = v
		default:
			return NewParseError(fmt.Sprintf("unsupported expression %v", expr))
		}
	default:
		return NewParseError(fmt.Sprintf("unsupported expression %v", expr))
	}

	v.Sections.container[v.scope] = t
	return nil
}

// VisitStatement visits statements...
func (v *DefaultVisitor) VisitStatement(stmt AST) error {
	switch stmt.Kind {
	case ASTKindCompletedSectionStatement:
		child := stmt.GetRoot()
		if child.Kind != ASTKindSectionStatement {
			return NewParseError(fmt.Sprintf("unsupported child statement: %T", child))
		}

		name := string(child.Root.Raw())
		v.Sections.container[name] = Section{}
		v.scope = name
	default:
		return NewParseError(fmt.Sprintf("unsupported statement: %s", stmt.Kind))
	}

	return nil
}

// Sections is a map of Section structures that represent
// a configuration.
type Sections struct {
	container map[string]Section
}

// GetSection will return section p. If section p does not exist,
// false will be returned in the second parameter.
func (t Sections) GetSection(p string) (Section, bool) {
	v, ok := t.container[p]
	return v, ok
}

// values represents a map of union values.
type values map[string]Value

// List will return a list of all sections that were successfully
// parsed.
func (t Sections) List() []string {
	keys := make([]string, len(t.container))
	i := 0
	for k := range t.container {
		keys[i] = k
		i++
	}

	sort.Strings(keys)
	return keys
}

// Section contains a name and values. This represent
// a sectioned entry in a configuration file.
type Section struct {
	Name   string
	values values
}

// Has will return whether or not an entry exists in a given section
func (t Section) Has(k string) bool {
	_, ok := t.values[k]
	return ok
}

// ValueType will returned what type the union is set to. If
// k was not found, the NoneType will be returned.
func (t Section) ValueType(k string) (ValueType, bool) {
	v, ok := t.values[k]
	return v.Type, ok
}

// Bool returns a bool value at k
func (t Section) Bool(k string) (bool, bool) {
	return t.values[k].BoolValue()
}

// Int returns an integer value at k
func (t Section) Int(k string) (int64, bool) {
	return t.values[k].IntValue()
}

// Float64 returns a float value at k
func (t Section) Float64(k string) (float64, bool) {
	return t.values[k].FloatValue()
}

// String returns the string value at k
func (t Section) String(k string) string {
	_, ok := t.values[k]
	if !ok {
		return ""
	}
	return t.values[k].StringValue()
}
