#!/usr/bin/python3

try:
    from .utils import aion_data_path as _aion_data_path, BaseXMLReader as _BaseXMLReader, BaseXMLWriter as _BaseXMLWriter
except ImportError:
    from utils import aion_data_path as _aion_data_path, BaseXMLReader as _BaseXMLReader, BaseXMLWriter as _BaseXMLWriter


config_file = _aion_data_path + "/config.xml"


class Aion:
    """
    get infos about all aion internal configs (and change them)

    :since: 0.1.0
    """

    def __init__(self) -> None:
        """
        set all class values

        :return: None

        :since: 0.1.0
        """
        self.all_listening_modes = ["auto", "manual"]
        self.all_stt_engines = ["google", "pocketsphinx"]
        self.all_time_formats = ["12", "24"]
        self.all_tts_engines = ["pico2wave", "espeak"]
        self.supported_languages = ["de_DE", "en_US"]

        self._aion_cfg_reader = _BaseXMLReader(config_file)
        self._aion_cfg_writer = _BaseXMLWriter(config_file)

    def get_hotword_file(self) -> str:
        """
        get set hotword file path

        :return: str
            returns path of the hotword file
            syntax: <hotword file path>
            example: "/usr/local/aion-<aion_version>/etc/Aion.pmdl"

        :since: 0.1.0
        """
        from glob import glob
        for value_list in self._aion_cfg_reader.get_infos(["hotword_file"]).values():
            for config in value_list:
                if config["parent"]["tag"] == "aion":
                    return glob(config["text"])[0]

    def get_language(self) -> str:
        """
        get set language locale

        :return: str
            returns language locale
            syntax: <language locale>
            example: "en_US"

        :since: 0.1.0
        """
        for value_list in self._aion_cfg_reader.get_infos(["language"]).values():
            for config in value_list:
                if config["parent"]["tag"] == "aion":
                    return config["text"]

    def get_listening_mode(self) -> str:
        """
        get set listening mode

        :return: str
            returns listening mode
            syntax: <listening mode>
            example: "auto"

        :since: 0.1.0
        """
        for value_list in self._aion_cfg_reader.get_infos(["listening_mode"]).values():
            for config in value_list:
                if config["parent"]["tag"] == "aion":
                    return config["text"]

    def get_pid_manipulation_number(self) -> int:
        """
        get set pid manipulation number

        :return: int
            returns the pid manipulation number
            syntax: <pid manipulation number>
            example: 4

        :since: 0.1.0
        """
        for value_list in self._aion_cfg_reader.get_infos(["pid_manipulation_number"]).values():
            for config in value_list:
                if config["parent"]["tag"] == "aion":
                    return int(config["text"])

    def get_stt_engine(self) -> str:
        """
        get set speech-to-text engine

        :return: str
            returns speech-to-text engine
            syntax: <speech-to-text engine>
            example: "google"

        :since: 0.1.0
        """
        for value_list in self._aion_cfg_reader.get_infos(["listening_source"]).values():
            for config in value_list:
                if config["parent"]["tag"] == "aion":
                    return config["text"]

    def get_time_format(self) -> int:
        """
        get set time format

        :return: str
            returns time format
            syntax: <time format>
            example: 24

        :since: 0.1.0
        """
        for value_list in self._aion_cfg_reader.get_infos(["time_format"]).values():
            for config in value_list:
                if config["parent"]["tag"] == "aion":
                    return int(config["text"])

    def get_tts_engine(self) -> str:
        """
        get set text-to-speech engine

        :return: str
            returns text-to-speech engine
            syntax: <text-to-speech engine>
            example: "espeak"

        :since: 0.1.0
        """
        for value_list in self._aion_cfg_reader.get_infos(["tts_engine"]).values():
            for config in value_list:
                if config["parent"]["tag"] == "aion":
                    return config["text"]

    @staticmethod
    def reset() -> None:
        """
        resets the aion config values

        :return: None

        :since: 0.1.0
        """
        from locale import getdefaultlocale

        _BaseXMLWriter(config_file).remove("config", "aion")
        aion_cfg_writer = _BaseXMLWriter(config_file)
        aion_cfg_writer.add("config", "aion")
        aion_cfg_writer.add("aion", "hotword_file", text="/usr/local/aion-*/etc/Aion.pmdl")
        aion_cfg_writer.add("aion", "language", text=str(getdefaultlocale()[0]))
        aion_cfg_writer.add("aion", "listening_mode", text="auto")
        aion_cfg_writer.add("aion", "pid_manipulation_number", text="4")
        aion_cfg_writer.add("aion", "stt_engine", text="pocketsphinx")
        aion_cfg_writer.add("aion", "time_format", text="12")
        aion_cfg_writer.add("aion", "tts_engine", text="espeak")
        aion_cfg_writer.write()

    def set_hotword_file(self, hotword_file: str) -> None:
        """
        sets the hotword file

        :param hotword_file: str
            location from the new hotword file
            syntax: <hotword_file>
           example: "/usr/local/aion-*/etc/Aion.pmdl"
        :return: None

        :since: 0.1.0
        """
        try:
            from ._error_codes import config_no_hotword_file_file
        except ImportError:
            from _error_codes import config_no_hotword_file_file
        from os.path import isfile

        if isfile(hotword_file):
            self._aion_cfg_writer.update("aion", "hotword_file", text=str(hotword_file))
            self._aion_cfg_writer.write()
        else:
            raise FileNotFoundError("Errno: " + config_no_hotword_file_file + " - Couldn't find file '" + hotword_file + "'")

    def set_language(self, language: str) -> None:
        """
        sets the language locale

        :param language: str
            new language locale
            syntax: <language locale>
            example: "en_US"
        :return: None

        :since: 0.1.0
        """
        from colorama import Fore

        if language in self.supported_languages:
            self._aion_cfg_writer.update("aion", "language", text=str(language))
            self._aion_cfg_writer.write()
        else:
            print(Fore.RED + "'" + language + "' isn't an official supported language for speech output (type 'aion.Config.supported_languages' to see all supported languages).\n"
                                              "The complete speech output is now in English. You have to create your own '.lng' file to support your language.\n" +
                                              str(self.supported_languages) + " are the supported languages" + Fore.RESET)
            self._aion_cfg_writer.update("aion", "language", text=str(language))
            self._aion_cfg_writer.write()

    def set_listening_mode(self, listening_mode: str) -> None:
        """
        sets the listening mode

        :param listening_mode: str
            new listening mode
            syntax: <listening mode>
            example: "auto"
        :return: None

        :since: 0.1.0
        """
        try:
            from ._error_codes import config_no_supported_listening_mode
        except ImportError:
            from _error_codes import config_no_supported_listening_mode

        if listening_mode in self.all_listening_modes:
            self._aion_cfg_writer.update("aion", "listening_mode", text=str(listening_mode))
            self._aion_cfg_writer.write()
        else:
            raise ValueError("Errno: " + config_no_supported_listening_mode + " - " + str(listening_mode) + " isn't a supported listening mode. Please choose from these: " + str(self.all_listening_modes))

    def set_pid_manipulation_number(self, pid_manipulation_number: int) -> None:
        """
        sets the pid manipulation number

        :param pid_manipulation_number: int
            new pid manipulation number
            syntax: <pid manipulation number>
            example: 4
        :return: None

        :since: 0.1.0
        """
        self._aion_cfg_writer.update("aion", "listening_mode", text=str(pid_manipulation_number))
        self._aion_cfg_writer.write()

    def set_stt_engine(self, stt_engine: str) -> None:
        """
        sets the speech-to-text engine

        :param stt_engine: str
            new speech-to-text engine
            syntax: <speech-to-text engine>
            example: "pocketsphinx"
        :return: None

        :since: 0.1.0
        """
        try:
            from ._error_codes import config_no_supported_listening_source
        except ImportError:
            from _error_codes import config_no_supported_listening_source

        if stt_engine in self.all_stt_engines:
            self._aion_cfg_writer.update("aion", "stt_engine", text=str(stt_engine))
            self._aion_cfg_writer.write()
        else:
            raise ValueError("Errno: " + config_no_supported_listening_source + " - " + str(stt_engine) + " isn't a supported listening source. Please choose from these: " + str(self.all_stt_engines))

    def set_time_format(self, time_format: str) -> None:
        """
        sets the time format

        :param time_format: str
            new time format
            syntax: <time format>
            example: "24"
        :return: None

        :since: 0.1.0
        """
        try:
            from ._error_codes import config_no_supported_time_format
        except ImportError:
            from _error_codes import config_no_supported_time_format

        if str(time_format) in self.all_time_formats:
            self._aion_cfg_writer.update("aion", "time_format", text=str(time_format))
            self._aion_cfg_writer.write()
        else:
            raise ValueError("Error: " + config_no_supported_time_format + " - " + str(time_format) + " isn't a supported time format. Please choose from these: " + str(self.all_time_formats))

    def set_tts_engine(self, tts_engine: str) -> None:
        """
        sets the text-to-speech engine

        :param tts_engine: str
            new text-to-speech engine
            syntax: <text-to-speech engine>
            example: "espeak"
        :return: None

        :since: 0.1.0
        """
        try:
            from ._error_codes import config_no_supported_tts_engine
        except ImportError:
            from _error_codes import config_no_supported_tts_engine

        if tts_engine in self.all_tts_engines:
            self._aion_cfg_writer.update("aion", "tts_engine", text=str(tts_engine))
            self._aion_cfg_writer.write()
        else:
            raise ValueError("Errno: " + config_no_supported_tts_engine + " - " +str(tts_engine) + " isn't a supported tts engine. Please choose from these: " + str(self.all_tts_engines))


