from flask import Flask, request, jsonify
from latex2sympy2 import latex2sympy, latex2latex
from timeout_decorator import timeout
from sympy import solve, latex, N
import multiprocessing
import time
import ast
import re
import queue

app = Flask(__name__)


# for stop process after 3 seconds of timeout
def solve_equation_with_timeout(equation, symbols, timeout):
    def worker():
        try:
            solution = solve(equation, symbols, dict=True)
            result_queue.put(solution)
        except Exception as e:
            result_queue.put(None)

    result_queue = multiprocessing.Queue()
    process = multiprocessing.Process(target=worker)
    process.start()
    process.join(timeout)
    if process.is_alive():
        process.terminate()
        return None
    return result_queue.get()

#float upto 2 decimal for numeric answers
def floor_to_two_decimal_places(number):
    rounded_number = round(number, 2)  # Round to two decimal places
    formatted_number = "{:.2f}".format(
        rounded_number
    )  # Format as a string with two decimal places
    return formatted_number.rstrip("0").rstrip(".")

#for detect equation is arithmetic or not
def is_arithmetic_equation(equation):
    valid_operators = ["+", "-", "*", "/", ":"]
    # Use regular expression to split based on arithmetic operators

    components = re.split(r"([+\-*/:])", equation.replace(" ", ""))

    # print(components)

    if len(components) % 2 != 1:
        # print('1', False)
        return False  # Incorrect number of components

    for i, component in enumerate(components):
        if i % 2 == 0:  # Operand position
            if not component.replace(".", "", 1).isdigit():
                # print('2', False)

                return False  # Operand is not numeric
        else:  # Operator position
            if component not in valid_operators:
                # print('3', False)

                return False  # Invalid operator

    # print(True)

    return True

def add_parentheses(match):
    return f"{match.group(1)} = ({match.group(2)})"

#to add paretheses after = in equations
def add_parentheses_to_equations(equations):
    # Define a regular expression pattern to find equations with '=' sign
    equation_list = equations.split(",")
    add_parentheses_to_equations = []
    for equation in equation_list:
        equation_pattern = re.compile(r"(\S+)\s*=\s*(.*?)(?:,|$)", re.M)
        add_parentheses_to_equations.append(
            equation_pattern.sub(add_parentheses, equation)
        )

    list_of_equations_with_parentheses = ",".join(add_parentheses_to_equations)
    # print(list_of_equations_with_parentheses)

    return list_of_equations_with_parentheses

#to remove = if equation have nothing after =
def remove_equal_and_whitespace_at_end(equation):
    # Define a regular expression pattern to check if '=' is at the very end of the equation
    pattern = re.compile(r"=\s*$")

    # Search for the pattern in the equation
    match = pattern.search(equation)

    if match is not None and match.end() == len(equation):
        # Remove the '=' sign and any trailing white spaces at the end
        modified_equation = equation[: -len(match.group())]
        return modified_equation
    else:
        return equation


def replace_equals_sign_2(match):
    if match.group(1) == "=":
        return "="


#for convert latex equation to system of equation
def convert_latex_system_to_comma_separated_list(latex_system):
    """This function converts a LaTeX system of equations to a comma-separated list of equations."""
    
    # Define regular expression patterns for LaTeX array environments
    latex_system_patterns = [
        r"\\begin{array} { l } (.*?)\\end{array}",
        r"\\begin{array} { c } (.*?)\\end{array}",
        r"\\begin{array} { r } (.*?)\\end{array}",
    ]

    # Match the regular expression patterns against the LaTeX system of equations.
    equations = None
    for pattern in latex_system_patterns:
        match = re.search(pattern, latex_system)
        if match:
            equations = match.group(1)
            break

    # If no match is found, return the original input
    if equations is None:
        return latex_system

    # Process the equations: remove equal signs and join with commas
    equation_list = [re.sub(r"(=)", replace_equals_sign_2, eq) for eq in equations.split("\\\\") if re.search(r"=", eq)]
    comma_separated_list_of_equations = ",".join(equation_list)

    return comma_separated_list_of_equations

