import sympy as sp
import numpy as np
import re

def parse_input(user_input):
    """
    Parse the user input and determine the type of mathematical operation.
    """
    user_input = user_input.lower()
    
    if user_input.startswith("solve for"):
        return "solve"
    elif user_input.startswith("differentiate") or user_input.startswith("derive"):
        return "differentiate"
    elif user_input.startswith("integrate"):
        return "integrate"
    elif user_input.startswith("simplify"):
        return "simplify"
    elif user_input.startswith("factor"):
        return "factor"
    elif user_input.startswith("evaluate"):
        return "evaluate"
    elif user_input.startswith("matrix"):
        if "add" in user_input:
            return "matrix_add"
        elif "multiply" in user_input:
            return "matrix_multiply"
        elif "invert" in user_input:
            return "matrix_invert"
    else:
        # Attempt to identify basic arithmetic operations
        arithmetic_ops = ['add', 'subtract', 'multiply', 'divide']
        for op in arithmetic_ops:
            if op in user_input:
                return f"arithmetic_{op}"
    return "unknown"

def solve_equation(equation):
    """
    Solve algebraic equations for a variable.
    """
    try:
        # Extract the variable to solve for
        match = re.match(r"solve for (\w+) in (.+)", equation)
        if not match:
            return "Please provide input in the format: solve for x in equation"
        var, expr = match.groups()
        var = sp.Symbol(var)
        eq = sp.sympify(expr)
        solutions = sp.solve(eq, var)
        steps = f"Solving for {var} in {expr}:\n{var} = {sp.solve(eq, var)}"
        return steps
    except Exception as e:
        return f"Error solving equation: {e}"

def differentiate(expr):
    """
    Differentiate the given expression.
    """
    try:
        match = re.match(r"(differentiate|derive) (.+) with respect to (\w+)", expr)
        if not match:
            return "Please provide input in the format: differentiate expression with respect to variable"
        _, expression, var = match.groups()
        variable = sp.Symbol(var)
        derivative = sp.diff(expression, variable)
        steps = f"Differentiate {expression} with respect to {var}:\n{sp.pretty(derivative)}"
        return steps
    except Exception as e:
        return f"Error differentiating expression: {e}"

def integrate(expr):
    """
    Integrate the given expression.
    """
    try:
        match = re.match(r"integrate (.+) with respect to (\w+)", expr)
        if not match:
            return "Please provide input in the format: integrate expression with respect to variable"
        expression, var = match.groups()
        variable = sp.Symbol(var)
        integral = sp.integrate(expression, variable)
        steps = f"Integrate {expression} with respect to {var}:\n{sp.pretty(integral)} + C"
        return steps
    except Exception as e:
        return f"Error integrating expression: {e}"

def simplify_expression(expr):
    """
    Simplify the given expression.
    """
    try:
        match = re.match(r"simplify (.+)", expr)
        if not match:
            return "Please provide input in the format: simplify expression"
        expression = match.group(1)
        simplified = sp.simplify(expression)
        steps = f"Simplify {expression}:\n{sp.pretty(simplified)}"
        return steps
    except Exception as e:
        return f"Error simplifying expression: {e}"

def factor_polynomial(expr):
    """
    Factor the given polynomial expression.
    """
    try:
        match = re.match(r"factor (.+)", expr)
        if not match:
            return "Please provide input in the format: factor expression"
        expression = match.group(1)
        factored = sp.factor(expression)
        steps = f"Factor {expression}:\n{sp.pretty(factored)}"
        return steps
    except Exception as e:
        return f"Error factoring expression: {e}"

def evaluate_function(expr):
    """
    Evaluate the function at a given value.
    """
    try:
        match = re.match(r"evaluate (.+) at (\w+) *= *(.+)", expr)
        if not match:
            return "Please provide input in the format: evaluate function at x = value"
        function, var, value = match.groups()
        variable = sp.Symbol(var)
        func = sp.sympify(function)
        val = func.subs(variable, float(value))
        steps = f"Evaluate {function} at {var} = {value}:\n{val}"
        return steps
    except Exception as e:
        return f"Error evaluating function: {e}"

