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

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

import inspect
import os
import time

from fenrirscreenreader.core import debug
from fenrirscreenreader.core.i18n import _
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 VmenuManager:
    def __init__(self):
        self.menuDict = {}
        self.curr_index = None
        self.currMenu = ""
        self.active = False
        self.reset = True
        self.useTimeout = True
        self.searchText = ""
        self.lastSearchTime = time.time()

    def initialize(self, environment):
        self.env = environment
        # use default path
        self.defaultVMenuPath = (
            fenrir_path + "/commands/vmenu-profiles/KEY"
        )
        # if there is no user configuration
        if (
            self.env["runtime"]["SettingsManager"].get_setting(
                "menu", "vmenu_path"
            )
            != ""
        ):
            self.defaultVMenuPath = self.env["runtime"][
                "SettingsManager"
            ].get_setting("menu", "vmenu_path")
            if not self.defaultVMenuPath.endswith("/"):
                self.defaultVMenuPath += "/"
            self.defaultVMenuPath += "KEY"

        self.create_menu_tree()
        self.closeAfterAction = False

    def shutdown(self):
        pass

    def clear_search_text(self):
        self.searchText = ""

    def search_entry(self, value, forceReset=False):
        if self.curr_index is None:
            return ""
        if self.reset or forceReset:
            self.clear_search_text()
        else:
            if self.useTimeout:
                if time.time() - self.lastSearchTime > 1:
                    self.clear_search_text()
        self.searchText += value.upper()
        self.lastSearchTime = time.time()
        start_index = self.get_curr_index()
        while True:
            if not self.next_index():
                return ""
            entry = self.get_current_entry()
            if entry.upper().startswith(self.searchText):
                return entry
            if start_index == self.get_curr_index():
                return ""

    def set_curr_menu(self, currMenu=""):
        self.curr_index = None
        self.currMenu = ""
        if currMenu != "":
            currMenu += " " + _("Menu")
            try:
                t = self.menuDict[currMenu]
                l = list(self.menuDict.keys())
                self.curr_index = [l.index(currMenu)]
            except Exception as e:
                print(e)
                self.currMenu = ""
                self.curr_index = None
                return
            if self.inc_level():
                self.currMenu = currMenu
            else:
                self.currMenu = ""
                self.curr_index = None

    def get_curr_menu(self):
        return self.currMenu

    def get_active(self):
        return self.active

    def toggle_vmenu_mode(self, closeAfterAction=True):
        self.set_active(not self.get_active(), closeAfterAction)

    def set_active(self, active, closeAfterAction=True):
        if self.env["runtime"]["HelpManager"].is_tutorial_mode():
            return
        self.active = active
        if self.active:
            self.closeAfterAction = closeAfterAction
            try:
                self.create_menu_tree()
            except Exception as e:
                print(e)
            try:
                if self.currMenu != "":
                    self.set_curr_menu(self.currMenu)
                if self.curr_index is None:
                    if len(self.menuDict) > 0:
                        self.curr_index = [0]
            except Exception as e:
                print(e)
            try:
                # navigation
                self.env["bindings"][
                    str([1, ["KEY_ESC"]])
                ] = "TOGGLE_VMENU_MODE"
                self.env["bindings"][str([1, ["KEY_UP"]])] = "PREV_VMENU_ENTRY"
                self.env["bindings"][
                    str([1, ["KEY_DOWN"]])
                ] = "NEXT_VMENU_ENTRY"
                self.env["bindings"][
                    str([1, ["KEY_SPACE"]])
                ] = "CURR_VMENU_ENTRY"
                self.env["bindings"][
                    str([1, ["KEY_LEFT"]])
                ] = "DEC_LEVEL_VMENU"
                self.env["bindings"][
                    str([1, ["KEY_RIGHT"]])
                ] = "INC_LEVEL_VMENU"
                self.env["bindings"][
                    str([1, ["KEY_ENTER"]])
                ] = "EXEC_VMENU_ENTRY"
                # search
                self.env["bindings"][str([1, ["KEY_A"]])] = "SEARCH_A"
                self.env["bindings"][str([1, ["KEY_B"]])] = "SEARCH_B"
                self.env["bindings"][str([1, ["KEY_C"]])] = "SEARCH_C"
                self.env["bindings"][str([1, ["KEY_D"]])] = "SEARCH_D"
                self.env["bindings"][str([1, ["KEY_E"]])] = "SEARCH_E"
                self.env["bindings"][str([1, ["KEY_F"]])] = "SEARCH_F"
                self.env["bindings"][str([1, ["KEY_G"]])] = "SEARCH_G"
                self.env["bindings"][str([1, ["KEY_H"]])] = "SEARCH_H"
                self.env["bindings"][str([1, ["KEY_I"]])] = "SEARCH_I"
                self.env["bindings"][str([1, ["KEY_J"]])] = "SEARCH_J"
                self.env["bindings"][str([1, ["KEY_K"]])] = "SEARCH_K"
                self.env["bindings"][str([1, ["KEY_L"]])] = "SEARCH_L"
                self.env["bindings"][str([1, ["KEY_M"]])] = "SEARCH_M"
                self.env["bindings"][str([1, ["KEY_N"]])] = "SEARCH_N"
                self.env["bindings"][str([1, ["KEY_O"]])] = "SEARCH_O"
                self.env["bindings"][str([1, ["KEY_P"]])] = "SEARCH_P"
                self.env["bindings"][str([1, ["KEY_Q"]])] = "SEARCH_Q"
                self.env["bindings"][str([1, ["KEY_R"]])] = "SEARCH_R"
                self.env["bindings"][str([1, ["KEY_S"]])] = "SEARCH_S"
                self.env["bindings"][str([1, ["KEY_T"]])] = "SEARCH_T"
                self.env["bindings"][str([1, ["KEY_U"]])] = "SEARCH_U"
                self.env["bindings"][str([1, ["KEY_V"]])] = "SEARCH_V"
                self.env["bindings"][str([1, ["KEY_W"]])] = "SEARCH_W"
                self.env["bindings"][str([1, ["KEY_X"]])] = "SEARCH_X"
                self.env["bindings"][str([1, ["KEY_Y"]])] = "SEARCH_Y"
                self.env["bindings"][str([1, ["KEY_Z"]])] = "SEARCH_Z"
                # page navigation
                self.env["bindings"][
                    str([1, ["KEY_PAGEUP"]])
                ] = "PAGE_UP_VMENU"
                self.env["bindings"][
                    str([1, ["KEY_PAGEDOWN"]])
                ] = "PAGE_DOWN_VMENU"
            except Exception as e:
                print(e)
        else:
            try:
                self.curr_index = None
                self.env["bindings"] = self.env["runtime"][
                    "SettingsManager"
                ].get_binding_backup()
            except Exception as e:
                self.env["runtime"]["DebugManager"].write_debug_out(
                    "VmenuManager set_active: Error loading binding backup: "
                    + str(e),
                    debug.DebugLevel.ERROR,
                )

    def create_menu_tree(self, resetIndex=True):
        if resetIndex:
            self.curr_index = None
        menu = self.fs_tree_to_dict(self.defaultVMenuPath)
        if menu:
            self.menuDict = menu

        # Add dynamic voice menus
        try:
            from fenrirscreenreader.core.dynamicVoiceMenu import (
                add_dynamic_voice_menus,
            )

            add_dynamic_voice_menus(self)
        except Exception as e:
            print(f"Error adding dynamic voice menus: {e}")

        # Add dynamic keyboard layout menus
        try:
            from fenrirscreenreader.core.dynamicKeyboardLayoutMenu import (
                add_dynamic_keyboard_layout_menus,
            )

            add_dynamic_keyboard_layout_menus(self)
        except Exception as e:
            print(f"Error adding dynamic keyboard layout menus: {e}")

        # index still valid?
        if self.curr_index is not None:
            try:
                r = self.get_value_by_path(self.menuDict, self.curr_index)
                if r == {}:
                    self.curr_index = None
            except Exception as e:
                self.env["runtime"]["DebugManager"].write_debug_out(
                    "VmenuManager create_menu_tree: Error checking menu index validity: "
                    + str(e),
                    debug.DebugLevel.ERROR,
                )
                self.curr_index = None

    def execute_menu(self):
        if self.curr_index is None:
            return
        try:
            command = self.get_value_by_path(self.menuDict, self.curr_index)
            if command is not None:
                command.run()
                if self.closeAfterAction:
                    self.set_active(False)
        except Exception as e:
            try:
                self.inc_level()
                text = self.get_current_entry()
                self.env["runtime"]["OutputManager"].present_text(
                    text, interrupt=True
                )
            except Exception as ex:
                self.env["runtime"]["DebugManager"].write_debug_out(
                    "VmenuManager execute_menu: Error presenting menu text: "
                    + str(ex),
                    debug.DebugLevel.ERROR,
                )

    def inc_level(self):
        if self.curr_index is None:
            return False
        try:
            r = self.get_value_by_path(self.menuDict, self.curr_index + [0])
            if r == {}:
                return False
        except Exception as e:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "VmenuManager inc_level: Error accessing menu path: " + str(e),
                debug.DebugLevel.ERROR,
            )
            return False
        self.curr_index.append(0)
        return True

    def dec_level(self):
        if self.curr_index is None:
            return False
        if self.currMenu != "":
            if len(self.curr_index) <= 2:
                return False
        elif len(self.curr_index) == 1:
            return False
        self.curr_index = self.curr_index[: len(self.curr_index) - 1]
        return True

    def next_index(self):
        if self.curr_index is None:
            return False
        if self.curr_index[len(self.curr_index) - 1] + 1 >= len(
            self.get_nested_by_path(self.menuDict, self.curr_index[:-1])
        ):
            self.curr_index[len(self.curr_index) - 1] = 0
        else:
            self.curr_index[len(self.curr_index) - 1] += 1
        return True

    def get_curr_index(self):
        if self.curr_index is None:
            return 0
        return self.curr_index[len(self.curr_index) - 1]

    def prev_index(self):
        if self.curr_index is None:
            return False
        if self.curr_index[len(self.curr_index) - 1] == 0:
            self.curr_index[len(self.curr_index) - 1] = (
                len(
                    self.get_nested_by_path(
                        self.menuDict, self.curr_index[:-1]
                    )
                )
                - 1
            )
        else:
            self.curr_index[len(self.curr_index) - 1] -= 1
        return True

    def page_up(self):
        if self.curr_index is None:
            return False
        menu_size = len(
            self.get_nested_by_path(self.menuDict, self.curr_index[:-1])
        )
        if menu_size <= 1:
            return False
        jump_size = max(1, int(menu_size * 0.1))  # 10% of menu size, minimum 1
        new_index = self.curr_index[len(self.curr_index) - 1] - jump_size
        if new_index < 0:
            new_index = 0
        self.curr_index[len(self.curr_index) - 1] = new_index
        return True

    def page_down(self):
        if self.curr_index is None:
            return False
        menu_size = len(
            self.get_nested_by_path(self.menuDict, self.curr_index[:-1])
        )
        if menu_size <= 1:
            return False
        jump_size = max(1, int(menu_size * 0.1))  # 10% of menu size, minimum 1
        new_index = self.curr_index[len(self.curr_index) - 1] + jump_size
        if new_index >= menu_size:
            new_index = menu_size - 1
        self.curr_index[len(self.curr_index) - 1] = new_index
        return True

    def get_current_entry(self):
        return self.get_keys_by_path(self.menuDict, self.curr_index)[
            self.curr_index[-1]
        ]

    def fs_tree_to_dict(self, path_):
        for root, dirs, files in os.walk(path_):
            tree = {
                d
                + " "
                + _("Menu"): self.fs_tree_to_dict(os.path.join(root, d))
                for d in dirs
                if not d.startswith("__")
            }
            for f in files:
                try:
                    file_name, file_extension = os.path.splitext(f)
                    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
                    command = self.env["runtime"]["CommandManager"].load_file(
                        root + "/" + f
                    )
                    tree.update({file_name + " " + _("Action"): command})
                except Exception as e:
                    print(e)
            return tree  # note we discontinue iteration trough os.walk

    def get_nested_by_path(self, complete, path):
        path = path.copy()
        if path != []:
            index = list(complete.keys())[path[0]]
            nested = self.get_nested_by_path(complete[index], path[1:])
            return nested
        else:
            return complete

    def get_keys_by_path(self, complete, path):
        if not isinstance(complete, dict):
            return []
        d = complete
        for i in path[:-1]:
            d = d[list(d.keys())[i]]
        return list(d.keys())

    def get_value_by_path(self, complete, path):
        if not isinstance(complete, dict):
            return complete
        d = complete.copy()
        for i in path:
            d = d[list(d.keys())[i]]
        return d
