import os
import gradio as gr
import nltk
import numpy as np
import tflearn
import random
import json
import pickle
from nltk.tokenize import word_tokenize
from nltk.stem.lancaster import LancasterStemmer
from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline
import googlemaps
import folium
import torch
import pandas as pd
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import LabelEncoder

# Suppress TensorFlow warnings
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"  # No GPU available, use CPU only
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"    # Suppress TensorFlow logging

# Download necessary NLTK resources
nltk.download("punkt")
stemmer = LancasterStemmer()

# Load intents and chatbot training data
with open("intents.json") as file:
    intents_data = json.load(file)

with open("data.pickle", "rb") as f:
    words, labels, training, output = pickle.load(f)

# Build the chatbot model
net = tflearn.input_data(shape=[None, len(training[0])])
net = tflearn.fully_connected(net, 8)
net = tflearn.fully_connected(net, 8)
net = tflearn.fully_connected(net, len(output[0]), activation="softmax")
net = tflearn.regression(net)
chatbot_model = tflearn.DNN(net)
chatbot_model.load("MentalHealthChatBotmodel.tflearn")

# Hugging Face sentiment and emotion models
tokenizer_sentiment = AutoTokenizer.from_pretrained("cardiffnlp/twitter-roberta-base-sentiment")
model_sentiment = AutoModelForSequenceClassification.from_pretrained("cardiffnlp/twitter-roberta-base-sentiment")
tokenizer_emotion = AutoTokenizer.from_pretrained("j-hartmann/emotion-english-distilroberta-base")
model_emotion = AutoModelForSequenceClassification.from_pretrained("j-hartmann/emotion-english-distilroberta-base")

# Google Maps API Client
gmaps = googlemaps.Client(key=os.getenv("GOOGLE_API_KEY"))

# Disease Prediction Code
def load_data():
    try:
        df = pd.read_csv("Training.csv")
        tr = pd.read_csv("Testing.csv")
    except FileNotFoundError:
        raise RuntimeError("Data files not found. Please ensure `Training.csv` and `Testing.csv` are uploaded correctly.")

    # Encode diseases
    disease_dict = {
        'Fungal infection': 0, 'Allergy': 1, 'GERD': 2, 'Chronic cholestasis': 3, 'Drug Reaction': 4,
        'Peptic ulcer diseae': 5, 'AIDS': 6, 'Diabetes ': 7, 'Gastroenteritis': 8, 'Bronchial Asthma': 9,
        'Hypertension ': 10, 'Migraine': 11, 'Cervical spondylosis': 12, 'Paralysis (brain hemorrhage)': 13,
        'Jaundice': 14, 'Malaria': 15, 'Chicken pox': 16, 'Dengue': 17, 'Typhoid': 18, 'hepatitis A': 19,
        'Hepatitis B': 20, 'Hepatitis C': 21, 'Hepatitis D': 22, 'Hepatitis E': 23, 'Alcoholic hepatitis': 24,
        'Tuberculosis': 25, 'Common Cold': 26, 'Pneumonia': 27, 'Dimorphic hemmorhoids(piles)': 28,
        'Heart attack': 29, 'Varicose veins': 30, 'Hypothyroidism': 31, 'Hyperthyroidism': 32,
        'Hypoglycemia': 33, 'Osteoarthritis': 34, 'Arthritis': 35,
        '(vertigo) Paroxysmal Positional Vertigo': 36, 'Acne': 37, 'Urinary tract infection': 38,
        'Psoriasis': 39, 'Impetigo': 40
    }

    # Replace prognosis values with numerical categories
    df.replace({'prognosis': disease_dict}, inplace=True)

    # Check unique values in prognosis for debugging
    print("Unique values in prognosis after mapping:", df['prognosis'].unique())

    # Ensure prognosis is purely numerical after mapping
    if df['prognosis'].dtype == 'object':  # Check for unmapped entries
        raise ValueError(f"The prognosis contains unmapped values: {df['prognosis'].unique()}")

    df['prognosis'] = df['prognosis'].astype(int)  # Convert to integer
    df = df.infer_objects()  # Remove 'copy' argument

    # Similar process for the testing data
    tr.replace({'prognosis': disease_dict}, inplace=True)

    # Check unique values in testing data
    print("Unique values in prognosis for testing data after mapping:", tr['prognosis'].unique())
    
    if tr['prognosis'].dtype == 'object':  # Check for unmapped entries
        raise ValueError(f"Testing data prognosis contains unmapped values: {tr['prognosis'].unique()}")

    tr['prognosis'] = tr['prognosis'].astype(int)  # Convert to integer if necessary
    tr = tr.infer_objects()  # Remove 'copy' argument

    return df, tr, disease_dict