#for extract variables from equation
def extract_variables_from_formula(formula):
    names = [i.id for i in ast.walk(ast.parse(formula)) if isinstance(i, ast.Name)]
    return list(set(names))


def replace_equals_sign(match):
    if match.group(1) == "=":
        return "-"

#for change language of messages of API
def app_language(selected_language, msg_type):
    language_messages = {
        "zh-hant": {
            "success_msg": "成功解決",
            "timeout_msg": "這個方程花了太長時間才求解",
            "not_solvable_msg": "方程不可解",
            "incorrect_eq_msg": "公式不正確",
            "request_method_msg": "選擇有效的方法",
        },
        "zh-hans": {
            "success_msg": "成功解决",
            "timeout_msg": "这个方程花了太长时间才求解",
            "not_solvable_msg": "方程不可解",
            "incorrect_eq_msg": "公式不正确",
            "request_method_msg": "选择有效的方法",
        },
        "ru": {
            "success_msg": "Успешно решено",
            "timeout_msg": "Решение уравнения заняло слишком много времени",
            "not_solvable_msg": "Уравнение неразрешимое",
            "incorrect_eq_msg": "неверное уравнение",
            "request_method_msg": "Выберите допустимый метод",
        },
        "es": {
            "success_msg": "Resuelto con éxito",
            "timeout_msg": "La ecuación tardó demasiado en resolverse",
            "not_solvable_msg": "Ecuación no resoluble",
            "incorrect_eq_msg": "Ecuación incorrecta",
            "request_method_msg": "Seleccione un método válido",
        },
        "de": {
            "success_msg": "Erfolgreich gelöst",
            "timeout_msg": "Die Lösung der Gleichung dauerte zu lange",
            "not_solvable_msg": "Gleichung nicht lösbar",
            "incorrect_eq_msg": "Falsche Gleichung",
            "request_method_msg": "Gültige Methode auswählen",
        },
        "fr": {
            "success_msg": "Résolu avec succès",
            "timeout_msg": "L’équation a pris trop de temps à résoudre",
            "not_solvable_msg": "Équation non résoluble",
            "incorrect_eq_msg": "équation incorrecte",
            "request_method_msg": "Sélectionner une méthode valide",
        },
        "ja": {
            "success_msg": "正常に解決されました",
            "timeout_msg": "方程式を解くのに時間がかかりすぎた",
            "not_solvable_msg": "方程式が解けない",
            "incorrect_eq_msg": "方程式が正しくない",
            "request_method_msg": "有効な方法を選択",
        },
        "ko": {
            "success_msg": "성공적으로 해결되었습니다.",
            "timeout_msg": "방정식을 푸는 데 시간이 너무 오래 걸렸습니다.",
            "not_solvable_msg": "방정식을 풀 수 없음",
            "incorrect_eq_msg": "잘못된 수식",
            "request_method_msg": "유효한 방법 선택",
        },
        "ar": {
            "success_msg": "تم حلها بنجاح",
            "timeout_msg": "استغرقت المعادلة وقتا طويلا لحلها",
            "not_solvable_msg": "المعادلة غير قابلة للحل",
            "incorrect_eq_msg": "معادلة غير صحيحة",
            "request_method_msg": "حدد طريقة صالحة",
        },
        "default": {
            "success_msg": "Successfully solved",
            "timeout_msg": "The equation took too long to solve",
            "not_solvable_msg": "Equation not solvable",
            "incorrect_eq_msg": "Incorrect equation",
            "request_method_msg": "Select valid method",
        },
    }

    language_code = selected_language if selected_language in language_messages else "default"
    return language_messages[language_code].get(msg_type, "")


