package utils

import (
	"fmt"
	"strconv"
	"strings"
	"unicode"
)

// EvaluateFormula calculates the result of a mathematical formula with variable 'x'
// Supported operators: +, -, *, /
// Supported grouping: ( )
func EvaluateFormula(formula string, x float64) (float64, error) {
	if strings.TrimSpace(formula) == "" {
		return x, nil
	}

	postfix, err := infixToPostfix(formula)
	if err != nil {
		return 0, err
	}

	return evaluatePostfix(postfix, x)
}

func infixToPostfix(formula string) ([]string, error) {
	var output []string
	var stack []string // operator stack

	i := 0
	for i < len(formula) {
		char := formula[i]

		if unicode.IsSpace(rune(char)) {
			i++
			continue
		}

		// Number or Variable 'x'
		if unicode.IsDigit(rune(char)) || char == '.' || char == 'x' || char == 'X' {
			start := i
			if char == 'x' || char == 'X' {
				output = append(output, "x")
				i++
			} else {
				for i < len(formula) && (unicode.IsDigit(rune(formula[i])) || formula[i] == '.') {
					i++
				}
				output = append(output, formula[start:i])
			}
			continue
		}

		// Operators
		if isOperator(char) {
			for len(stack) > 0 && isOperator(stack[len(stack)-1][0]) && precedence(stack[len(stack)-1][0]) >= precedence(char) {
				output = append(output, stack[len(stack)-1])
				stack = stack[:len(stack)-1]
			}
			stack = append(stack, string(char))
			i++
			continue
		}

		// Parentheses
		if char == '(' {
			stack = append(stack, "(")
			i++
			continue
		}

		if char == ')' {
			for len(stack) > 0 && stack[len(stack)-1] != "(" {
				output = append(output, stack[len(stack)-1])
				stack = stack[:len(stack)-1]
			}
			if len(stack) == 0 {
				return nil, fmt.Errorf("mismatched parentheses")
			}
			stack = stack[:len(stack)-1] // pop '('
			i++
			continue
		}

		return nil, fmt.Errorf("invalid character: %c", char)
	}

	for len(stack) > 0 {
		if stack[len(stack)-1] == "(" {
			return nil, fmt.Errorf("mismatched parentheses")
		}
		output = append(output, stack[len(stack)-1])
		stack = stack[:len(stack)-1]
	}

	return output, nil
}

func evaluatePostfix(postfix []string, x float64) (float64, error) {
	var stack []float64

	for _, token := range postfix {
		if token == "x" || token == "X" {
			stack = append(stack, x)
		} else if val, err := strconv.ParseFloat(token, 64); err == nil {
			stack = append(stack, val)
		} else {
			// Operator
			if len(stack) < 2 {
				return 0, fmt.Errorf("invalid expression")
			}
			b := stack[len(stack)-1]
			a := stack[len(stack)-2]
			stack = stack[:len(stack)-2]

			var res float64
			switch token {
			case "+":
				res = a + b
			case "-":
				res = a - b
			case "*":
				res = a * b
			case "/":
				if b == 0 {
					return 0, fmt.Errorf("division by zero")
				}
				res = a / b
			default:
				return 0, fmt.Errorf("unknown operator: %s", token)
			}
			stack = append(stack, res)
		}
	}

	if len(stack) != 1 {
		return 0, fmt.Errorf("invalid expression result")
	}

	return stack[0], nil
}

func isOperator(c byte) bool {
	return c == '+' || c == '-' || c == '*' || c == '/'
}

func precedence(c byte) int {
	switch c {
	case '+', '-':
		return 1
	case '*', '/':
		return 2
	}
	return 0
}

