# """
# This module defines a Gradio-based web application for the Semantrix game. The application allows users to play the game in either Spanish or English, using different embedding models for word similarity.

# Modules:
#     gradio: Used for creating the web interface.
#     json: Used for loading configuration files.
#     game: Contains the Semantrix class for game logic.

# File Paths:
#     config_file_path: Path to the configuration file.
#     logo_path: Path to the logo image.
#     logo_win_path: Path to the winning logo image.

# Functions:
#     convert_to_markdown_centered(text):
#         Converts text to a centered markdown format for displaying game history and last attempt.

#     reset(difficulty, lang, model):
#         Resets the game state based on the selected difficulty, language, and model.

#     change(state, inp):
#         Changes the game state by incrementing the state variable.

#     update(state, radio, inp, hint):
#         Updates the game state and UI components based on the current state and user inputs.

# Gradio Components:
#     demo: The main Gradio Blocks component that contains the entire UI layout.
#     header: A Markdown component for displaying the game header.
#     state: A State component for tracking the current game state.
#     difficulty: A State component for tracking the difficulty level.
#     hint: A State component for tracking if a hint is provided.
#     img: An Image component for displaying the game logo.
#     ranking: A Markdown component for displaying the ranking.
#     out: A Textbox component for displaying game messages.
#     hint_out: A Textbox component for displaying hints.
#     radio: A Radio component for user selections.
#     inp: A Textbox component for user input.
#     but: A Button component for several actions.
#     give_up: A Button component for giving up.
#     reload: A Button component for reloading the game.
#     model: A Dropdown component for selecting the embedding model.
#     lang: A Dropdown component for selecting the language.

# Events:
#     inp.submit: Triggers the change function on input submission.
#     but.click: Triggers the change function on button click.
#     give_up.click: Triggers the change function on give up button click.
#     radio.input: Triggers the change function on radio input.
#     reload.click: Triggers the reset function on reload button click.
#     demo.load: Triggers the reset function on demo load.
#     lang[0].select: Triggers the reset function on language selection.
#     model[0].select: Triggers the reset function on model selection.
#     state.change: Triggers the update function on state change.

# Main:
#     Launches the Gradio application if the script is run as the main module.
# """

import gradio as gr
import json
from datetime import datetime, date
from game import Semantrix, Model_class
from huggingface_hub import CommitScheduler
import os
import logging

# Configure logging
logging.basicConfig(
    level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)


# File paths for configuration and images
base_path = os.path.dirname(os.path.abspath(__file__))
config_file_path = os.path.join(base_path, "config/lang.json")
logo_path = os.path.join(base_path, "config/images/logo.png")
logo_win_path = os.path.join(base_path, "config/images/logo_win.gif")
condition_config_path = os.path.join(base_path, "config/condition.json")
data_path = os.path.join(base_path, "data")
plays_path = os.path.join(data_path, "plays")

condition_name = "condition_1"
# Dynamically determine the condition name based on the folder name
# folder_name = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
# condition = folder_name.split("_")[-1][-1]
# condition_name = "condition_" + condition


with open(condition_config_path, "r") as file:
    condition_config = json.load(file)

model = condition_config[condition_name]["model"]
hints_enabled = condition_config[condition_name]["hints"]

# Loading the configuration file
with open(config_file_path, "r") as file:
    Config_full = json.load(file)

scheduler = CommitScheduler(
    repo_id="Jsevisal/semantrix_data_" + condition_name[-1],
    repo_type="dataset",
    folder_path=plays_path,
    path_in_repo="data",
    every=10,
)

lang = 0  # Language configuration flag (0 for Spanish, 1 for English)

# Setting the configuration based on the language flag
if lang == 1:
    Config = Config_full["ENG"]["Game"]
    Menu = Config_full["ENG"]["Menu"]
else:
    Config = Config_full["SPA"]["Game"]
    Menu = Config_full["SPA"]["Menu"]