def perform_matrix_operation(operation, user_input):
    """
    Perform matrix operations: add, multiply, invert.
    """
    try:
        if operation == "matrix_add":
            # Expect two matrices separated by 'and'
            matrices = user_input.split("and")
            if len(matrices) != 2:
                return "Please provide two matrices separated by 'and'."
            mat1 = parse_matrix(matrices[0].replace("matrix add", "").strip())
            mat2 = parse_matrix(matrices[1].strip())
            if mat1 is None or mat2 is None:
                return "Error parsing matrices."
            result = np.add(mat1, mat2)
            steps = f"Adding matrices:\n{mat1}\n+\n{mat2}\n=\n{result}"
            return steps
        elif operation == "matrix_multiply":
            # Expect two matrices separated by 'and'
            matrices = user_input.split("and")
            if len(matrices) != 2:
                return "Please provide two matrices separated by 'and'."
            mat1 = parse_matrix(matrices[0].replace("matrix multiply", "").strip())
            mat2 = parse_matrix(matrices[1].strip())
            if mat1 is None or mat2 is None:
                return "Error parsing matrices."
            result = np.matmul(mat1, mat2)
            steps = f"Multiplying matrices:\n{mat1}\n*\n{mat2}\n=\n{result}"
            return steps
        elif operation == "matrix_invert":
            # Expect one matrix
            mat = parse_matrix(user_input.replace("matrix invert", "").strip())
            if mat is None:
                return "Error parsing matrix."
            if mat.shape[0] != mat.shape[1]:
                return "Only square matrices can be inverted."
            inv = np.linalg.inv(mat)
            steps = f"Inverting matrix:\n{mat}\n=\n{inv}"
            return steps
    except np.linalg.LinAlgError:
        return "Matrix is singular and cannot be inverted."
    except Exception as e:
        return f"Error performing matrix operation: {e}"

def parse_matrix(matrix_str):
    """
    Parse a string representation of a matrix into a NumPy array.
    Example input: [[1,2],[3,4]]
    """
    try:
        matrix = eval(matrix_str)
        return np.array(matrix)
    except:
        return None

def perform_arithmetic(operation, user_input):
    """
    Perform basic arithmetic operations: add, subtract, multiply, divide.
    """
    try:
        # Extract the numbers
        numbers = re.findall(r"[-+]?\d*\.\d+|\d+", user_input)
        numbers = [float(num) for num in numbers]
        if operation == "arithmetic_add":
            result = sum(numbers)
            steps = f"Adding {numbers} = {result}"
        elif operation == "arithmetic_subtract":
            result = numbers[0]
            for num in numbers[1:]:
                result -= num
            steps = f"Subtracting {numbers} = {result}"
        elif operation == "arithmetic_multiply":
            result = 1
            for num in numbers:
                result *= num
            steps = f"Multiplying {numbers} = {result}"
        elif operation == "arithmetic_divide":
            result = numbers[0]
            try:
                for num in numbers[1:]:
                    result /= num
            except ZeroDivisionError:
                return "Error: Division by zero."
            steps = f"Dividing {numbers} = {result}"
        return steps
    except Exception as e:
        return f"Error performing arithmetic operation: {e}"

def main():
    print("Welcome to the Math Solver!")
    print("You can enter your math problem in natural language.")
    print("Type 'exit' to quit.\n")
    
    while True:
        user_input = input("Enter your math problem: ")
        if user_input.lower() in ["exit", "quit"]:
            print("Goodbye!")
            break
        
        operation = parse_input(user_input)
        
        if operation == "solve":
            result = solve_equation(user_input)
        elif operation == "differentiate":
            result = differentiate(user_input)
        elif operation == "integrate":
            result = integrate(user_input)
        elif operation == "simplify":
            result = simplify_expression(user_input)
        elif operation == "factor":
            result = factor_polynomial(user_input)
        elif operation == "evaluate":
            result = evaluate_function(user_input)
        elif operation.startswith("matrix"):
            result = perform_matrix_operation(operation, user_input)
        elif operation.startswith("arithmetic"):
            result = perform_arithmetic(operation, user_input)
        else:
            result = "I'm sorry, I couldn't understand that problem. Please try a different format."
        
        print("\n" + result + "\n" + "-"*50 + "\n")

if __name__ == "__main__":
    main()