import speech_recognition as sr
import pygame
import os
import json
import nltk
import time
import webbrowser
import wave
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.model_selection import train_test_split
from sklearn.neural_network import MLPClassifier
from sklearn.preprocessing import LabelEncoder
import joblib
import librosa
import noisereduce as nr
from nltk.sentiment.vader import SentimentIntensityAnalyzer
from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
from gtts import gTTS
import tkinter as tk
from tkinter import messagebox, simpledialog, Toplevel
from tkinter import Button, Entry, Label, Listbox, StringVar
from PIL import Image, ImageTk
import datetime
from datetime import date

import requests
import serial
import pocketsphinx as ps
from transformers import Wav2Vec2ForCTC, Wav2Vec2Processor
from tkinter import messagebox
from tkinter import Button, Text, Label, Listbox
import pyautogui
import sounddevice as sd
from scipy.io import wavfile
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
import random
import distutils
from transformers import BertTokenizer, BertModel
import torch
from transformers import Wav2Vec2Model, Wav2Vec2Tokenizer
from sklearn.model_selection import train_test_split
import sys
import platform

# Έλεγχος συμβατότητας λειτουργικού συστήματος
if platform.system() != "Windows":
    print("Το πρόγραμμα είναι συμβατό μόνο με τα Windows.")
    sys.exit(1)

# Έλεγχος για CUDA
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# Αρχικοποίηση του Wav2Vec2 μοντέλου και του processor
processor = Wav2Vec2Processor.from_pretrained("facebook/wav2vec2-base-960h")
model = Wav2Vec2ForCTC.from_pretrained("facebook/wav2vec2-base-960h").to(device)

# Φόρτωση των υπαρχόντων ερωτήσεων-απαντήσεων και ιστορικού συνομιλίας
def load_qa_pairs_and_history():
    qa_file = "qa_data.json"
    history_file = "conversation_history.json"

    if os.path.exists(qa_file):
        with open(qa_file, 'r', encoding='utf-8') as f:
            qa_pairs = json.load(f)
    else:
        qa_pairs = {}

    if os.path.exists(history_file):
        with open(history_file, 'r', encoding='utf-8') as f:
            conversation_history = json.load(f)
    else:
        conversation_history = []

    return qa_pairs, conversation_history

qa_pairs, conversation_history = load_qa_pairs_and_history()

# Συνάρτηση για ακρόαση και αναγνώριση ομιλίας
def listen():
    recognizer = sr.Recognizer()
    with sr.Microphone() as source:
        print("Ακούω...")
        recognizer.adjust_for_ambient_noise(source)
        audio = recognizer.listen(source)

    try:
        print("Αναγνώριση...")
        text = recognizer.recognize_google(audio, language="el-GR")
        print(f"Αναγνωρίστηκε: {text}")
        return text.lower()
    except sr.UnknownValueError:
        print("Δεν αναγνωρίστηκε η ομιλία")
        return ""
    except sr.RequestError as e:
        print(f"Σφάλμα στην υπηρεσία αναγνώρισης ομιλίας: {e}")
        return ""

# Συνάρτηση για αποθήκευση ερωτήσεων-απαντήσεων και ιστορικού συνομιλίας
def save_qa_pairs_and_history(qa_pairs, conversation_history):
    qa_file = "qa_data.json"
    history_file = "conversation_history.json"

    with open(qa_file, 'w', encoding='utf-8') as f:
        json.dump(qa_pairs, f, ensure_ascii=False, indent=4)

    with open(history_file, 'w', encoding='utf-8') as f:
        json.dump(conversation_history, f, ensure_ascii=False, indent=4)

# Συνάρτηση για ανάλυση και εξαγωγή χαρακτηριστικών από κείμενο
def analyze_text(text):
    tokens = word_tokenize(text.lower())
    tokens = [word for word in tokens if word.isalpha()]
    tokens = [word for word in tokens if word not in stopwords.words('greek')]
    lemmatizer = WordNetLemmatizer()
    tokens = [lemmatizer.lemmatize(word) for word in tokens]
    preprocessed_text = ' '.join(tokens)

    sia = SentimentIntensityAnalyzer()
    sentiment = sia.polarity_scores(text)['compound']

    return preprocessed_text, sentiment

# Συνάρτηση για παροχή προσαρμοσμένων απαντήσεων με βάση το ιστορικό συνομιλίας
def get_response_with_history(question, conversation_history):
    preprocessed_question, sentiment = analyze_text(question)

    # Αναζήτηση σχετικών προηγούμενων αλληλεπιδράσεων
    relevant_history = [entry for entry in conversation_history if preprocessed_question in entry['question']]

    if relevant_history:
        # Προσαρμογή απάντησης με βάση το ιστορικό
        if sentiment > 0:
            tone = "θετική"
        elif sentiment < 0:
            tone = "αρνητική"
        else:
            tone = "ουδέτερη"

        response = f" {tone} "
    else:
        response = get_response_with_bert(question)

    return response