# Function to convert text to centered markdown format
def convert_to_markdown_centered(text):
    lines = text.strip().split("\n")

    if not lines:
        return ""

    last_attempt = lines[0]
    history_attempts = lines[2:12]
    markdown = '<div align="center">\n\n'

    markdown += "## " + Menu["Best_tries"] + "\n"
    markdown += "<table>\n"
    markdown += "  <tr>\n"
    markdown += "    <th>" + Menu["N_try"] + "</th>\n"
    markdown += "    <th>" + Menu["Word"] + "</th>\n"
    markdown += "    <th>" + Menu["Score"] + "</th>\n"
    markdown += "  </tr>\n"

    for line in history_attempts:
        items = eval(line.strip())
        markdown += "  <tr>\n"
        markdown += f"    <td><strong>{items[0]}</strong></td>\n"
        markdown += f"    <td>{items[1]}</td>\n"
        markdown += f"    <td>{items[2]}</td>\n"
        markdown += "  </tr>\n"

    markdown += "</table>\n\n"

    last_items = eval(last_attempt)
    markdown += f"## " + Menu["Last_try"] + "\n"
    markdown += (
        f"**{last_items[0]}:** {last_items[1]} - "
        + Menu["Score"]
        + f": {last_items[2]}\n\n"
    )
    markdown += "---\n\n"

    markdown += "</div>"

    return markdown


