/*
Copyright 2023 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package ini

// Copy from https://github.com/aws/aws-sdk-go
// May have been modified by Beijing Volcanoengine Technology Ltd.

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]

			if rhs.Root.Type() != TokenLit {
				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 {
	return t.values[k].BoolValue()
}

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

// Float64 returns a float value at k
func (t Section) Float64(k string) float64 {
	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()
}