# Συνάρτηση για ακρόαση και αναγνώριση ομιλίας με PocketSphinx
def listen_pocketsphinx():
    recognizer = ps.Recognizer()
    microphone = ps.Microphone()

    try:
        with microphone as source:
            print("Ακούω...")
            recognizer.adjust_for_ambient_noise(source)
            audio = recognizer.listen(source)

        print("Αναγνώριση...")
        text = recognizer.recognize(audio)
        print(f"Αναγνωρίστηκε: {text}")
        return text.lower()

    except ps.pocketsphinx.error as e:
        print(f"Σφάλμα αναγνώρισης ομιλίας: {e}")
        return ""

# Συνάρτηση για ηχογράφηση και αποθήκευση σημειώσεων
def record_note():
    RATE = 44100
    CHANNELS = 1
    RECORD_SECONDS = 10

    speak("Παρακαλώ, πείτε μου τον τίτλο της σημείωσης.")
    title = listen()
    if not title:
        speak("Δεν κατάλαβα τον τίτλο. Χρησιμοποιώ προεπιλεγμένο τίτλο.")
        title = "Σημείωση"

    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    WAVE_OUTPUT_FILENAME = f"{title}_{timestamp}.wav"

    speak(f"Ξεκινάω την ηχογράφηση για {RECORD_SECONDS} δευτερόλεπτα. Πείτε 'τέλος' όταν θέλετε να σταματήσετε νωρίτερα.")
    recording = sd.rec(int(RECORD_SECONDS * RATE), samplerate=RATE, channels=CHANNELS)
    sd.wait()

    speak("Η ηχογράφηση ολοκληρώθηκε.")
    # Μετατροπή του numpy array σε bytes
    audio_data = recording.tobytes()

    recognizer = sr.Recognizer()
    audio = sr.AudioData(audio_data, RATE, 2)
    try:
        text = recognizer.recognize_google(audio, language="el-GR")
        print(f"Αναγνωρίστηκε: {text}")
        if "τέλος" in text.lower():
            speak("Εντοπίστηκε η λέξη 'τέλος'. Η ηχογράφηση θα περικοπεί.")
    except sr.UnknownValueError:
        print("Δεν αναγνωρίστηκε ομιλία")
    except sr.RequestError as e:
        print(f"Σφάλμα στην αναγνώριση ομιλίας: {e}")

    wavfile.write(WAVE_OUTPUT_FILENAME, RATE, recording)
    
    speak(f"Η σημείωση αποθηκεύτηκε ως {WAVE_OUTPUT_FILENAME}")

# Συνάρτηση για αποθήκευση ερωτήσεων-απαντήσεων
def save_qa_pairs(qa_pairs):
    qa_file = "qa_data.json"
    with open(qa_file, 'w', encoding='utf-8') as f:
        json.dump(qa_pairs, f, ensure_ascii=False, indent=4)

# Συνάρτηση για αναζήτηση στο διαδίκτυο
def search_web(query):
    try:
        query = query.replace("αναζήτηση για", "").strip()
        url = f"https://www.google.com/search?q={query}"
        webbrowser.open(url)
        speak(f"Ανοίγω τα αποτελέσματα αναζήτησης για '{query}' στο διαδίκτυο.")
    except Exception as e:
        print(f"Σφάλμα κατά την αναζήτηση στο διαδίκτυο: {e}")



# Κατέβασμα πακέτων NLTK
nltk.download('punkt')
nltk.download('wordnet')
nltk.download('stopwords')
nltk.download('vader_lexicon')

# Προεπεξεργασία κειμένου
def preprocess_text(text):
    tokens = word_tokenize(text.lower())
    tokens = [word for word in tokens if word.isalpha()]
    tokens = [word for word in tokens if word not in stopwords.words('greek')]
    lemmatizer = WordNetLemmatizer()
    tokens = [lemmatizer.lemmatize(word) for word in tokens]
    return ' '.join(tokens)

# Ανάλυση συναισθήματος
def analyze_sentiment(text):
    sia = SentimentIntensityAnalyzer()
    sentiment_score = sia.polarity_scores(text)['compound']
    
    if sentiment_score > 0:
        sentiment = "θετική"
    elif sentiment_score < 0:
        sentiment = "αρνητική"
    else:
        sentiment = "ουδέτερη"
    
    return sentiment