#
with gr.Blocks(
    theme="ocean",
) as demo:
    # Initializing state variables to manage the internal state of the application
    state = gr.State(-1)  # State variable to track the current game state
    difficulty = gr.State(-1)  # State variable to track the difficulty level
    hint = gr.State(False)  # State variable to track if the hint is provided

    secret_word_used = gr.BrowserState(
        0
    )  # State variable to track the number of secret words used

    # Initializing the game using the Semantrix class with default parameters
    game_instances = {}
    sessions_to_remove = {}
    cooldown_time = 120  # Time in seconds to remove the session from the game_instances
    model_class = Model_class(lang=0, model_type=model)

    # Creating a Markdown component to display the header
    header = gr.Markdown(
        """
    <p style="text-align:center"> """
        + Menu["Header"]
        + """ </p>
    """
    )

    # Function to reset the game
    def reset(reload, request: gr.Request):
        global Config, game, Menu, model, lang  # Declare global variables to modify them within the function

        if reload:
            game_instances[request.session_hash] = Semantrix(
                lang=0, model_type=model, session_hash=request.session_hash
            )
            game_instances[request.session_hash].reset_game()  # Reset the game state

            logger.info("New session detected: %s", request.session_hash)
            logger.info("Game instances: %s", game_instances)

        # Define the initial output components for the UI
        output = [
            -1,
            gr.Textbox(visible=False),
            gr.Textbox(visible=False),
            gr.Image(logo_path, visible=True, interactive=False),
            gr.Button(Menu["Start"], visible=True, variant="secondary"),
            gr.Radio(visible=False),
            gr.Textbox(visible=False),
            gr.Button(visible=False),
            gr.Markdown(
                """
                    <p style="text-align:center"> """
                + Menu["Header"]
                + """ </p>
                    """
            ),
            gr.Button(visible=False),
        ]

        # Return the initial output components
        return output

    def remove_game_instance(timer_tick=False, request: gr.Request | None = None):
        request = None if timer_tick else request

        if request is not None:
            logger.info("Session on inactivity timer: %s", request.session_hash)
            sessions_to_remove[request.session_hash] = datetime.now()
        if len(sessions_to_remove.items()) > 0:
            for session_hash, timestamp in list(sessions_to_remove.items()):
                if (datetime.now() - timestamp).seconds > cooldown_time:
                    del sessions_to_remove[session_hash]

                    session_id = game_instances[session_hash].get_session_id()
                    del game_instances[session_hash]

                    # Delete ranking file if it exists
                    ranking_file = os.path.join(
                        data_path, f"rankings/ranking_{session_hash}.txt"
                    )
                    if os.path.exists(ranking_file):
                        os.remove(ranking_file)

                    # Delete plays files if it exists
                    session_files = [
                        f for f in os.listdir(plays_path) if f.startswith(session_id)
                    ]
                    for file_name in session_files:
                        with open(os.path.join(plays_path, file_name), "r") as file:
                            os.remove(os.path.join(plays_path, file_name))

                    logger.info("Deleted session: %s", session_hash)

    # Function to change the state of the game
    def change(state, inp):
        # Increment the state by 1
        state = state + 1

        # Return the updated state and input component
        return [state, inp]

    # Function to update the game state based on the current state of the game
    def update(state, radio, inp, hint, secret_word_used, request: gr.Request):
        global difficulty, hints_enabled

        # Define the difficulty state
        dif_state = 3

        # Initialize the output component list with the current state

        state_int = state

        output = [state]

        if request.session_hash in sessions_to_remove:
            sessions_to_remove.pop(request.session_hash)
            logger.info("Session saved: %s", request.session_hash)

        # Define UI components for the initial state
        if state_int == -1:
            output.extend(
                [
                    gr.Button(Menu["Start"], visible=True),
                    gr.Radio(label="", visible=False),
                    gr.Textbox(
                        Config[list(Config.keys())[state_int]], visible=False, label=""
                    ),
                    gr.Button(Menu["Give_up"], visible=False),
                    gr.Textbox(visible=False),
                    gr.Image(interactive=False, visible=True),
                    gr.Textbox(visible=False),
                    gr.Button(visible=False),
                    gr.Markdown(visible=False),
                    gr.Button(visible=False),
                    secret_word_used,
                ]
            )

        # Define UI components for the first state, ask the user if they want to know the rules
        elif state_int == 1:
            output.extend(
                [
                    gr.Button(visible=False),
                    gr.Radio(
                        [Menu["Yes"], Menu["No"]], value=None, label="", visible=True
                    ),
                    gr.Textbox(
                        Config[list(Config.keys())[state_int]], visible=True, label=""
                    ),
                    gr.Button(Menu["Give_up"], visible=False),
                    gr.Textbox(visible=False),
                    gr.Image(interactive=False, visible=False),
                    gr.Textbox(visible=False),
                    gr.Button(visible=False),
                    gr.Markdown(visible=False),
                    gr.Button(visible=False),
                    secret_word_used,
                ]
            )

        # Define UI components for the second state, Depending on the answer, show the rules or keep going
        elif state_int == 2:
            if radio == Menu["No"]:
                output = [
                    dif_state,
                    gr.Button("Introducir", visible=True),
                    gr.Radio(visible=False),
                    gr.Textbox(
                        Config[list(Config.keys())[state_int]], visible=True, label=""
                    ),
                    gr.Button(Menu["Give_up"], visible=False),
                    gr.Textbox(visible=False),
                    gr.Image(interactive=False, visible=False),
                    gr.Textbox(visible=False),
                    gr.Button(visible=False),
                    gr.Markdown(visible=False),
                    gr.Button(visible=False),
                    secret_word_used,
                ]

            else:
                output.extend(
                    [
                        gr.Button(Menu["Next"], visible=True),
                        gr.Radio(visible=False),
                        gr.Textbox(
                            Config[list(Config.keys())[state_int]],
                            visible=True,
                            label="",
                        ),
                        gr.Button(Menu["Give_up"], visible=False),
                        gr.Textbox(visible=False),
                        gr.Image(interactive=False, visible=False),
                        gr.Textbox(visible=False),
                        gr.Button(visible=False),
                        gr.Markdown(visible=False),
                        gr.Button(visible=False),
                        secret_word_used,
                    ]
                )

        # Define UI components for the difficulty state, ask the user to select the difficulty level
        elif state_int == dif_state:
            output.extend(
                [
                    gr.Button(Menu["Start"], visible=True, variant="primary"),
                    gr.Radio(visible=False),
                    gr.Textbox(
                        Config[list(Config.keys())[state_int]], visible=True, label=""
                    ),
                    gr.Button(Menu["Give_up"], visible=False),
                    gr.Textbox(visible=False),
                    gr.Image(interactive=False, visible=False),
                    gr.Textbox(visible=False),
                    gr.Button(visible=False),
                    gr.Markdown(visible=False),
                    gr.Button(visible=False),
                    secret_word_used,
                ]
            )

        # Define UI components for the difficulty state + 2, play the game based on the selected difficulty level and prepare the game for the word guessing
        elif state_int == dif_state + 1:

            game_instances[request.session_hash].prepare_game(
                secret_word_used, 2 if hints_enabled else 4
            )

            output.extend(
                [
                    gr.Button(Menu["Send"], visible=True, variant="primary"),
                    gr.Radio(label="", visible=False),
                    gr.Textbox(visible=False, label=""),
                    gr.Button(visible=False, variant="stop"),
                    gr.Textbox(
                        value="",
                        visible=True,
                        autofocus=True,
                        placeholder=Menu["New_word"],
                    ),
                    gr.Image(interactive=False, visible=False),
                    gr.Textbox(visible=False),
                    gr.Button(visible=False),
                    gr.Markdown(visible=False),
                    gr.Button(visible=False),
                    secret_word_used + 1,
                ]
            )

        # Define UI components for the state greater than the difficulty state + 2, play the game and provide feedback based on the user input
        elif state_int > dif_state + 1:

            # Send the user input to the game and get the feedback from the game
            feed = game_instances[request.session_hash].play_game(inp, model_class)

            # Check if the feedback contains the ranking information and process it
            feedback_trim = feed.split("[rank]")
            if len(feedback_trim) > 1:
                ranking_vis = True
                ranking_md = convert_to_markdown_centered(feedback_trim[1])

            else:
                ranking_vis = False
                ranking_md = ""

            # Check if the feedback contains a hint, win, or lose message
            feedback = feedback_trim[0].split("[hint]")
            win = feedback_trim[0].split("[win]")
            lose = feedback_trim[0].split("[lose]")

            # Check if the feedback contains a hint message
            if len(feedback) > 1:
                hint = True
                hint_out = feedback[1]
                feedback = feedback[0]
            else:
                hint = False
                feedback = feedback[0]

            # Check if the feedback contains a win or lose message and process it
            if len(win) > 1 or len(lose) > 1:

                # Check if the user won the game
                won = True if len(win) > 1 else False

                # Get the curiosity message from the game
                curiosity = game_instances[request.session_hash].curiosity()

                # Define the output components for the win or lose state
                output.extend(
                    [
                        gr.Button(Menu["Send"], visible=False, variant="primary"),
                        gr.Radio(label="", visible=False),
                        gr.Textbox(win[1] if won else lose[1], visible=True, label=""),
                        gr.Button(visible=False, variant="stop"),
                        gr.Textbox(
                            value="", visible=False, placeholder=Menu["New_word"]
                        ),
                        gr.Image(
                            logo_win_path if won else logo_path,
                            interactive=False,
                            visible=True,
                        ),
                        gr.Textbox(curiosity, visible=True, label=Menu["Curiosity"]),
                        gr.Button(Menu["Play_again"], variant="primary", visible=True),
                        gr.Markdown(visible=False),
                        gr.Button(visible=True),
                        secret_word_used,
                    ]
                )

                return output

            # Define the output components for the feedback and keep playing
            output.extend(
                [
                    gr.Button(Menu["Send"], visible=True, variant="primary"),
                    gr.Radio(label="", visible=False),
                    gr.Textbox(feedback, visible=True, label=""),
                    gr.Button(visible=True, variant="stop"),
                    gr.Textbox(value="", visible=True, placeholder=Menu["New_word"]),
                    gr.Image(logo_path, interactive=False, visible=False),
                    gr.Textbox(hint_out if hint else "", visible=hint, label="Pista"),
                    gr.Button(visible=False),
                    gr.Markdown(ranking_md, visible=ranking_vis),
                    gr.Button(visible=False),
                    secret_word_used,
                ]
            )

        # Define UI components for the rest of the states, state for showing basic text to the user
        else:
            output.extend(
                [
                    gr.Button(Menu["Next"], visible=True),
                    gr.Radio(label="", visible=False),
                    gr.Textbox(
                        Config[list(Config.keys())[state_int]], visible=True, label=""
                    ),
                    gr.Button("Pista", visible=False),
                    gr.Textbox(visible=False),
                    gr.Image(interactive=False, visible=False),
                    gr.Textbox(visible=False),
                    gr.Button(visible=False),
                    gr.Markdown(visible=False),
                    gr.Button(visible=False),
                    secret_word_used,
                ]
            )

        # Return the output components
        return output

    def commit_data(request: gr.Request):
        session_id = game_instances[request.session_hash].get_session_id()
        session_files = [f for f in os.listdir(plays_path) if f.startswith(session_id)]
        combined_data = []

        for file_name in session_files:
            with open(os.path.join(plays_path, file_name), "r") as file:
                combined_data.append(json.load(file))
                os.remove(os.path.join(plays_path, file_name))

        combined_file_path = os.path.join(plays_path, f"{session_id}.json")
        with open(combined_file_path, "w") as combined_file:
            json.dump(combined_data, combined_file, indent=4)

        scheduler.push_to_hub()

        if os.path.exists(combined_file_path):
            os.remove(combined_file_path)

        return [
            gr.Button(visible=False),
            gr.Textbox(visible=False),
            gr.Textbox(visible=False),
            gr.Button(
                "Rellenar cuestionario",
                variant="primary",
                link="https://docs.google.com/forms/d/e/1FAIpQLSdp38ERDGSk9qeS7xaEcaiCbedh48cMXZy8e02dR6-1aGCs1Q/viewform?usp=pp_url&entry.2136602733="
                + session_id,
                visible=True,
            ),
        ]

    # Define the UI layout for the gam
    img = gr.Image(logo_path, height=430, interactive=False, visible=True)
    ranking = gr.Markdown(visible=False)

    with gr.Row():
        out = gr.Textbox(visible=False, placeholder=Config[list(Config.keys())[0]])
        hint_out = gr.Textbox(visible=False)

    radio = gr.Radio(visible=False)

    with gr.Row():
        inp = gr.Textbox(visible=False, interactive=True, label="")
        but = gr.Button(Menu["Start"])
        give_up = gr.Button("Pista", visible=False)
        reload = gr.Button(Menu["Play_again"], visible=False)
    finish = gr.Button(
        "Terminar",
        variant="stop",
        # link="https://docs.google.com/forms/d/e/1FAIpQLSd0z8nI4hhOSR83yPIw_bR3KkSt25Lsq0ZXG1pZnkldeoceqA/viewform?usp=pp_url&entry.327829192=Condici%C3%B3n+1",
        visible=False,
    )

    timer_delete = gr.Timer(value=30)

    # Define the UI events for the game

    # Define events that trigger the game state change
    timer_delete.tick(remove_game_instance, inputs=[gr.State(True)])
    inp.submit(
        change,
        inputs=[state, inp],
        outputs=[state, inp],
        concurrency_limit=5,
    )
    but.click(
        change,
        inputs=[state, inp],
        outputs=[state, inp],
        concurrency_limit=5,
    )
    give_up.click(
        change,
        inputs=[
            state,
            gr.Textbox("give_up", visible=False, interactive=True, label=""),
        ],
        outputs=[state, inp],
        concurrency_limit=5,
    )
    radio.input(
        change,
        inputs=[state, inp],
        outputs=[state, inp],
        concurrency_limit=5,
    )
    finish.click(
        commit_data, outputs=[reload, out, hint_out, finish]
    )  # Define events that trigger the game reset
    reload.click(
        reset,
        inputs=[gr.State(False)],
        outputs=[state, out, inp, img, but, radio, hint_out, reload, header, finish],
    )
    demo.load(
        reset,
        inputs=[gr.State(True)],
        outputs=[state, out, inp, img, but, radio, hint_out, reload, header, finish],
    )
    demo.unload(remove_game_instance)

    # Define events that trigger the game state update
    state.change(
        update,
        inputs=[state, radio, inp, hint, secret_word_used],
        outputs=[
            state,
            but,
            radio,
            out,
            give_up,
            inp,
            img,
            hint_out,
            reload,
            ranking,
            finish,
            secret_word_used,
        ],
    )

if __name__ == "__main__":
    demo.launch(debug=True)