@app.route("/AI_Math", methods=["POST", "GET"])
def AI_Math():
    
    selected_language = ""

    if request.method == "POST":
        try:
            latex_systems = request.form["equations"]

            selected_language = ""

            if 'language' in request.form :
                selected_language = request.form["language"]

            print("input", latex_systems)
            comma_separated_list_of_equations = []

            comma_separated_equation = convert_latex_system_to_comma_separated_list(
                latex_systems
            )
            comma_separated_list_of_equations.append(comma_separated_equation)

            modified_equations_else = []
            modified_equations_if = []

            # print(comma_separated_list_of_equations)
            
            for equations_set in comma_separated_list_of_equations:
                if re.search(r",", equations_set):
                    if re.search(r"=|=", equations_set):
                        equations_set1 = add_parentheses_to_equations(equations_set)
                        equations_set = re.sub(
                            r"(=)|=", replace_equals_sign, equations_set1
                        )
                    modified_equations_if.append(equations_set)
                else:
                    modified_equations_else.append(equations_set)

            for modified_equation in modified_equations_if:
                sympy_expr = latex2sympy(modified_equation)

                variables = extract_variables_from_formula(str(sympy_expr))
                # return variables

                pattern = r"^(?![a-zA-Z]+$)[a-zA-Z0-9_]+$|^[a-zA-Z]$"

                # Filter the variables to keep only single alphabets
                filtered_variables = [
                    var for var in variables if re.match(pattern, var)
                ]

                variables = tuple(filtered_variables)

                # timout is for 3 second if process takes more than 3 seconds than it will stop

                solution = solve_equation_with_timeout(sympy_expr, variables, timeout=3)
                if solution is not None:
                    latex_solution = latex(solution)
                    print("solution->sympy", latex_solution)
                    print("char_count", len(latex_solution))
                    # print('type', type(latex_solution))
                    # output_string = latex_solution.replace("\\", "")
                    # return //jsonify({'data': None, 'message': 'The equation took too long to solve', 'status': 400})
                    return {
                        "question": latex_systems,
                        "data": latex_solution,
                        "message": app_language(selected_language, "success_msg"),
                        "status": 200,
                    }
                else:
                    print("The equation took too long to solve.")
                    return jsonify(
                        {
                            "question": latex_systems,
                            "data": None,
                            "message": app_language(selected_language, "timeout_msg"),
                            "status": 400,
                        }
                    )

            for original_equation in modified_equations_else:
                # print("single equations---------->", original_equation)
                try:
                    print(remove_equal_and_whitespace_at_end(original_equation))
                    if (
                        is_arithmetic_equation(
                            remove_equal_and_whitespace_at_end(original_equation)
                        )
                        == True
                    ):
                        sol2 = latex2sympy(
                            remove_equal_and_whitespace_at_end(original_equation)
                        )
                        Nsol = N(sol2)
                        l2l = floor_to_two_decimal_places(Nsol)
                        print("N", l2l)
                        print("char_count", len(l2l))

                        return {
                            "question": latex_systems,
                            "data": str(l2l),
                            "message": app_language(selected_language, "success_msg"),
                            "status": 200,
                        }
                    else:
                        solutions = latex2latex(
                            remove_equal_and_whitespace_at_end(original_equation)
                        )
                        l2l = solutions
                        print("l2l", l2l)
                        print("char_count", len(l2l))

                        return {
                            "question": latex_systems,
                            "data": l2l,
                            "message": app_language(selected_language, "success_msg"),
                            "status": 200,
                        }

                except Exception as e:
                    print('exp1', e)
                    return jsonify(
                        {
                            "question": latex_systems,
                            "data": None,
                            "message": app_language(
                                selected_language, "not_solvable_msg"
                            ),
                            "status": 400,
                        }
                    )

        except ValueError as ve:
            print('ValueError', ve)

            return jsonify(
                {
                    "data": None,
                    "message": app_language(selected_language, "incorrect_eq_msg"),
                    "status": 400,
                }
            )
        except SyntaxError as se:
            print('SyntaxError', se)
        
            return jsonify(
                {
                    "data": None,
                    "message": app_language(selected_language, "incorrect_eq_msg"),
                    "status": 400,
                }
            )
        except:
            print('general', e)
        
            return jsonify(
                {
                    "data": None,
                    "message": app_language(selected_language, "incorrect_eq_msg"),
                    "status": 400,
                }
            )
    else:
        return jsonify(
            {
                "data": None,
                "message": app_language(selected_language, "request_method_msg"),
                "status": 400,
            }
        )


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=9004)