package main

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

// Define all token types
type TokenType int

const (
	EOF TokenType = iota
	Number
	Plus
	Minus
	Multiply
	Divide
	Power
	Sqrt
	Root
	Sin
	Cos
	Tan
	Cot
	LeftParen
	RightParen
	Variable
	Comma
)

// Token structure
type Token struct {
	Type  TokenType
	Value string
}

// Function to check if a rune is a space
func isSpace(r rune) bool {
	return unicode.IsSpace(r)
}

// Function to check if a rune is a digit
func isDigit(r rune) bool {
	return unicode.IsDigit(r)
}

// Function to check if a rune is an operator
func isOperator(r rune) bool {
	return strings.ContainsRune("+-*/^", r)
}

// Add a function to check if a rune is a letter
func isLetter(r rune) bool {
	return unicode.IsLetter(r)
}

// Function to convert an expression string into tokens
func tokenize(expr string) ([]Token, error) {
	var tokens []Token
	for len(expr) > 0 {
		expr = strings.TrimSpace(expr)
		if len(expr) == 0 {
			break
		}
		var t Token
		switch {
		case expr[0] == '+':
			t = Token{Plus, "+"}
			expr = expr[1:]
		case expr[0] == '-':
			if isDigit(rune(expr[1])) {
				num := strings.TrimLeftFunc(expr[1:], isDigit)
				t = Token{Number, "-" + expr[1:len(expr)-len(num)]}
				expr = num
			} else {
				t = Token{Minus, "-"}
				expr = expr[1:]
			}
		case expr[0] == '*':
			t = Token{Multiply, "*"}
			expr = expr[1:]
		case expr[0] == '/':
			t = Token{Divide, "/"}
			expr = expr[1:]
		case expr[0] == '(':
			t = Token{LeftParen, "("}
			expr = expr[1:]
		case expr[0] == ')':
			t = Token{RightParen, ")"}
			expr = expr[1:]
		case expr[0] == ',':
			t = Token{Comma, ","}
			expr = expr[1:]
		case expr[0] == '^':
			t = Token{Power, "^"}
			expr = expr[1:]
		case strings.HasPrefix(expr, "sqrt"):
			t = Token{Sqrt, "sqrt"}
			expr = expr[len("sqrt"):]
		case strings.HasPrefix(expr, "root"):
			t = Token{Root, "root"}
			expr = expr[len("root"):]
		case strings.HasPrefix(expr, "sin"):
			t = Token{Sin, "sin"}
			expr = expr[len("sin"):]
		case strings.HasPrefix(expr, "cos"):
			t = Token{Cos, "cos"}
			expr = expr[len("cos"):]
		case strings.HasPrefix(expr, "tan"):
			t = Token{Tan, "tan"}
			expr = expr[len("tan"):]
		case strings.HasPrefix(expr, "cot"):
			t = Token{Cot, "cot"}
			expr = expr[len("cot"):]
		case isDigit(rune(expr[0])) || (expr[0] == '.' && len(expr) > 1 && isDigit(rune(expr[1]))):
			// Найдите длину числовой последовательности
			var numLen int
			for ; numLen < len(expr) && (isDigit(rune(expr[numLen])) || expr[numLen] == '.'); numLen++ {
			}
			t = Token{Number, expr[:numLen]}
			expr = expr[numLen:]
		case isLetter(rune(expr[0])):
			varName := strings.TrimLeftFunc(expr, isLetter)
			t = Token{Variable, expr[:len(expr)-len(varName)]}
			expr = varName
		default:
			return nil, fmt.Errorf("Неизвестный символ: %c", expr[0])
		}

		tokens = append(tokens, t)
	}

	return tokens, nil
}

// Function to get the precedence of an operator
func precedence(op TokenType) int {
	switch op {
	case Power:
		return 4
	case Sqrt, Sin, Cos, Tan, Cot:
		return 3
	case Multiply, Divide:
		return 2
	case Plus, Minus:
		return 1
	case LeftParen, RightParen:
		return 0
	default:
		return 0
	}
}

