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

// operator-rel.go
package expr

import "reflect"

//-------- equal term

func newEqualTerm(tk *Token) (inst *term) {
	return &term{
		tk:       *tk,
		children: make([]*term, 0, 2),
		position: posInfix,
		priority: priRelational,
		evalFunc: evalEqual,
	}
}

type deepFuncTemplate func(a, b any) (eq bool, err error)

func equals(a, b any, deepCmp deepFuncTemplate) (eq bool, err error) {
	if isNumOrFract(a) && isNumOrFract(b) {
		if IsNumber(a) && IsNumber(b) {
			if IsInteger(a) && IsInteger(b) {
				li, _ := a.(int64)
				ri, _ := b.(int64)
				eq = li == ri
			} else {
				eq = numAsFloat(a) == numAsFloat(b)
			}
		} else {
			var cmp int
			if cmp, err = cmpAnyFract(a, b); err == nil {
				eq = cmp == 0
			}
		}
	} else if deepCmp != nil && IsList(a) && IsList(b) {
		eq, err = deepCmp(a, b)
	} else {
		eq = reflect.DeepEqual(a, b)
	}

	return
}

func evalEqual(ctx ExprContext, self *term) (v any, err error) {
	var leftValue, rightValue any

	if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
		return
	}

	v, err = equals(leftValue, rightValue, nil)
	return
}

//-------- not equal term

func newNotEqualTerm(tk *Token) (inst *term) {
	return &term{
		tk:       *tk,
		children: make([]*term, 0, 2),
		position: posInfix,
		priority: priRelational,
		evalFunc: evalNotEqual,
	}
}

func evalNotEqual(ctx ExprContext, self *term) (v any, err error) {
	if v, err = evalEqual(ctx, self); err == nil {
		b, _ := ToBool(v)
		v = !b
	}
	return
}

//-------- less term

func newLessTerm(tk *Token) (inst *term) {
	return &term{
		tk:       *tk,
		children: make([]*term, 0, 2),
		position: posInfix,
		priority: priRelational,
		evalFunc: evalLess,
	}
}

func lessThan(self *term, a, b any) (isLess bool, err error) {
	if isNumOrFract(a) && isNumOrFract(b) {
		if IsNumber(a) && IsNumber(b) {
			if IsInteger(a) && IsInteger(b) {
				li, _ := a.(int64)
				ri, _ := b.(int64)
				isLess = li < ri
			} else {
				isLess = numAsFloat(a) < numAsFloat(b)
			}
		} else {
			var cmp int
			if cmp, err = cmpAnyFract(a, b); err == nil {
				isLess = cmp < 0
			}
		}
	} else if IsString(a) && IsString(b) {
		ls, _ := a.(string)
		rs, _ := b.(string)
		isLess = ls < rs
		// Inclusion test
	} else if IsList(a) && IsList(b) {
		aList, _ := a.(*ListType)
		bList, _ := b.(*ListType)
		isLess = len(*aList) < len(*bList) && bList.contains(aList)
	} else {
		err = self.errIncompatibleTypes(a, b)
	}
	return
}

func evalLess(ctx ExprContext, self *term) (v any, err error) {
	var leftValue, rightValue any

	if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
		return
	}
	v, err = lessThan(self, leftValue, rightValue)
	return
}

//-------- less or equal term

func newLessEqualTerm(tk *Token) (inst *term) {
	return &term{
		tk:       *tk,
		children: make([]*term, 0, 2),
		position: posInfix,
		priority: priRelational,
		evalFunc: evalLessEqual,
	}
}

func lessThanOrEqual(self *term, a, b any) (isLessEq bool, err error) {
	if isLessEq, err = lessThan(self, a, b); err == nil {
		if !isLessEq {
			if IsList(a) && IsList(b) {
				isLessEq, err = sameContent(a, b)
			} else {
				isLessEq, err = equals(a, b, nil)
			}
		}
	}
	return
}

func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
	var leftValue, rightValue any

	if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
		return
	}

	v, err = lessThanOrEqual(self, leftValue, rightValue)

	return
}

//-------- greater term

func newGreaterTerm(tk *Token) (inst *term) {
	return &term{
		tk:       *tk,
		children: make([]*term, 0, 2),
		position: posInfix,
		priority: priRelational,
		evalFunc: evalGreater,
	}
}

func evalGreater(ctx ExprContext, self *term) (v any, err error) {
	var leftValue, rightValue any

	if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
		return
	}

	v, err = lessThan(self, rightValue, leftValue)
	return
}

//-------- greater or equal term

func newGreaterEqualTerm(tk *Token) (inst *term) {
	return &term{
		tk:       *tk,
		children: make([]*term, 0, 2),
		position: posInfix,
		priority: priRelational,
		evalFunc: evalGreaterEqual,
	}
}

func evalGreaterEqual(ctx ExprContext, self *term) (v any, err error) {
	var leftValue, rightValue any

	if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
		return
	}

	v, err = lessThanOrEqual(self, rightValue, leftValue)
	return
}

// init
func init() {
	registerTermConstructor(SymDoubleEqual, newEqualTerm)
	registerTermConstructor(SymNotEqual, newNotEqualTerm)
	registerTermConstructor(SymLess, newLessTerm)
	registerTermConstructor(SymLessOrEqual, newLessEqualTerm)
	registerTermConstructor(SymGreater, newGreaterTerm)
	registerTermConstructor(SymGreaterOrEqual, newGreaterEqualTerm)
}