df, tr, disease_dict = load_data()
l1 = list(df.columns[:-1])  # All columns except prognosis
X = df[l1]
y = df['prognosis']
X_test = tr[l1]
y_test = tr['prognosis']

# Encode the target variable with LabelEncoder if still in string format
le = LabelEncoder()
y_encoded = le.fit_transform(y)  # Encode string labels into integers

def train_models():
    models = {
        "Decision Tree": DecisionTreeClassifier(),
        "Random Forest": RandomForestClassifier(),
        "Naive Bayes": GaussianNB()
    }
    trained_models = {}
    for model_name, model_obj in models.items():
        model_obj.fit(X, y_encoded)  # Use encoded labels
        acc = accuracy_score(y_test, model_obj.predict(X_test))
        trained_models[model_name] = (model_obj, acc)
    return trained_models

trained_models = train_models()

def predict_disease(model, symptoms):
    input_test = np.zeros(len(l1))
    for symptom in symptoms:
        if symptom in l1:
            input_test[l1.index(symptom)] = 1
    prediction = model.predict([input_test])[0]
    confidence = model.predict_proba([input_test])[0][prediction] if hasattr(model, 'predict_proba') else None
    return {
        "disease": list(disease_dict.keys())[list(disease_dict.values()).index(prediction)],
        "confidence": confidence
    }

def disease_prediction_interface(symptoms):
    symptoms_selected = [s for s in symptoms if s != "None"]
    
    if len(symptoms_selected) < 3:
        return ["Please select at least 3 symptoms for accurate prediction."]
    
    results = []
    for model_name, (model, acc) in trained_models.items():
        prediction_info = predict_disease(model, symptoms_selected)
        predicted_disease = prediction_info["disease"]
        confidence_score = prediction_info["confidence"]
        
        result = f"{model_name} Prediction: Predicted Disease: **{predicted_disease}**"
        if confidence_score is not None:
            result += f" (Confidence: {confidence_score:.2f})"
        result += f" (Accuracy: {acc * 100:.2f}%)"
        
        results.append(result)

    return results

# Helper Functions (for chatbot)
def bag_of_words(s, words):
    bag = [0] * len(words)
    s_words = word_tokenize(s)
    s_words = [stemmer.stem(word.lower()) for word in s_words if word.isalnum()]
    for se in s_words:
        for i, w in enumerate(words):
            if w == se:
                bag[i] = 1
    return np.array(bag)

def generate_chatbot_response(message, history):
    history = history or []
    try:
        result = chatbot_model.predict([bag_of_words(message, words)])
        tag = labels[np.argmax(result)]
        response = next((random.choice(intent["responses"]) for intent in intents_data["intents"] if intent["tag"] == tag), "I'm sorry, I didn't understand that. 🤔")
    except Exception as e:
        response = f"Error: {e}"
    history.append((message, response))
    return history, response

def analyze_sentiment(user_input):
    inputs = tokenizer_sentiment(user_input, return_tensors="pt")
    with torch.no_grad():
        outputs = model_sentiment(**inputs)
    sentiment_class = torch.argmax(outputs.logits, dim=1).item()
    sentiment_map = ["Negative 😔", "Neutral 😐", "Positive 😊"]
    return f"Sentiment: {sentiment_map[sentiment_class]}"

