// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Parse nodes.

package parse

import (
	"fmt"
	"strconv"
)

// A Node is an element in the parse tree. The interface is trivial.
// The interface contains an unexported method so that only
// types local to this package can satisfy it.
type Node interface {
	Type() NodeType
	String() string
	StringAST() string
	Position() Pos     // byte position of start of node in full original input string
	Check(*Tree) error // performs type checking for itself and sub-nodes
	Return() ReturnType

	// Make sure only functions in this package can create Nodes.
	unexported()
}

// NodeType identifies the type of a parse tree node.
type NodeType int

// Pos represents a byte position in the original input text from which
// this template was parsed.
type Pos int

// Position returns the integer Position of p
func (p Pos) Position() Pos {
	return p
}

// unexported keeps Node implementations local to the package.
// All implementations embed Pos, so this takes care of it.
func (Pos) unexported() {
}

// Type returns itself and provides an easy default implementation
// for embedding in a Node. Embedded in all non-trivial Nodes.
func (t NodeType) Type() NodeType {
	return t
}

const (
	// NodeFunc is a function call.
	NodeFunc NodeType = iota
	// NodeBinary is a binary operator: math, logical, compare
	NodeBinary
	// NodeUnary is unary operator: !, -
	NodeUnary
	// NodeString is string constant.
	NodeString
	// NodeNumber is a numerical constant (Scalar).
	NodeNumber
	// NodeVar is variable: $A
	NodeVar
)

// String returns the string representation of the NodeType
func (t NodeType) String() string {
	switch t {
	case NodeFunc:
		return "NodeFunc"
	case NodeBinary:
		return "NodeBinary"
	case NodeUnary:
		return "NodeUnary"
	case NodeString:
		return "NodeString"
	case NodeNumber:
		return "NodeNumber"
	default:
		return "NodeUnknown"
	}
}

// Nodes.

// VarNode holds a variable reference.
type VarNode struct {
	NodeType
	Pos
	Name string // Without the $ or {}
	Text string // Raw
}

func newVar(pos Pos, name, text string) *VarNode {
	return &VarNode{NodeType: NodeVar, Pos: pos, Name: name, Text: text}
}

// Type returns the Type of the VarNode so it fulfills the Node interface.
func (n *VarNode) Type() NodeType { return NodeVar }

// String returns the string representation of the VarNode so it fulfills the Node interface.
func (n *VarNode) String() string { return n.Text }

// StringAST returns the string representation of abstract syntax tree of the VarNode so it fulfills the Node interface.
func (n *VarNode) StringAST() string { return n.String() }

// Check performs parse time checking on the VarNode so it fulfills the Node interface.
func (n *VarNode) Check(*Tree) error {
	return nil
}

// Return returns the result type of the VarNode so it fulfills the Node interface.
func (n *VarNode) Return() ReturnType {
	return TypeSeriesSet // Vars are only time series for now I guess....
}

// FuncNode holds a function invocation.
type FuncNode struct {
	NodeType
	Pos
	Name   string
	F      *Func
	Args   []Node
	Prefix string
}

func newFunc(pos Pos, name string, f Func) *FuncNode {
	return &FuncNode{NodeType: NodeFunc, Pos: pos, Name: name, F: &f}
}

func (f *FuncNode) append(arg Node) {
	f.Args = append(f.Args, arg)
}

// String returns the string representation of the FuncNode so it fulfills the Node interface.
func (f *FuncNode) String() string {
	s := f.Name + "("
	for i, arg := range f.Args {
		if i > 0 {
			s += ", "
		}
		s += arg.String()
	}
	s += ")"
	return s
}

// StringAST returns the string representation of abstract syntax tree of the FuncNode so it fulfills the Node interface.
func (f *FuncNode) StringAST() string {
	s := f.Name + "("
	for i, arg := range f.Args {
		if i > 0 {
			s += ", "
		}
		s += arg.StringAST()
	}
	s += ")"
	return s
}