def add_entry(name: str, text: str = None, attrib: dict = {}, parent_name: str = "config", parent_attrib: dict = {}) -> None:
    """
    adds an entry from the config file

    :param name: str
        name of the new entry
        syntax: <name>
        example: "test_entry"
    :param text: str, optional
        text of the new entry
        syntax: <text>
        example: "Test"
    :param attrib: dict, optional
        attributes of the new entry
        syntax: {<attribute name>: <attribute value>}
        example: {"test_attrib", "test"}
    :param parent_name: str, optional
        name of the parent entry to which the entry is added
        syntax: <parent name>
        example: "test_parent"
    :param parent_attrib: dict, optional
        attributes of the parent entry
        syntax: {<parent attribute name>: <parent attribute value>}
        example: {"version": "1.0.0"}
    :return: None

    :since: 0.1.0
    """
    try:
        from ._error_codes import config_name_config_is_used_as_root_name, config_character_must_be_in_alphabet
    except ImportError:
        from _error_codes import config_name_config_is_used_as_root_name, config_character_must_be_in_alphabet

    cfg_writer = _BaseXMLWriter(config_file)

    if name == "config":
        raise NameError("Errno: " + config_name_config_is_used_as_root_name + " - Name 'config' is already used as root name")
    for char in name:
        if char not in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_":
            raise IndexError("Errno: " + config_character_must_be_in_alphabet + " - " + char + " in " + name + " must be in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_'")

    cfg_writer.add(parent_name, name, text, attrib, parent_attrib=parent_attrib)
    cfg_writer.write()


