#Important/necessary libraries
import pyttsx3 #text to speech (backup/not using)
import webbrowser #to get URLs for google
import serial #to communicate with the arduino
import serial.tools.list_ports #to detect available serial ports
import wikipedia as wiki #gets information from wikipedia
import time #gets the time details
import os #operating system functions library
import speech_recognition as sr #Recognizes voice/takes speech
from time import ctime #gives us the time as a string
from gtts import gTTS #text to speech (google's)
import random #Generates file names
import pygame #to play audio files

#Initializes pygame to play audio
pygame.init()
pygame.mixer.init()

#Function to check if arduino is connected
def arduino_port():
    ports = serial.tools.list_ports.comports() #gets all the available ports 
    for port in ports:
        try:
            s = serial.Serial(port.device, 9600, timeout=1) #Tries to connect to the serial port
            time.sleep(2)  #wait for Arduino to reset
            s.write(b's')  #ask arduino if lamp is on
            time.sleep(0.5) #waits while arduino sends a answer 

            if s.in_waiting:
                response = s.readline().decode().strip() #reads the bytes from arduino and converts it to strings
                if response in ["on", "off"]:
                    s.close()
                    return port.device #returns the port being used
            s.close()
        except:
            pass
    return None #returns nothing if can't connect to arduino

#Function that converts text to speech that is played by pygame
def speak(text):
    tts = gTTS(text=text) #converts text to speech
    filename = f"voice_{random.randint(0, 10000)}.mp3" #generates a random filename for the audio
    tts.save(filename) #saves the file
    pygame.mixer.music.load(filename) #loads the file into pygame mixer to be played
    pygame.mixer.music.play() #plays the file

    #waits for speech to finish
    while pygame.mixer.music.get_busy():
        time.sleep(0.1)
    pygame.mixer.music.stop()

    #deletes the file(so new one can be made)
    time.sleep(0.5)
    for _ in range(5):
        try:
            os.remove(filename) #deletes audio file
            break
        except PermissionError:
            time.sleep(0.3) #waits before trying again if not deleted

#Function to listen to the user's input
def listen():
    r = sr.Recognizer() #Recognizer for recording speech
    with sr.Microphone() as source: 
        print("Listening")
        r.adjust_for_ambient_noise(source) #adjusts for background noise
        audio = r.listen(source) #records audio
    try:
        query = r.recognize_google(audio) #converts audio to text
        print("You said:", query)
        return query.lower() #makes the text lowercase
    except sr.UnknownValueError:
        return "" #returns nothing if it can't pick up audio
    except sr.RequestError:
        speak("Sorry, I am unable to connect to the speech service.")
        return ""

#Function to check if lamp is on 
def lamp_is_on(ser):
    if ser is None:
        return True  
    try:
        ser.write(b's')  #asks arduino if lamp is on 
        time.sleep(0.3)
        if ser.in_waiting:
            status = ser.readline().decode().strip()
            return status == "on" #returns true when lamp is on 
    except:
        return False #returns false is lamp is off
    return False

def main():
    port = arduino_port() #finding the arduino port 
    if port is not None:
        ser = serial.Serial(port, 9600, timeout=1) #opens serial connection
        time.sleep(2) 
        print(f"Connected to Arduino on {port}")
    else:
        ser = None
        print("Arduino not found. Running without lamp check.")

    speak("Welcome!")

    #Runs while lamp is on
    while True:
        if lamp_is_on(ser):
            query = listen() #listens to user
            
            #responds to hi/hey/hello
            if "hi" in query or "hello" in query or "hey" in query:
                speak("Hello! How can I help you?")
            
            #responds to time
            if "time" in query:
                speak(ctime())
            
            #responds to goodbye or bye
            if "goodbye" in query or "bye" in query:
                speak("Goodbye! Have a nice day.")
                break

            #responds to google
            if "google" in query:
                speak("What do you want to search on Google?")
                search_query = listen()
                if search_query:
                    url = "https://www.google.com/search?q=" + search_query.replace(" ", "+")
                    webbrowser.open(url) #opens the url/search result in google
                    speak(f"Here are the Google search results for {search_query}")
            
            #responds to youtube
            if "youtube" in query:
                speak("What do you want to search on YouTube?")
                search_query = listen()
                if search_query:
                    url = "https://www.youtube.com/results?search_query=" + search_query.replace(" ", "+")
                    webbrowser.open(url) #opens the youtube request on youtube
                    speak(f"Here are the YouTube search results for {search_query}")
            
            #responds to summarize
            if "summarize" in query or "summarise" in query:
                speak("What topic should I summarize?")
                topic = listen()
                if topic:
                    try:
                        summary = wiki.summary(topic, sentences=2) #gets a summary
                        speak(summary) #speaks the summary
                    except Exception:
                        speak("Sorry, I couldn't find any information on that topic.")
            
            #Does nothing if it could not understand/pick up what the user said 
            if query == "":
                pass

        else:
            print("Lamp is off. Assistant is inactive.")
            time.sleep(2)

if __name__ == "__main__":
    main() #starts the voice assistant