// Function to evaluate an expression given its tokens
func eval(tokens []Token, variableMaps ...map[string]float64) (float64, error) {
	// Combine all the variable maps
	vars := make(map[string]float64)
	for _, variableMap := range variableMaps {
		for key, value := range variableMap {
			vars[key] = value
		}
	}

	applyOperation := func(op Token, args ...float64) (float64, error) {
		switch op.Type {
		case Plus:
			return args[0] + args[1], nil
		case Minus:
			return args[0] - args[1], nil
		case Multiply:
			return args[0] * args[1], nil
		case Divide:
			return args[0] / args[1], nil
		case Power:
			return math.Pow(args[0], args[1]), nil
		case Sqrt:
			return math.Sqrt(args[0]), nil
		case Root:
			return math.Pow(args[1], 1.0/args[0]), nil
		case Sin:
			return math.Sin(args[0]), nil
		case Cos:
			return math.Cos(args[0]), nil
		case Tan:
			return math.Tan(args[0]), nil
		case Cot:
			return 1 / math.Tan(args[0]), nil
		default:
			return 0, fmt.Errorf("Неизвестная операция: %v", op.Value)
		}
	}

	var numStack []float64
	var opStack []Token

	popNumStack := func() float64 {
		val := numStack[len(numStack)-1]
		numStack = numStack[:len(numStack)-1]
		return val
	}

	for i := 0; i < len(tokens); i++ {
		token := tokens[i]

		switch token.Type {
		case Comma:
			if i+1 < len(tokens) && tokens[i+1].Type != LeftParen {
				// Push both numbers onto numStack before processing the root operation
				numStack = append(numStack, popNumStack())
			} else {
				return 0, fmt.Errorf("Incorrect usage of comma")
			}
		case Root:
			// Save the start position of the index for parsing the root arguments later
			rootIndex := i

			// Skip the “root(” token first
			i++

			// Find the position of the comma separating the arguments of root
			j := i
			open := 1
			for ; open != 0 && j < len(tokens); j++ {
				if tokens[j].Type == LeftParen {
					open++
				}
				if tokens[j].Type == RightParen {
					open--
				}
				if open == 1 && tokens[j].Type == Comma {
					break
				}
			}
			if j == len(tokens) {
				return 0, fmt.Errorf("Mismatched parentheses")
			}

			// Evaluate the first argument of root (the base)
			baseTokens := tokens[rootIndex+1 : j]
			base, err := eval(baseTokens)
			if err != nil {
				return 0, err
			}

			// Evaluate the second argument of root (the value)
			valueTokens := tokens[j+1:]

			// Find the position of the closing parenthesis
			k := j + 1
			open = 1
			for ; open != 0 && k < len(tokens); k++ {
				if tokens[k].Type == LeftParen {
					open++
				}
				if tokens[k].Type == RightParen {
					open--
				}
			}
			if k == len(tokens) {
				return 0, fmt.Errorf("Mismatched parentheses")
			}

			valueTokens = tokens[j+1 : k-1]
			value, err := eval(valueTokens)
			if err != nil {
				return 0, err
			}

			// Calculate the result and push it to numStack
			result, err := applyOperation(Token{Root, "root"}, base, value)
			if err != nil {
				return 0, err
			}
			numStack = append(numStack, result)

			// Skip the rest of the root tokens
			i = k - 1

		case Number:
			val, err := strconv.ParseFloat(token.Value, 64)
			if err != nil {
				return 0, err
			}
			numStack = append(numStack, val)
		case Variable:
			val, ok := vars[token.Value]
			if !ok {
				return 0, fmt.Errorf("Отсутствует значение для переменной: %v", token.Value)
			}
			numStack = append(numStack, val)
		case Plus, Minus, Multiply, Divide, Power, Sqrt, Sin, Cos, Tan, Cot:
			for len(opStack) > 0 && precedence(token.Type) <= precedence(opStack[len(opStack)-1].Type) {
				op := opStack[len(opStack)-1]
				opStack = opStack[:len(opStack)-1]

				var args []float64
				if op.Type == Sqrt || op.Type == Sin || op.Type == Cos || op.Type == Tan || op.Type == Cot {
					args = []float64{popNumStack()}
				} else {
					args = []float64{popNumStack(), popNumStack()}
					args[0], args[1] = args[1], args[0] // Reverse the order
				}

				res, err := applyOperation(op, args...)
				if err != nil {
					return 0, err
				}

				numStack = append(numStack, res)
			}
			opStack = append(opStack, token)
		case LeftParen:
			opStack = append(opStack, token)
		case RightParen:
			for len(opStack) > 0 && opStack[len(opStack)-1].Type != LeftParen {
				op := opStack[len(opStack)-1]
				opStack = opStack[:len(opStack)-1]

				var args []float64
				if op.Type == Sqrt || op.Type == Sin || op.Type == Cos || op.Type == Tan || op.Type == Cot {
					args = []float64{popNumStack()}
				} else {
					args = []float64{popNumStack(), popNumStack()}
					args[0], args[1] = args[1], args[0] // Reverse the order
				}

				res, err := applyOperation(op, args...)
				if err != nil {
					return 0, err
				}

				numStack = append(numStack, res)
			}
			if len(opStack) > 0 && opStack[len(opStack)-1].Type == LeftParen {
				opStack = opStack[:len(opStack)-1]
			}
		}
	}

	for len(opStack) > 0 {
		op := opStack[len(opStack)-1]
		opStack = opStack[:len(opStack)-1]

		var args []float64
		if op.Type == Sqrt || op.Type == Sin || op.Type == Cos || op.Type == Tan || op.Type == Cot {
			args = []float64{popNumStack()}
		} else {
			args = []float64{popNumStack(), popNumStack()}
			args[0], args[1] = args[1], args[0] // Reverse the order
		}

		res, err := applyOperation(op, args...)
		if err != nil {
			return 0, err
		}

		numStack = append(numStack, res)
	}

	if len(numStack) != 1 {
		return 0, fmt.Errorf("Неверное количество аргументов")
	}

	return numStack[0], nil
}

func main() {
	expr := "root(3, 27)" // Вычисляет кубический корень из 27
	tokens, err := tokenize(expr)
	if err != nil {
		fmt.Println("Ошибка при токенизации:", err)
		return
	}

	result, err := eval(tokens)
	if err != nil {
		fmt.Println("Ошибка при вычислении:", err)
		return
	}
	fmt.Printf("%s = %v\n", expr, result)
}

Ошибка при вычислении: Mismatched parentheses