import numpy as np
import random
import re
import json
import pyttsx3
import speech_recognition as sr
from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
from textblob import TextBlob

class Bot:
    def __init__(self, name, jsonfile, math="on"):
        self.name = name
        self.conversations = self.load_conversations(jsonfile)
        self.engine = pyttsx3.init()
        self.math_enabled = math.lower() == "on"

    def preprocess_text(self, text):
        # Correct spelling mistakes using TextBlob
        corrected_text = str(TextBlob(text).correct())

        # Tokenization
        tokens = word_tokenize(corrected_text)

        # Stopword removal and remove non-alphabetic characters
        stop_words = set(stopwords.words('english'))
        tokens = [re.sub(r'[^a-zA-Z]', '', token).lower() for token in tokens if token.lower() not in stop_words]

        # Remove empty tokens
        tokens = [token for token in tokens if token]

        return tokens

    def generate_response(self, user_input):
        max_similarity = 0
        best_response = None
        try:
            for entry in self.conversations:
                question = entry["question"]
                question_tokens = self.preprocess_text(question)
                user_input_tokens = self.preprocess_text(user_input)
                common_tokens = set(question_tokens) & set(user_input_tokens)
                similarity = len(common_tokens) / max(len(question_tokens), len(user_input_tokens))

                if similarity > max_similarity:
                    max_similarity = similarity
                    best_response = entry.get("answers", ["I'm sorry, I don't have a response for that."])

            if best_response:
                return random.choice(best_response)
            else:
                if self.math_enabled and "math" in user_input.lower():
                    return self.handle_math_question(user_input)
                else:
                    return "I'm sorry, I didn't understand your question."

        except ZeroDivisionError:
            return "Minimum of 4 characters and/or numbers."

        except BaseException as e:
            return f"Error: {e}"

    def handle_math_question(self, user_input):
        math_functions = {
            "sin": np.sin,
            "cos": np.cos,
            "tan": np.tan,
            "asin": np.arcsin,
            "acos": np.arccos,
            "atan": np.arctan,
            "sqrt": np.sqrt,
            "log": np.log,
            "exp": np.exp
        }

        operands = ["+", "-", "*", "/", "^", "%", "//"]

        for function_name, function in math_functions.items():
            if function_name in user_input:
                argument = re.search(r'\d+', user_input).group()
                try:
                    result = function(float(argument))
                    return f"The result of {user_input} is {result}."
                except ValueError:
                    return f"Invalid argument for {function_name}."

        for operand in operands:
            if operand in user_input:
                numbers = re.findall(r'\d+', user_input)
                if operand == "+":
                    result = sum(map(int, numbers))
                elif operand == "-":
                    result = int(numbers[0]) - int(numbers[1])
                elif operand == "*":
                    result = int(numbers[0]) * int(numbers[1])
                elif operand == "/":
                    if int(numbers[1]) != 0:
                        result = int(numbers[0]) / int(numbers[1])
                    else:
                        return "Division by zero is not allowed."
                elif operand == "^":
                    result = int(numbers[0]) ** int(numbers[1])
                elif operand == "%":
                    result = int(numbers[0]) % int(numbers[1])
                elif operand == "//":
                    result = int(numbers[0]) // int(numbers[1])

                return f"The result of {user_input} is {result}."

        return "I'm sorry, I couldn't solve that math question."

    def speak(self, text):
        self.engine.say(text)
        self.engine.runAndWait()

    def load_conversations(self, file_path):
        with open(file_path, 'r') as file:
            return json.load(file)

    def listen_for_input(self):
        recognizer = sr.Recognizer()
        with sr.Microphone() as source:
            print("Listening...")
            recognizer.adjust_for_ambient_noise(source)  # Adjust microphone for ambient noise
            audio = recognizer.listen(source)  # Listen for audio input

        try:
            print("Recognizing...")
            user_input = recognizer.recognize_google(audio)  # Recognize speech using Google Speech Recognition
            print("You:", user_input)
            return user_input
        except sr.UnknownValueError:
            print("JZ: Sorry, I could not understand your speech.")
            return ""
        except sr.RequestError as e:
            print("JZ: Speech recognition request failed:", e)
            return ""

    def usrinput(self):
        while True:
            user_input = self.listen_for_input()  # Listen for user speech input
            if user_input.lower() == 'exit':
                break
            if user_input:
                bot_response = self.generate_response(user_input)
                print(f"{self.name}: {bot_response}")
                self.speak(bot_response)

