import gradio as gr
import json
import os
import tempfile
import shutil
import zipfile
from relatively_constant_variables import finished_product_demo, all_states

class Player:
    def __init__(self):
        self.inventory = []
        self.money = 20
        self.knowledge = {}

    def add_item(self, item):
        self.inventory.append(item)

    def has_item(self, item):
        return item in self.inventory

    def update_knowledge(self, topic):
        self.knowledge[topic] = True

class GameSession:
    def __init__(self, starting_location='village', starting_state='start'):
        self.player = Player()
        self.current_location = starting_location
        self.current_state = starting_state
        self.game_log = []

    def make_choice(self, choice_index):
        state = all_states[self.current_location][self.current_state]
        if 0 <= choice_index < len(state['choices']):
            choice = state['choices'][choice_index]
            next_state = state['transitions'][choice]

            self.game_log.append(f"You chose: {choice}")
            self.game_log.append(state['description'])

            if 'consequences' in state and choice in state['consequences']:
              if state['consequences'][choice]:
                  state['consequences'][choice](self.player)
              else:
                  # Handle empty consequence, e.g., log a message or provide a default action
                  print(f"No consequence for choice: {choice}")
                  # You can add any default action here if needed

            if '_' in next_state:
                self.current_location, self.current_state = next_state.split('_')
            else:
                self.current_state = next_state

            return self.get_current_state_info()
        else:
            return "Invalid choice. Please try again."

    def get_current_state_info(self):
        state = all_states[self.current_location][self.current_state]
        choices = [f"{idx + 1}. {choice}" for idx, choice in enumerate(state['choices'])]
        return state['description'], choices, "\n".join(self.game_log)
    
    def get_current_state_media(self):
        media = all_states[self.current_location][self.current_state]['media']
        return media

def start_game(starting_location='village', starting_state='start', new_states=all_states):
    global all_states
    game_session = GameSession(starting_location, starting_state)
    description, choices, game_log = game_session.get_current_state_info()
    all_states = new_states
    return description, choices, game_log, game_session

def make_choice(choice, game_session, with_media=False): #Calls the nested make choice function in the game session class
    if not choice:
        description, choices, game_log = game_session.get_current_state_info()
        return description, choices, "Please select a choice before proceeding.", game_session

    choice_index = int(choice.split('.')[0]) - 1 
    result = game_session.make_choice(choice_index)

    if with_media:
        media = game_session.get_current_state_media()
        return result[0], gr.update(choices=result[1]), result[2], game_session, media
    else:
        return result[0], gr.update(choices=result[1]), result[2], game_session

def validate_transitions(all_states):
    errors = []
    for location, states in all_states.items():
        for state_key, state in states.items():
            for transition_key, transition_state in state['transitions'].items():
                # Check if the transition is to another location
                if transition_state in all_states:
                    trans_location, trans_state = transition_state, 'start'  # Assuming 'start' state for new locations
                elif '_' in transition_state:
                    trans_location, trans_state = transition_state.split('_')
                else:
                    trans_location, trans_state = location, transition_state

                # Validate the transition state
                if trans_location not in all_states or trans_state not in all_states[trans_location]:
                    errors.append(f"Invalid transition from {location}.{state_key} to {trans_location}.{trans_state}")

    return errors

path_errors = validate_transitions(all_states)
if path_errors:
    for error in path_errors:
        print(error)
else:
    print("All transitions are valid.")


def load_game(custom_config=None, with_media=False):
    global all_states
    if not custom_config:
        return gr.update(value="No custom configuration provided."), None, None, None, None, None, None

    try:
        new_config = json.loads(custom_config)
        all_states = new_config

        # Determine the starting location and state
        starting_location = next(iter(all_states.keys()))
        starting_state = next(iter(all_states[starting_location].keys()))
        print(f"Starting location: {starting_location}, Starting state: {starting_state}")

        game_session = GameSession(starting_location, starting_state)
        description, choices, game_log = game_session.get_current_state_info()
        new_path_errors = validate_transitions(all_states)
        
        output_media = []

        if with_media:
            media_list = all_states[starting_location][starting_state].get('media', [])
            print(f"Media list: {media_list}")
            
            if media_list:
                for media_path in media_list:
                    #media_component = create_media_component(media_path)
                    output_media.append(media_path)
                print(f"Created {len(output_media)} media components")

        success_message = f"Custom configuration loaded successfully!\n{new_path_errors}"
        return (
            gr.update(value=success_message),
            game_log,
            description,
            gr.update(choices=choices),
            gr.update(value=custom_config),
            game_session,
            output_media if with_media else None
        )

    except json.JSONDecodeError as e:
        error_message = format_json_error(custom_config, e)
        return gr.update(value=error_message), None, None, None, gr.update(value=custom_config), None, None

    except Exception as e:
        error_message = f"Error loading custom configuration: {str(e)}"
        return gr.update(value=error_message), None, None, None, gr.update(value=custom_config), None, None

