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

// operator-prod.go
package expr

import (
	"strings"
)

//-------- multiply term

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

func mulValues(opTerm *term, leftValue, rightValue any) (v any, err error) {
	if IsString(leftValue) && IsInteger(rightValue) {
		s, _ := leftValue.(string)
		n, _ := rightValue.(int64)
		v = strings.Repeat(s, int(n))
	} else if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
		if IsFloat(leftValue) || IsFloat(rightValue) {
			v = numAsFloat(leftValue) * numAsFloat(rightValue)
		} else if isFraction(leftValue) || isFraction(rightValue) {
			v, err = mulAnyFract(leftValue, rightValue)
		} else {
			leftInt, _ := leftValue.(int64)
			rightInt, _ := rightValue.(int64)
			v = leftInt * rightInt
		}
	} else {
		err = opTerm.errIncompatibleTypes(leftValue, rightValue)
	}
	return
}

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

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

	return mulValues(prodTerm, leftValue, rightValue)
}

//-------- divide term

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

func divValues(opTerm *term, leftValue, rightValue any) (v any, err error) {
	if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
		if IsFloat(leftValue) || IsFloat(rightValue) {
			d := numAsFloat(rightValue)
			if d == 0.0 {
				err = opTerm.errDivisionByZero()
			} else {
				v = numAsFloat(leftValue) / d
			}
		} else if isFraction(leftValue) || isFraction(rightValue) {
			v, err = divAnyFract(leftValue, rightValue)
		} else {
			leftInt, _ := leftValue.(int64)
			if rightInt, _ := rightValue.(int64); rightInt == 0 {
				err = opTerm.errDivisionByZero()
			} else {
				v = leftInt / rightInt
			}
		}
	} else if IsString(leftValue) && IsString(rightValue) {
		source := leftValue.(string)
		sep := rightValue.(string)
		v = ListFromStrings(strings.Split(source, sep))
	} else if IsString(leftValue) && IsInteger(rightValue) {
		source := leftValue.(string)
		partSize := int(rightValue.(int64))
		if partSize == 0 {
			err = opTerm.errDivisionByZero()
		} else {
			partCount := len(source) / partSize
			remainder := len(source) % partSize
			listSize := partCount
			if remainder > 0 {
				listSize++
			}
			parts := make([]any, 0, listSize)
			for i := 0; i < partCount; i++ {
				parts = append(parts, source[i*partSize:(i+1)*partSize])
			}
			if remainder > 0 {
				parts = append(parts, source[len(source)-remainder:])
			}
			v = newList(parts)
		}
	} else {
		err = opTerm.errIncompatibleTypes(leftValue, rightValue)
	}
	return
}

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

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

	return divValues(opTerm, leftValue, rightValue)
}

//-------- divide as float term

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

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

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

	if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
		d := numAsFloat(rightValue)
		if d == 0.0 {
			err = floatDivTerm.errDivisionByZero()
		} else {
			v = numAsFloat(leftValue) / d
		}
	} else {
		err = floatDivTerm.errIncompatibleTypes(leftValue, rightValue)
	}
	return
}

//-------- reminder term

func newRemainderTerm(tk *Token) (inst *term) {
	return &term{
		tk:       *tk,
		children: make([]*term, 0, 2),
		position: posInfix,
		priority: priProduct,
		evalFunc: evalRemainder,
	}
}
func remainderValues(opTerm *term, leftValue, rightValue any) (v any, err error) {
	if IsInteger(leftValue) && IsInteger(rightValue) {
		rightInt, _ := rightValue.(int64)
		if rightInt == 0 {
			err = opTerm.errDivisionByZero()
		} else {
			leftInt, _ := leftValue.(int64)
			v = leftInt % rightInt
		}
	} else {
		err = opTerm.errIncompatibleTypes(leftValue, rightValue)
	}
	return
}

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

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

	return remainderValues(remainderTerm, leftValue, rightValue)
}

// init
func init() {
	registerTermConstructor(SymStar, newMultiplyTerm)
	registerTermConstructor(SymSlash, newDivideTerm)
	registerTermConstructor(SymDotSlash, newDivideAsFloatTerm)
	registerTermConstructor(SymPercent, newRemainderTerm)
}
