// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.

// term.go
package expr

import (
	"strings"
)

type termPriority uint32

const (
	priNone termPriority = iota
	priRange
	priBut
	priAssign
	priInsert
	priOr
	priAnd
	priNot
	priRelational
	priBitwiseOr
	priBitwiseAnd
	priBitwiseNot
	priSum
	priProduct
	priFraction
	priSelector
	priBinShift
	priSign
	priFact
	priIterValue
	priDefault
	priIncDec
	priDot
	priDereference
	priValue
)

type termPosition uint8

const (
	posLeaf termPosition = iota
	posInfix
	posPrefix
	posPostfix
	posMultifix
)

type evalFuncType func(ctx ExprContext, self *term) (v any, err error)

type term struct {
	tk       Token
	parent   *term
	children []*term
	position termPosition // operator position: leaf, infix, prefix, postfix, multifix
	priority termPriority // operator priority: higher value means higher priority
	evalFunc evalFuncType
}

func (s *term) Clone() (d *term) {
	var children []*term
	if s.children != nil {
		children = make([]*term, len(s.children))
		for i, c := range s.children {
			children[i] = c.Clone()
		}
	}
	d = &term{
		tk:       *s.tk.Clone(),
		parent:   s.parent,
		children: children,
		position: s.position,
		priority: s.priority,
		evalFunc: s.evalFunc,
	}
	return
}

func (term *term) String() string {
	var sb strings.Builder
	term.toString(&sb)
	return sb.String()
}

func (term *term) toString(sb *strings.Builder) {
	if term.position == posLeaf {
		sb.WriteString(term.tk.String())
	} else {
		sb.WriteByte('[')
		sb.WriteString(term.tk.String())
		if term.children != nil {
			sb.WriteByte('(')
			for i, c := range term.children {
				if i > 0 {
					sb.WriteByte(' ')
				}
				c.toString(sb)
			}
			sb.WriteByte(')')
		}
		sb.WriteByte(']')
	}
}

func (term *term) getChildrenCount() (count int) {
	if term.position == posLeaf || term.children == nil {
		count = 0
	} else {
		count = len(term.children)
	}
	return
}

func (term *term) getRoom() (room int) {
	switch term.position {
	case posLeaf:
		room = 0
	case posInfix:
		room = 2
	case posPostfix, posPrefix:
		room = 1
	default:
		panic("Invalid node position")
	}
	return
}

func (term *term) isComplete() bool {
	return term.getChildrenCount() == term.getRoom()
}

func (term *term) removeLastChild() (child *term) {
	if term.children != nil {
		child = term.children[len(term.children)-1]
		term.children = term.children[0 : len(term.children)-1]
	} else {
		panic("Can't get last child")
	}
	return
}

func (term *term) isLeaf() bool {
	return term.position == posLeaf
}

func (term *term) getPriority() termPriority {
	return term.priority
}

func (term *term) setParent(parent *term) {
	term.parent = parent
	if parent != nil && len(parent.children) < cap(parent.children) {
		parent.children = append(parent.children, term)
	}
}

func (term *term) symbol() Symbol {
	return term.tk.Sym
}

func (term *term) source() string {
	return term.tk.source
}

func (term *term) value() any {
	return term.tk.Value
}

func (term *term) compute(ctx ExprContext) (v any, err error) {
	if term.evalFunc == nil {
		err = term.Errorf("undefined eval-func for %q term", term.source())
	} else {
		v, err = term.evalFunc(ctx, term)
	}
	return
}

// func (term *term) toInt(computedValue any, valueDescription string) (i int, err error) {
// 	if index64, ok := computedValue.(int64); ok {
// 		i = int(index64)
// 	} else {
// 		err = term.Errorf("%s, got %s (%v)", valueDescription, TypeName(computedValue), computedValue)
// 	}
// 	return
// }

func (term *term) errIncompatibleTypes(leftValue, rightValue any) error {
	leftType := TypeName(leftValue)
	leftText := getFormatted(leftValue, Truncate)
	rightType := TypeName(rightValue)
	rightText := getFormatted(rightValue, Truncate)
	return term.tk.Errorf(
		"left operand '%s' [%s] and right operand '%s' [%s] are not compatible with operator %q",
		leftText, leftType,
		rightText, rightType,
		term.source())
}

func (term *term) errIncompatibleType(value any) error {
	return term.tk.Errorf(
		"prefix/postfix operator %q do not support operand '%v' [%s]",
		term.source(), value, TypeName(value))
}

func (term *term) errDivisionByZero() error {
	return term.tk.Errorf("division by zero")
}

func (term *term) Errorf(template string, args ...any) (err error) {
	err = term.tk.Errorf(template, args...)
	return
}

func (term *term) checkOperands() (err error) {
	switch term.position {
	case posInfix:
		if term.children == nil || len(term.children) != 2 || term.anyChildrenNil() {
			err = term.tk.Errorf("infix operator %q requires two non-nil operands, got %d", term.source(), term.getChildrenCount())
		}
	case posPrefix:
		if term.children == nil || len(term.children) != 1 || term.children[0] == nil {
			err = term.tk.Errorf("prefix operator %q requires one non-nil operand", term.tk.String())
		}
	case posPostfix:
		if term.children == nil || len(term.children) != 1 || term.anyChildrenNil() {
			err = term.tk.Errorf("postfix operator %q requires one non-nil operand", term.tk.String())
		}
	case posMultifix:
		if term.children == nil || len(term.children) < 3 || term.anyChildrenNil() {
			err = term.tk.Errorf("infix operator %q requires at least three not operands, got %d", term.source(), term.getChildrenCount())
		}
	}
	return
}

func (term *term) anyChildrenNil() bool {
	for _, child := range term.children {
		if child == nil {
			return true
		}
	}
	return false
}
func (term *term) evalInfix(ctx ExprContext) (leftValue, rightValue any, err error) {
	if err = term.checkOperands(); err == nil {
		if leftValue, err = term.children[0].compute(ctx); err == nil {
			rightValue, err = term.children[1].compute(ctx)
		}
	}
	return
}

func (term *term) evalPrefix(ctx ExprContext) (childValue any, err error) {
	if err = term.checkOperands(); err == nil {
		childValue, err = term.children[0].compute(ctx)
	}
	return
}

// NOTE Temporary solution to support function parameters with default value

func (t *term) forceChild(c *term) {
	if t.children == nil {
		t.children = make([]*term, 0, 1)
	}
	t.children = append(t.children, c)
}