def delete_entry(name: str, parent_name: str = "config", parent_attrib: dict = {}) -> None:
    """
    deletes an entry from the config file

    :param name: str
        name of the entry to be deleted
        syntax: <name>
        example: "test_entry"
    :param parent_name: str, optional
        name of the parent entry of the entry to be deleted
        syntax: <parent name>
        example: "test_parent"
    :param parent_attrib: dict, optional
        attributes of the parent entry from the entry to be searched
        syntax: {<attribute name>: <attribute value>}
        example: {"test_attrib", "test"}
    :return: None

    :since: 0.1.0
    """
    try:
        from ._error_codes import config_root_tag_cannot_be_removed
    except ImportError:
        from _error_codes import config_root_tag_cannot_be_removed

    cfg_writer = _BaseXMLWriter(config_file)

    if name == "config":
        raise NameError("Errno: " + config_root_tag_cannot_be_removed + " - The root tag cannot be removed")

    cfg_writer.remove(parent_name, name, parent_attrib)
    cfg_writer.write()


def get_entry(name: str, parent_name: str = None, parent_attrib: dict = None) -> dict:
    """
    get infos about an entry

    :param name: str
        name of the entry to be searched
        syntax: <name>
        example: "test_entry"
    :param parent_name: str, optional
        name of the parent entry of the entry to be deleted
        syntax: <parent name>
        example: "test_parent"
    :param parent_attrib: dict, optional
        attributes of the parent entry
        syntax: {<attribute name>: <attribute value>}
        example: {"test_attrib", "test"}
    :return: dict
        returns the infos about the given entry
        syntax: {"text": <text of entry>, "attrib": <attributes of entry>}
        e.g.: {"text": "entry text", "attrib": {"version": "1.0.0"}}

    :since: 0.1.0
    """
    cfg_reader = _BaseXMLReader(config_file)
    return_dict = {}

    if parent_name:
        for value_list in cfg_reader.get_infos([name]).values():
            for entry in value_list:
                if entry["parent"] == parent_name:
                    if parent_attrib:
                        if entry["attrib"] == parent_attrib:
                            return_dict["text"] = entry["text"]
                            return_dict["attrib"] = entry["attrib"]
                            break
                    else:
                        return_dict["text"] = entry["text"]
                        return_dict["attrib"] = entry["attrib"]
                        break
    else:
        return_dict["text"] = cfg_reader.get_infos([name]).items().index(0)["text"]
        return_dict["attrib"] = cfg_reader.get_infos([name]).items().index(0)["attrib"]

    return return_dict


def update_entry(name: str, text: str = None, attrib: dict = {}, parent_name: str = "config", **extra: str) -> None:
    """
    updates an entry

    :param name: str
        name of the entry to be updated
        syntax: <name>
        example: "test_entry"
    :param text: str, optional
        new text of the entry to be updated
        syntax: <text>
        example: "new test text"
    :param attrib: dict, optional
        new attributes of the entry to be updated
        syntax: {<attribute name>: <attribute value>}
        example: {"new_test_attrib", "new_test"}
    :param parent_name: str, optional
        parent entry of the entry to be updated
        syntax: <parent name>
        example: "test_parent"
    :return: None

    :since: 0.1.0
    """
    try:
        from ._error_codes import config_root_tag_cannot_be_updated
    except ImportError:
        from _error_codes import config_root_tag_cannot_be_updated

    cfg_writer = _BaseXMLWriter(config_file)

    if name == "config":
        raise NameError("Errno: " + config_root_tag_cannot_be_updated + " - Can't update root name")

    if extra:
        cfg_writer.update(parent_name, name, text, {**attrib, **extra})
    else:
        cfg_writer.update(parent_name, name, text, attrib)