# Εξαγωγή χαρακτηριστικών ήχου με χρήση Wav2Vec
def extract_audio_embeddings(audio):
    inputs = processor(audio, return_tensors="pt").input_values
    outputs = model(inputs).last_hidden_state
    audio_embeddings = outputs.mean(dim=1).squeeze(0).detach().numpy()
    return audio_embeddings

# Λήψη απάντησης με χρήση BERT
def get_response_with_bert(question):
    preprocessed_question = preprocess_text(question)

    bert_model_name = "bert-base-uncased"
    bert_tokenizer = BertTokenizer.from_pretrained(bert_model_name)
    bert_model = BertModel.from_pretrained(bert_model_name)

    bert_inputs = bert_tokenizer(preprocessed_question, return_tensors="pt")
    bert_outputs = bert_model(**bert_inputs)
    last_hidden_state = bert_outputs.last_hidden_state

    question_embedding = torch.mean(last_hidden_state, dim=1).squeeze(0).detach().numpy()

    tfidf_vectorizer = TfidfVectorizer()
    questions = list(qa_pairs.keys())
    tfidf_matrix = tfidf_vectorizer.fit_transform(questions)
    tfidf_question = tfidf_vectorizer.transform([preprocessed_question])
    similarity_scores = cosine_similarity(tfidf_question, tfidf_matrix)

    similar_indices = similarity_scores[0].argsort()[-2:][::-1]
    similar_questions = [questions[index] for index in similar_indices]

    if similar_questions:
        return qa_pairs[similar_questions[0]]
    else:
        return "Δεν έχω ακόμα μάθει την απάντηση σε αυτή την ερώτηση. Μπορείτε να με διδάξετε!"

# Λίστα με ελληνικά κανάλια τηλεόρασης που έχουν live streaming
greek_tv_channels = {
    "άνοιξε άλφα": "https://alphatv.gr/live/",
    "αλφα": "https://alphatv.gr/live/",
    "alpha": "https://alphatv.gr/live/",
    "ant1": "https://www.antenna.gr/webtv/live",
    "αντ1": "https://www.antenna.gr/webtv/live",
    "skai": "https://www.skai.gr/tv/live",
    "σκαϊ": "https://www.skai.gr/tv/live",
    "star": "https://www.star.gr/tv/live-stream",
    "σταρ": "https://www.star.gr/tv/live-stream",
    "ert": "https://www.ertflix.gr/epg/channel/ert1-live",
    "ertflix": "https://www.ertflix.gr/epg/channel/ert1-live",
    "ερτ": "https://www.ertflix.gr/epg/channel/ert1-live",
    "ερτφλιχ": "https://www.ertflix.gr/epg/channel/ert1-live",
    "ερτflix": "https://www.ertflix.gr/epg/channel/ert1-live",
    "youtube": "https://www.youtube.com/",
    "netflix": "https://www.netflix.com/browse",
    "kastamonitis": "https://www.youtube.com/@christoskastamonitis",
    "κασταμονίτης": "https://www.youtube.com/@christoskastamonitis",
    "μπατίδης": "https://www.youtube.com/@BillBad2050/videos",
    "ΜΠΑΝΤΙΔΗΣ": "https://www.youtube.com/@BillBad2050/videos",
    "μπαντίδης": "https://www.youtube.com/@BillBad2050/videos",
    "καιρός": "https://www.okairos.gr/%CE%BC%CE%B5%CE%B3%CE%B1%CE%BB%CF%8C%CF%80%CE%BF%CE%BB%CE%B7.html?d=nw",
    "καιρό": "https://www.okairos.gr/%CE%BC%CE%B5%CE%B3%CE%AC%CE%BB%CE%BF%CF%80%CE%BF%CE%BB%CE%B7.html?d=nw",
    "tik tok": "https://www.tiktok.com/foryou",
    "Έντεχνα": "https://www.youtube.com/watch?v=nUCZkuwPwGg",
    "ελληνικά": "https://www.youtube.com/watch?v=nUCZkuwPwGg",
    "έντεχνα": "https://www.youtube.com/watch?v=nUCZkuwPwGg",
    "ράδιο δρόμος": "https://live24.gr/radio/dromos.jsp",
    "ραδιοδρομος": "https://live24.gr/radio/dromos.jsp",
    "πρώτο θέμα": "https://www.protothema.gr/",
    "in.gr": "https://www.in.gr//",
    "ιν.gr": "https://www.in.gr//",
    "φωτιά τώρα": "https://www.google.com/search?q=%CF%86%CF%89%CF%84%CE%B9%CE%AC%20%CF%84%CF%8E%CF%81%CE%B1",
    "σεισμός τώρα ": "https://www.google.com/search?q=%CF%83%CE%B5%CE%B9%CF%83%CE%BC%CF%8C%CF%82%20%CF%84%CF%8E%CF%81%CE%B1",
    "σεισμός τώρα ": "https://www.seismoi-live.gr/",
}