// Check performs parse time checking on the FuncNode so it fulfills the Node interface.
func (f *FuncNode) Check(t *Tree) error {
	if len(f.Args) < len(f.F.Args) {
		return fmt.Errorf("parse: not enough arguments for %s", f.Name)
	} else if len(f.Args) > len(f.F.Args) {
		return fmt.Errorf("parse: too many arguments for %s", f.Name)
	}

	for i, arg := range f.Args {
		funcType := f.F.Args[i]
		argType := arg.Return()
		// if funcType == TypeNumberSet && argType == TypeScalar {
		// 	argType = TypeNumberSet
		// }
		if funcType == TypeVariantSet {
			if !(argType == TypeNumberSet || argType == TypeSeriesSet || argType == TypeScalar) {
				return fmt.Errorf("parse: expected %v or %v for argument %v, got %v", TypeNumberSet, TypeSeriesSet, i, argType)
			}
		} else if funcType != argType {
			return fmt.Errorf("parse: expected %v, got %v for argument %v (%v)", funcType, argType, i, arg.String())
		}
		if err := arg.Check(t); err != nil {
			return err
		}
	}

	if f.F.Check != nil {
		return f.F.Check(t, f)
	}
	return nil
}

// Return returns the result type of the FuncNode so it fulfills the Node interface.
func (f *FuncNode) Return() ReturnType {
	return f.F.Return
}

// ScalarNode holds a number: signed or unsigned integer or float.
// The value is parsed and stored under all the types that can represent the value.
// This simulates in a small amount of code the behavior of Go's ideal constants.
type ScalarNode struct {
	NodeType
	Pos
	IsUint  bool    // Number has an unsigned integral value.
	IsFloat bool    // Number has a floating-point value.
	Uint64  uint64  // The unsigned integer value.
	Float64 float64 // The floating-point value.
	Text    string  // The original textual representation from the input.
}

func newNumber(pos Pos, text string) (*ScalarNode, error) {
	n := &ScalarNode{NodeType: NodeNumber, Pos: pos, Text: text}
	// Do integer test first so we get 0x123 etc.
	u, err := strconv.ParseUint(text, 0, 64) // will fail for -0.
	if err == nil {
		n.IsUint = true
		n.Uint64 = u
	}
	// If an integer extraction succeeded, promote the float.
	if n.IsUint {
		n.IsFloat = true
		n.Float64 = float64(n.Uint64)
	} else {
		f, err := strconv.ParseFloat(text, 64)
		if err == nil {
			n.IsFloat = true
			n.Float64 = f
			// If a floating-point extraction succeeded, extract the int if needed.
			if !n.IsUint && float64(uint64(f)) == f {
				n.IsUint = true
				n.Uint64 = uint64(f)
			}
		}
	}
	if !n.IsUint && !n.IsFloat {
		return nil, fmt.Errorf("illegal number syntax: %q", text)
	}
	return n, nil
}

// String returns the string representation of the ScalarNode so it fulfills the Node interface.
func (n *ScalarNode) String() string {
	return n.Text
}

// StringAST returns the string representation of abstract syntax tree of the ScalarNode so it fulfills the Node interface.
func (n *ScalarNode) StringAST() string {
	return n.String()
}

// Check performs parse time checking on the ScalarNode so it fulfills the Node interface.
func (n *ScalarNode) Check(*Tree) error {
	return nil
}

// Return returns the result type of the ScalarNode so it fulfills the Node interface.
func (n *ScalarNode) Return() ReturnType {
	return TypeScalar
}

// StringNode holds a string constant. The value has been "unquoted".
type StringNode struct {
	NodeType
	Pos
	Quoted string // The original text of the string, with quotes.
	Text   string // The string, after quote processing.
}

func newString(pos Pos, orig, text string) *StringNode {
	return &StringNode{NodeType: NodeString, Pos: pos, Quoted: orig, Text: text}
}

// String returns the string representation of the StringNode so it fulfills the Node interface.
func (s *StringNode) String() string {
	return s.Quoted
}

// StringAST returns the string representation of abstract syntax tree of the StringNode so it fulfills the Node interface.
func (s *StringNode) StringAST() string {
	return s.String()
}

// Check performs parse time checking on the StringNode so it fulfills the Node interface.
func (s *StringNode) Check(*Tree) error {
	return nil
}

// Return returns the result type of the TypeString so it fulfills the Node interface.
func (s *StringNode) Return() ReturnType {
	return TypeString
}