def load_game_edit_version(custom_config=None, with_media=False, custom_starting_location=None, custom_starting_state=None):
    global all_states
    if not custom_config:
        return gr.update(value="No custom configuration provided."), None, None, None, None, None, None

    try:
        new_config = json.loads(custom_config)
        all_states = new_config

        # Determine the starting location and state
        if custom_starting_location and custom_starting_state:
            if custom_starting_location not in all_states or custom_starting_state not in all_states[custom_starting_location]:
                raise ValueError(f"Invalid custom starting point: {custom_starting_location}, {custom_starting_state}")
            starting_location = custom_starting_location
            starting_state = custom_starting_state
        else:
            starting_location = next(iter(all_states.keys()))
            starting_state = next(iter(all_states[starting_location].keys()))
        
        print(f"Starting location: {starting_location}, Starting state: {starting_state}")

        game_session = GameSession(starting_location, starting_state)
        description, choices, game_log = game_session.get_current_state_info()
        new_path_errors = validate_transitions(all_states)
        
        output_media = []

        if with_media:
            media_list = all_states[starting_location][starting_state].get('media', [])
            print(f"Media list: {media_list}")
            
            if media_list:
                for media_path in media_list:
                    output_media.append(media_path)
                print(f"Created {len(output_media)} media components")

        success_message = f"Custom configuration loaded successfully!\n{new_path_errors}"
        return (
            gr.update(value=success_message),
            game_log,
            description,
            gr.update(choices=choices),
            gr.update(value=custom_config),
            game_session,
            output_media if with_media else None
        )

    except json.JSONDecodeError as e:
        error_message = format_json_error(custom_config, e)
        return gr.update(value=error_message), None, None, None, gr.update(value=custom_config), None, None

    except Exception as e:
        error_message = f"Error loading custom configuration: {str(e)}"
        return gr.update(value=error_message), None, None, None, gr.update(value=custom_config), None, None
    
media_folder = os.path.abspath("saved_media") #make sure same as SAVE_DIR below

def export_config_with_media(config_json):
    global media_folder
    """
    Export the config JSON and zip it along with any files referenced in the media fields.
    
    :param config_json: JSON string containing the config
    :param media_folder: Path to the folder containing media files
    :return: Path to the created zip file
    """
    # Parse the JSON
    config = json.loads(config_json)
    
    # Create a temporary directory to store files for zipping
    with tempfile.TemporaryDirectory() as temp_dir:
        # Save the config JSON to the temp directory
        config_path = os.path.join(temp_dir, 'config.json')
        with open(config_path, 'w') as f:
            json.dump(config, f, indent=2)
        
        # Collect all media files
        media_files = set()
        for location in config.values():
            if isinstance(location, dict):
                for sublocation in location.values():
                    if isinstance(sublocation, dict) and 'media' in sublocation:
                        media_files.update(sublocation['media'])
        
        # Copy media files to the temp directory
        for media_file in media_files:
            src_path = os.path.join(media_folder, media_file)
            if os.path.exists(src_path):
                dst_path = os.path.join(temp_dir, media_file)
                shutil.copy2(src_path, dst_path)
            else:
                print(f"Warning: Media file not found: {media_file}")
        
        # Create a zip file
        zip_path = os.path.join(os.path.dirname(media_folder), 'config_with_media.zip')
        with zipfile.ZipFile(zip_path, 'w') as zipf:
            for root, _, files in os.walk(temp_dir):
                for file in files:
                    file_path = os.path.join(root, file)
                    arcname = os.path.relpath(file_path, temp_dir)
                    zipf.write(file_path, arcname)
    
    return zip_path

def format_json_error(config, error):
    lineno, colno = error.lineno, error.colno
    lines = config.split('\n')
    error_line = lines[lineno - 1] if lineno <= len(lines) else ""
    pointer = ' ' * (colno - 1) + '^'

    return f"""Invalid JSON format in custom configuration:
Error at line {lineno}, column {colno}:
{error_line}
{pointer}
Error details: {str(error)}"""

def display_website(link):
  html = f"<iframe src='{link}' width='100%' height='1000px'></iframe>"
  gr.Info("If 404 then the space/page has probably been disabled - normally due to a better alternative")
  return html

initgameinfo = start_game()
fpeinitgameinfo = start_game(new_states=finished_product_demo)