# Λειτουργία για άνοιγμα ελληνικού καναλιού τηλεόρασης
def open_greek_tv_channel(channel_name):
    channel_name = channel_name.lower()
    if channel_name in greek_tv_channels:
        url = greek_tv_channels[channel_name]
        webbrowser.open(url)
        speak(f"Άνοιξα το κανάλι {channel_name.upper()}")
    else:
        speak(f"Δεν βρέθηκε το κανάλι {channel_name.upper()}. Παρακαλώ δοκιμάστε ξανά.")

# Μετατροπή κειμένου σε ομιλία και αναπαραγωγή
def speak(text):
    try:
        tts = gTTS(text=text, lang='el')
        filename = "voice.mp3"
        tts.save(filename)

        pygame.mixer.init()
        pygame.mixer.music.load(filename)
        pygame.mixer.music.play()

        while pygame.mixer.music.get_busy():
            pygame.time.Clock().tick(20)

        pygame.mixer.music.stop()
        pygame.mixer.quit()

        os.remove(filename)

    except Exception as e:
        print(f"Σφάλμα κατά την αναπαραγωγή ομιλίας: {e}. Παρακαλώ ελέγξτε τη ρύθμιση ήχου και προσπαθήστε ξανά.")

# Κύρια λειτουργία του εικονικού βοηθού
def main():
    speak("Γεια σας! Είμαι η Μία, ο εικονικός σας βοηθός. Πώς μπορώ να σας βοηθήσω σήμερα;")

    while True:
        command = listen()
        if command:
            last_activity_time = time.time()

            if "έξοδος" in command or "σταμάτα" in command:
                speak("Αντίο! Ήταν χαρά μου που σας βοήθησα.")
                break
            
            elif "προσθήκη απάντησης" in command:
                speak("Πείτε μου την ερώτηση, παρακαλώ.")
                question = listen()
                if question:
                    speak("Τώρα, πείτε μου την απάντηση.")
                    answer = listen()
                    if answer:
                        qa_pairs[question] = answer
                        save_qa_pairs(qa_pairs)
                        speak("Η ερώτηση και η απάντηση προστέθηκαν με επιτυχία.")

            elif "προβολή απαντήσεων" in command:
                display_questions()
            elif "σημείωσε" in command:
                record_note()
            elif "άνοιξε youtube" in command:
                webbrowser.open("https://www.youtube.com")
                speak("Άνοιξα τη σελίδα του YouTube για εσάς.")
            elif "άνοιξε φώτα" in command:
                send_command("1")
                speak("Άνοιξα το pin 13 στο Arduino.")
                print("Άνοιξα το pin 13 στο Arduino.")
                print(f"Η τρέχουσα ώρα είναι {current_time}.")

            elif "κλείσε φώτα" in command:
                send_command("0")
                speak(" Eκλεισα το pin 13 στο Arduino.")
                print(" Eκλεισα το pin 13 στο Arduino.")
                print(f"Η τρέχουσα ώρα είναι {current_time}.")
            elif "τι ώρα είναι" in command:
                current_time = datetime.now().strftime("%H:%M")
                print(f"Η τρέχουσα ώρα είναι {current_time}.")
                speak(f"Η τρέχουσα ώρα είναι {current_time}.")
            elif "τι μέρα είναι" in command:
                today = date.today()
                current_day = today.strftime("%d, %A")
                speak(f"Σήμερα είναι {current_day}.")
            elif "διάβασε ειδήσεις" in command:
                read_news()
            elif "άνοιξε" in command.lower() and any(channel in command.lower() for channel in greek_tv_channels):
                for channel_name in greek_tv_channels:
                    if channel_name in command.lower():
                        open_greek_tv_channel(channel_name)
                        break
                speak("")
            elif "μέτρα" in command:
                open_coordinate_window()
            elif "κλείσε το παράθυρο" in command or "κλείσε" in command:
                try:
                    pyautogui.hotkey('ctrl', 'w')  # Για Windows/Linux
                    # pyautogui.hotkey('command', 'w')  # Για macOS
                    speak("Έκλεισα το παράθυρο του browser.")
                except Exception as e:
                    speak("Δεν μπόρεσα να κλείσω το παράθυρο. Παρουσιάστηκε σφάλμα.")
                    print(f"Σφάλμα κατά το κλείσιμο του παραθύρου: {e}")
            else:
                response = get_response_with_history(command, conversation_history)
                speak(response)

                timestamp = datetime.now().strftime("%Y-%m-%d %H")
                conversation_history.append({'question': command, 'answer': response, 'timestamp': timestamp})
                save_qa_pairs_and_history(qa_pairs, conversation_history)

if __name__ == "__main__":
    main()