#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.

import glob
import inspect
import os
import time

from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import module_utils

currentdir = os.path.dirname(
    os.path.realpath(os.path.abspath(inspect.getfile(inspect.currentframe())))
)
fenrir_path = os.path.dirname(currentdir)


class CommandManager:
    def __init__(self):
        pass

    def initialize(self, environment):
        self.env = environment
        # commands
        self.env["commands"] = {}
        self.env["commandsIgnore"] = {}
        for command_folder in self.env["general"]["commandFolderList"]:
            self.env["runtime"]["CommandManager"].load_commands(command_folder)
            if (
                self.env["runtime"]["SettingsManager"].get_setting(
                    "general", "command_path"
                )
                != ""
            ):
                self.env["runtime"]["CommandManager"].load_commands(
                    command_folder,
                    self.env["runtime"]["SettingsManager"].get_setting(
                        "general", "command_path"
                    ),
                )

        # scripts for scriptKey
        self.env["runtime"]["CommandManager"].load_script_commands()

    def shutdown(self):
        for command_folder in self.env["general"]["commandFolderList"]:
            self.env["runtime"]["CommandManager"].shutdown_commands(
                command_folder
            )

    def load_file(self, filepath=""):
        if filepath == "":
            return None
        if not os.path.exists(filepath):
            self.env["runtime"]["DebugManager"].write_debug_out(
                "load_file: filepath not exists:" + filepath,
                debug.DebugLevel.WARNING,
            )
            return None
        if os.path.isdir(filepath):
            self.env["runtime"]["DebugManager"].write_debug_out(
                "load_file: filepath is a directory:" + filepath,
                debug.DebugLevel.ERROR,
            )
            return None
        if not os.access(filepath, os.R_OK):
            self.env["runtime"]["DebugManager"].write_debug_out(
                "load_file: filepath not readable:" + filepath,
                debug.DebugLevel.ERROR,
            )
            return None

        try:
            file_name, file_extension = os.path.splitext(filepath)
            file_name = file_name.split("/")[-1]
            if file_name.startswith("__"):
                return None
            if file_extension.lower() == ".py":
                command_mod = module_utils.import_module(file_name, filepath)
                command = command_mod.command()
                command.initialize(self.env)
                self.env["runtime"]["DebugManager"].write_debug_out(
                    "load_file: Load command:" + filepath,
                    debug.DebugLevel.INFO,
                    on_any_level=True,
                )
                return command
        except Exception as e:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "load_file: Loading command:" + filepath,
                debug.DebugLevel.ERROR,
            )
            self.env["runtime"]["DebugManager"].write_debug_out(
                str(e), debug.DebugLevel.ERROR
            )
        return None

    def load_commands(self, section="commands", command_path=""):
        if command_path == "":
            command_path = fenrir_path + "/commands/"
        if not command_path.endswith("/"):
            command_path += "/"
        command_folder = command_path + section + "/"
        if not os.path.exists(command_folder):
            self.env["runtime"]["DebugManager"].write_debug_out(
                "load_commands: command_folder not exists:" + command_folder,
                debug.DebugLevel.WARNING,
            )
            return
        if not os.path.isdir(command_folder):
            self.env["runtime"]["DebugManager"].write_debug_out(
                "load_commands: command_folder not a directory:"
                + command_folder,
                debug.DebugLevel.ERROR,
            )
            return
        if not os.access(command_folder, os.R_OK):
            self.env["runtime"]["DebugManager"].write_debug_out(
                "load_commands: command_folder not readable:" + command_folder,
                debug.DebugLevel.ERROR,
            )
            return
        self.env["commands"][section] = {}
        self.env["commandsIgnore"][section] = {}
        command_list = glob.glob(command_folder + "*")
        for command in command_list:
            try:
                file_name, file_extension = os.path.splitext(command)
                file_name = file_name.split("/")[-1]
                if file_name.startswith("__"):
                    continue
                # Skip base classes that shouldn't be loaded as commands
                if file_name.endswith("_base"):
                    continue
                # Check if command already exists to prevent duplicate loading
                if (
                    file_name.upper() in self.env["commands"][section]
                    and self.env["commands"][section][file_name.upper()]
                    is not None
                ):
                    continue
                if file_extension.lower() == ".py":
                    command_mod = module_utils.import_module(
                        file_name, command
                    )
                    self.env["commands"][section][
                        file_name.upper()
                    ] = command_mod.command()
                    self.env["commandsIgnore"][section][
                        file_name.upper()[file_name.upper().find("-") + 1 :]
                        + "_IGNORE"
                    ] = False
                    self.env["commands"][section][
                        file_name.upper()
                    ].initialize(self.env)
                    self.env["runtime"]["DebugManager"].write_debug_out(
                        "load_commands: Load command:"
                        + section
                        + "."
                        + file_name.upper(),
                        debug.DebugLevel.INFO,
                        on_any_level=True,
                    )
            except Exception as e:
                self.env["runtime"]["DebugManager"].write_debug_out(
                    "load_commands: Loading command:" + command,
                    debug.DebugLevel.ERROR,
                )
                self.env["runtime"]["DebugManager"].write_debug_out(
                    str(e), debug.DebugLevel.ERROR
                )
                continue

    def load_script_commands(self, section="commands", script_path=""):
        if script_path == "":
            script_path = self.env["runtime"]["SettingsManager"].get_setting(
                "general", "script_path"
            )
        if not script_path.endswith("/"):
            script_path += "/"
        if not os.path.exists(script_path):
            if os.path.exists(fenrir_path + "/../../config/scripts/"):
                script_path = fenrir_path + "/../../config/scripts/"
            else:
                self.env["runtime"]["DebugManager"].write_debug_out(
                    "scriptpath not exists:" + script_path,
                    debug.DebugLevel.WARNING,
                )
                return
        if not os.path.isdir(script_path):
            self.env["runtime"]["DebugManager"].write_debug_out(
                "scriptpath not a directory:" + script_path,
                debug.DebugLevel.ERROR,
            )
            return
        if not os.access(script_path, os.R_OK):
            self.env["runtime"]["DebugManager"].write_debug_out(
                "scriptpath not readable:" + script_path,
                debug.DebugLevel.ERROR,
            )
            return
        command_list = glob.glob(script_path + "*")
        sub_command = fenrir_path + "/commands/commands/subprocess.py"
        for command in command_list:
            invalid = False
            try:
                file_name, file_extension = os.path.splitext(command)
                file_name = file_name.split("/")[-1]
                if file_name.startswith("__"):
                    continue
                if file_name.upper() in self.env["commands"][section]:
                    continue
                command_mod = module_utils.import_module(
                    file_name, sub_command
                )
                self.env["commands"][section][
                    file_name.upper()
                ] = command_mod.command()
                self.env["commands"][section][file_name.upper()].initialize(
                    self.env, command
                )
                self.env["runtime"]["DebugManager"].write_debug_out(
                    "Load script:" + section + "." + file_name.upper(),
                    debug.DebugLevel.INFO,
                    on_any_level=True,
                )
                comm_settings = file_name.upper().split("__-__")
                if len(comm_settings) == 1:
                    keys = comm_settings[0]
                elif len(comm_settings) == 2:
                    keys = comm_settings[1]
                elif len(comm_settings) > 2:
                    continue
                keys = keys.split("__+__")
                shortcut_keys = []
                shortcut = []
                for key in keys:
                    if not self.env["runtime"]["InputManager"].is_valid_key(
                        key.upper()
                    ):
                        self.env["runtime"]["DebugManager"].write_debug_out(
                            "invalid key : "
                            + key.upper()
                            + " script:"
                            + file_name,
                            debug.DebugLevel.WARNING,
                        )
                        invalid = True
                        break
                    shortcut_keys.append(key.upper())
                if invalid:
                    continue
                if "KEY_SCRIPT" not in shortcut_keys:
                    shortcut_keys.append("KEY_SCRIPT")
                shortcut.append(1)
                shortcut.append(sorted(shortcut_keys))
                self.env["bindings"][str(shortcut)] = file_name.upper()
            except Exception as e:
                self.env["runtime"]["DebugManager"].write_debug_out(
                    "Loading script:" + file_name, debug.DebugLevel.ERROR
                )
                self.env["runtime"]["DebugManager"].write_debug_out(
                    str(e), debug.DebugLevel.ERROR
                )
                continue

    def shutdown_commands(self, section):
        # Check if the section exists in the commands dictionary
        if section not in self.env["commands"]:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "shutdown_commands: section not found:" + section,
                debug.DebugLevel.WARNING,
            )
            return

        for command in sorted(self.env["commands"][section]):
            try:
                self.env["commands"][section][command].shutdown()
                del self.env["commands"][section][command]
            except Exception as e:
                self.env["runtime"]["DebugManager"].write_debug_out(
                    "Shutdown command:" + section + "." + command,
                    debug.DebugLevel.ERROR,
                )
                self.env["runtime"]["DebugManager"].write_debug_out(
                    str(e), debug.DebugLevel.ERROR
                )
                continue

    def execute_switch_trigger(self, trigger, unLoadScript, loadScript):
        if self.env["runtime"]["ScreenManager"].is_ignored_screen():
            return
        # unload
        old_script = unLoadScript
        if self.command_exists(old_script, trigger):
            try:
                self.env["runtime"]["DebugManager"].write_debug_out(
                    "Executing switchtrigger.unload:"
                    + trigger
                    + "."
                    + old_script,
                    debug.DebugLevel.INFO,
                )
                self.env["commands"][trigger][old_script].unload()
            except Exception as e:
                self.env["runtime"]["DebugManager"].write_debug_out(
                    "Executing trigger:" + trigger + "." + old_script,
                    debug.DebugLevel.ERROR,
                )
                self.env["runtime"]["DebugManager"].write_debug_out(
                    str(e), debug.DebugLevel.ERROR
                )
        # load
        new_script = loadScript
        if self.command_exists(new_script, trigger):
            try:
                self.env["runtime"]["DebugManager"].write_debug_out(
                    "Executing switchtrigger.load:"
                    + trigger
                    + "."
                    + new_script,
                    debug.DebugLevel.INFO,
                )
                self.env["commands"][trigger][new_script].load()
            except Exception as e:
                self.env["runtime"]["DebugManager"].write_debug_out(
                    "Executing trigger:" + trigger + "." + new_script,
                    debug.DebugLevel.ERROR,
                )
                self.env["runtime"]["DebugManager"].write_debug_out(
                    str(e), debug.DebugLevel.ERROR
                )

    def execute_default_trigger(self, trigger, force=False):
        if not force:
            if self.env["runtime"]["ScreenManager"].is_ignored_screen():
                return
        for command in sorted(self.env["commands"][trigger]):
            if self.command_exists(command, trigger):
                try:
                    if self.env["commandsIgnore"][trigger][
                        command[command.find("-") + 1 :] + "_IGNORE"
                    ]:
                        self.env["commandsIgnore"][trigger][
                            command[command.find("-") + 1 :] + "_IGNORE"
                        ] = False
                        self.env["runtime"]["DebugManager"].write_debug_out(
                            "ignore trigger.command:"
                            + trigger
                            + "."
                            + command,
                            debug.DebugLevel.INFO,
                        )
                    else:
                        self.env["runtime"]["DebugManager"].write_debug_out(
                            "Executing trigger.command:"
                            + trigger
                            + "."
                            + command,
                            debug.DebugLevel.INFO,
                        )
                        self.env["commands"][trigger][command].run()
                except Exception as e:
                    self.env["runtime"]["DebugManager"].write_debug_out(
                        "Executing trigger:"
                        + trigger
                        + "."
                        + command
                        + str(e),
                        debug.DebugLevel.ERROR,
                    )

    def execute_command(self, command, section="commands"):
        if self.env["runtime"]["ScreenManager"].is_ignored_screen():
            return
        if self.command_exists(command, section):
            try:
                if (
                    self.env["runtime"]["HelpManager"].is_tutorial_mode()
                    and section != "help"
                ):
                    self.env["runtime"]["DebugManager"].write_debug_out(
                        "Tutorial for command:" + section + "." + command,
                        debug.DebugLevel.INFO,
                    )
                    description = self.get_command_description(
                        command, section
                    )
                    self.env["runtime"]["OutputManager"].present_text(
                        description, interrupt=False
                    )
                else:
                    self.env["runtime"]["DebugManager"].write_debug_out(
                        "Executing command:" + section + "." + command,
                        debug.DebugLevel.INFO,
                    )
                    self.run_command(command, section)
            except Exception as e:
                self.env["runtime"]["DebugManager"].write_debug_out(
                    "Executing command:"
                    + section
                    + "."
                    + command
                    + " "
                    + str(e),
                    debug.DebugLevel.ERROR,
                )

    def run_command(self, command, section="commands"):
        if self.command_exists(command, section):
            try:
                self.env["runtime"]["DebugManager"].write_debug_out(
                    "run_command command:" + section + "." + command,
                    debug.DebugLevel.INFO,
                )
                self.env["commands"][section][command].run()
            except Exception as e:
                self.env["runtime"]["DebugManager"].write_debug_out(
                    "run_command command:"
                    + section
                    + "."
                    + command
                    + " "
                    + str(e),
                    debug.DebugLevel.ERROR,
                )
        self.env["commandInfo"]["lastCommandExecutionTime"] = time.time()

    def get_command_description(self, command, section="commands"):
        if self.command_exists(command, section):
            try:
                return self.env["commands"][section][command].get_description()
            except Exception as e:
                self.env["runtime"]["DebugManager"].write_debug_out(
                    "command_manager.get_command_description:" + str(e),
                    debug.DebugLevel.ERROR,
                )
        self.env["commandInfo"]["lastCommandExecutionTime"] = time.time()

    def command_exists(self, command, section="commands"):
        try:
            return command in self.env["commands"][section]
        except Exception as e:
            return False

    def get_shortcut_for_command(self, command, formatKeys=False):
        shortcut = []
        try:
            raw_shortcut = list(self.env["bindings"].keys())[
                list(self.env["bindings"].values()).index(command)
            ]
            # prefer numbers for multitap
            for k in ["2", "3", "4", "5", "6", "7", "8"]:
                if k in raw_shortcut:
                    formatted_key = k
                    if formatKeys:
                        formatted_key = formatted_key.lower()
                        formatted_key += " times "
                    shortcut.append(formatted_key)
                    raw_shortcut.remove(k)
            # prefer metha keys
            for k in [
                "KEY_FENRIR",
                "KEY_SCRIPT",
                "KEY_CTRL",
                "KEY_SHIFT",
                "KEY_ALT",
                "KEY_META",
            ]:
                if k in raw_shortcut:
                    formatted_key = k
                    if formatKeys:
                        formatted_key = formatted_key.lower()
                        formatted_key = formatted_key.replace(
                            "key_kp", " keypad "
                        )
                        formatted_key = formatted_key.replace("key_", " ")
                    shortcut.append(formatted_key)
                    raw_shortcut.remove(k)
            # handle other keys
            for k in raw_shortcut:
                formatted_key = k
                if formatKeys:
                    formatted_key = formatted_key.lower()
                    formatted_key = formatted_key.replace("key_kp", " keypad ")
                    formatted_key = formatted_key.replace("key_", " ")
                shortcut.append(formatted_key)
        except Exception as e:
            pass
        return shortcut