def detect_emotion(user_input):
    pipe = pipeline("text-classification", model=model_emotion, tokenizer=tokenizer_emotion)
    result = pipe(user_input)
    emotion = result[0]["label"].lower().strip()
    emotion_map = {
        "joy": "Joy 😊",
        "anger": "Anger 😠",
        "sadness": "Sadness 😢",
        "fear": "Fear 😨",
        "surprise": "Surprise 😲",
        "neutral": "Neutral 😐",
    }
    return emotion_map.get(emotion, "Unknown 🤔"), emotion

def generate_suggestions(emotion):
    emotion_key = emotion.lower()
    suggestions = {
        # Define suggestions based on the detected emotion
    }
    
    formatted_suggestions = [
        [title, f'<a href="{link}" target="_blank">{link}</a>'] for title, link in suggestions.get(emotion_key, [["No specific suggestions available.", "#"]])
    ]
    return formatted_suggestions

def get_health_professionals_and_map(location, query):
    """Search nearby healthcare professionals using Google Maps API."""
    try:
        if not location or not query:
            return [], ""  # Return empty list if inputs are missing

        geo_location = gmaps.geocode(location)
        if geo_location:
            lat, lng = geo_location[0]["geometry"]["location"].values()
            places_result = gmaps.places_nearby(location=(lat, lng), radius=10000, keyword=query)["results"]
            professionals = []
            map_ = folium.Map(location=(lat, lng), zoom_start=13)
            for place in places_result:
                professionals.append([place['name'], place.get('vicinity', 'No address provided')])
                folium.Marker(
                    location=[place["geometry"]["location"]["lat"], place["geometry"]["location"]["lng"]],
                    popup=f"{place['name']}"
                ).add_to(map_)
            return professionals, map_._repr_html_()
        return [], ""  # Return empty list if no professionals found
    except Exception as e:
        return [], ""  # Return empty list on exception

# Main Application Logic
def app_function(user_input, location, query, symptoms, history):
    chatbot_history, _ = generate_chatbot_response(user_input, history)
    sentiment_result = analyze_sentiment(user_input)
    emotion_result, cleaned_emotion = detect_emotion(user_input)
    suggestions = generate_suggestions(cleaned_emotion)
    professionals, map_html = get_health_professionals_and_map(location, query)
    disease_results = disease_prediction_interface(symptoms)
    
    return (
        chatbot_history, 
        sentiment_result, 
        emotion_result, 
        suggestions, 
        professionals, 
        map_html,
        disease_results
    )

# CSS Styling
custom_css = """
body {
    font-family: 'Roboto', sans-serif;
    background-color: #3c6487;  /* Set the background color */
    color: white;
}

h1 {
    background: #ffffff;
    color: #000000;
    border-radius: 8px;
    padding: 10px;
    font-weight: bold;
    text-align: center;
    font-size: 2.5rem;
}

textarea, input {
    background: transparent;
    color: black;
    border: 2px solid orange;
    padding: 8px;
    font-size: 1rem;
    caret-color: black;
    outline: none;
    border-radius: 8px;
}

textarea:focus, input:focus {
    background: transparent;
    color: black;
    border: 2px solid orange;
    outline: none;
}

.df-container {
    background: white;
    color: black;
    border: 2px solid orange;
    border-radius: 10px;
    padding: 10px;
    font-size: 14px;
    max-height: 400px;
    height: auto;
    overflow-y: auto;
}

#suggestions-title {
    text-align: center !important; /* Ensure the centering is applied */
    font-weight: bold !important; /* Ensure bold is applied */
    color: white !important; /* Ensure color is applied */
    font-size: 4.2rem !important; /* Ensure font size is applied */
    margin-bottom: 20px !important; /* Ensure margin is applied */
}

/* Style for the submit button */
.gr-button {
    background-color: #ae1c93;  /* Set the background color to #ae1c93 */
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.06);
    transition: background-color 0.3s ease;
}

.gr-button:hover {
    background-color: #8f167b;
}

.gr-button:active {
    background-color: #7f156b;
}
"""

