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 == Root {
				// Просто пропустите запятую
			} else {
				return 0, fmt.Errorf("Неправильное использование запятой")
			}
		case Root:
			// Обработка операции root
			if len(numStack) < 2 {
				return 0, fmt.Errorf("Недостаточно аргументов для операции root")
			}
			args := []float64{popNumStack(), popNumStack()}
			args[0], args[1] = args[1], args[0] // Поменять местами
			res, err := applyOperation(token, args...)
			if err != nil {
				return 0, err
			}
			numStack = append(numStack, res)

			// Пропустить следующий символ открывающей скобки ‘(’
			i++

		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)
}

Ошибка при вычислении: Недостаточно аргументов для операции root