// BinaryNode holds two arguments and an operator.
type BinaryNode struct {
	NodeType
	Pos
	Args     [2]Node
	Operator item
	OpStr    string
}

func newBinary(operator item, arg1, arg2 Node) *BinaryNode {
	return &BinaryNode{NodeType: NodeBinary, Pos: operator.pos, Args: [2]Node{arg1, arg2}, Operator: operator, OpStr: operator.val}
}

// String returns the string representation of the BinaryNode so it fulfills the Node interface.
func (b *BinaryNode) String() string {
	return fmt.Sprintf("%s %s %s", b.Args[0], b.Operator.val, b.Args[1])
}

// StringAST returns the string representation of abstract syntax tree of the BinaryNode so it fulfills the Node interface.
func (b *BinaryNode) StringAST() string {
	return fmt.Sprintf("%s(%s, %s)", b.Operator.val, b.Args[0], b.Args[1])
}

// Check performs parse time checking on the BinaryNode so it fulfills the Node interface.
func (b *BinaryNode) Check(t *Tree) error {
	return nil
}

// Return returns the result type of the BinaryNode so it fulfills the Node interface.
func (b *BinaryNode) Return() ReturnType {
	t0 := b.Args[0].Return()
	t1 := b.Args[1].Return()
	if t1 > t0 {
		return t1
	}
	return t0
}

// UnaryNode holds one argument and an operator.
type UnaryNode struct {
	NodeType
	Pos
	Arg      Node
	Operator item
	OpStr    string
}

func newUnary(operator item, arg Node) *UnaryNode {
	return &UnaryNode{NodeType: NodeUnary, Pos: operator.pos, Arg: arg, Operator: operator, OpStr: operator.val}
}

// String returns the string representation of the UnaryNode so it fulfills the Node interface.
func (u *UnaryNode) String() string {
	return fmt.Sprintf("%s%s", u.Operator.val, u.Arg)
}

// StringAST returns the string representation of abstract syntax tree of the UnaryNode so it fulfills the Node interface.
func (u *UnaryNode) StringAST() string {
	return fmt.Sprintf("%s(%s)", u.Operator.val, u.Arg)
}

// Check performs parse time checking on the UnaryNode so it fulfills the Node interface.
func (u *UnaryNode) Check(t *Tree) error {
	switch rt := u.Arg.Return(); rt {
	case TypeNumberSet, TypeSeriesSet, TypeScalar:
		return u.Arg.Check(t)
	default:
		return fmt.Errorf(`parse: type error in %s, expected "number", got %s`, u, rt)
	}
}

// Return returns the result type of the UnaryNode so it fulfills the Node interface.
func (u *UnaryNode) Return() ReturnType {
	return u.Arg.Return()
}

// Walk invokes f on n and sub-nodes of n.
func Walk(n Node, f func(Node)) {
	f(n)
	switch n := n.(type) {
	case *BinaryNode:
		Walk(n.Args[0], f)
		Walk(n.Args[1], f)
	case *FuncNode:
		for _, a := range n.Args {
			Walk(a, f)
		}
	case *ScalarNode, *StringNode:
		// Ignore since these node types have no sub nodes.
	case *UnaryNode:
		Walk(n.Arg, f)
	default:
		panic(fmt.Errorf("other type: %T", n))
	}
}

// ReturnType represents the type that is returned from a node.
type ReturnType int

const (
	// TypeString is a single string.
	TypeString ReturnType = iota
	// TypeScalar is a unlabled number constant.
	TypeScalar
	// TypeNumberSet is a collection of labelled numbers.
	TypeNumberSet
	// TypeSeriesSet is a collection of labelled time series.
	TypeSeriesSet
	// TypeVariantSet is a collection of the same type Number, Series, or Scalar.
	TypeVariantSet
)

// String returns a string representation of the ReturnType.
func (f ReturnType) String() string {
	switch f {
	case TypeNumberSet:
		return "numberSet"
	case TypeString:
		return "string"
	case TypeSeriesSet:
		return "seriesSet"
	case TypeScalar:
		return "scalar"
	case TypeVariantSet:
		return "variant"
	default:
		return "unknown"
	}
}