# Gradio Application
with gr.Blocks(css=custom_css) as app:
    gr.HTML("<h1>🌟 Well-Being Companion</h1>")
    
    with gr.Row():
        user_input = gr.Textbox(label="Please Enter Your Message Here")
        location = gr.Textbox(label="Your Current Location Here")
        query = gr.Textbox(label="Search Health Professionals Nearby")
        
    with gr.Row():
        symptom1 = gr.Dropdown(choices=["None"] + l1, label="Symptom 1")
        symptom2 = gr.Dropdown(choices=["None"] + l1, label="Symptom 2")
        symptom3 = gr.Dropdown(choices=["None"] + l1, label="Symptom 3")
        symptom4 = gr.Dropdown(choices=["None"] + l1, label="Symptom 4")
        symptom5 = gr.Dropdown(choices=["None"] + l1, label="Symptom 5")

    submit = gr.Button(value="Submit", variant="primary")

    chatbot = gr.Chatbot(label="Chat History")
    sentiment = gr.Textbox(label="Detected Sentiment")
    emotion = gr.Textbox(label="Detected Emotion")
    
    gr.Markdown("Suggestions", elem_id="suggestions-title")
    
    suggestions = gr.DataFrame(headers=["Title", "Link"])  # Suggestions DataFrame
    professionals = gr.DataFrame(label="Nearby Health Professionals", headers=["Name", "Address"])  # Professionals DataFrame
    map_html = gr.HTML(label="Interactive Map")
    disease_predictions = gr.Textbox(label="Disease Predictions")  # For Disease Prediction Results

    submit.click(
        app_function,
        inputs=[user_input, location, query, [symptom1, symptom2, symptom3, symptom4, symptom5], chatbot],
        outputs=[chatbot, sentiment, emotion, suggestions, professionals, map_html, disease_predictions],
    )

app.launch()
runtime error
Exit code: 1. Reason: :164: calling TruncatedNormal.__init__ (from tensorflow.python.ops.init_ops) with dtype is deprecated and will be removed in a future version.
Instructions for updating:
Call initializer instance with the dtype argument instead of passing it to the constructor
2024-12-03 15:48:44.823351: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcuda.so.1'; dlerror: libcuda.so.1: cannot open shared object file: No such file or directory
2024-12-03 15:48:44.823380: W tensorflow/stream_executor/cuda/cuda_driver.cc:269] failed call to cuInit: UNKNOWN ERROR (303)
2024-12-03 15:48:44.823398: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:156] kernel driver does not appear to be running on this host (r-dreamstream-1-testing-2-y4slnulx-15a15-4s1kq): /proc/driver/nvidia/version does not exist
2024-12-03 15:48:44.823573: I tensorflow/core/platform/cpu_feature_guard.cc:193] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 AVX512F AVX512_VNNI FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2024-12-03 15:48:44.973150: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:354] MLIR V1 optimization pass is not enabled
Unique values in prognosis after mapping: [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
 28 29 30 31 32 33 'Osteoarthristis' 35
 '(vertigo) Paroymsal  Positional Vertigo' 37 38 39 40]
Traceback (most recent call last):
  File "/home/user/app/app.py", line 104, in <module>
    df, tr, disease_dict = load_data()
  File "/home/user/app/app.py", line 85, in load_data
    raise ValueError(f"The prognosis contains unmapped values: {df['prognosis'].unique()}")
ValueError: The prognosis contains unmapped values: [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
 28 29 30 31 32 33 'Osteoarthristis' 35
 '(vertigo) Paroymsal  Positional Vertigo' 37 38 39 40]