"""
Contains utility functions used by parts of the bot.

*Functions*
-----------
    sanitize(data: str, lower_case_value: bool = false) -> dict
    get_options() -> dict
    get_credentials() -> dict
    long_strings() -> dict
    get_params() -> dict
    log_this(messages: Union[str, list], channel: str = "",
            level: int = 20)
    cap(s: str) -> str
    make_files()
    replace_multiple(main_string: str, to_be_replaced: list,
                    new_string: str) -> str
    emoji_to_command(emoji: str) -> str
"""
import string
import json  # Used by long_strings(), get_params() and make_files()
import logging  # Used for logging
import os  # Used by make_files() to check if files exist
import sys  # Used to specify printing for logging
import imdb  # Used to disable logging for the module


BASE_37 = ":" + string.digits + string.ascii_uppercase
BASE_128 = list(
    string.digits +
    string.ascii_letters +
    "!#$€£¢¥¤&%()*+,-./;:<=>?@[]_{|}~ `¦§©®«»±µ·¿əʒ" +
    "ÆØÅÐÉÈÊÇÑÖ" +
    "æøåðéèêçñö"
)

# All of this is logging configuration
FORMAT = " %(asctime)s | %(name)-16s | %(levelname)-8s | %(message)s"
PRINTFORMAT = "%(asctime)s - %(message)s"
DATEFORMAT = "%Y-%m-%d %H:%M:%S"

logging.addLevelName(25, "PRINT")
loggingConfigParams = {
    "format": FORMAT,
    "datefmt": DATEFORMAT,
    "level": logging.INFO,
    "filename": "gwendolyn.log"
}
logging.basicConfig(**loggingConfigParams)
logger = logging.getLogger("Gwendolyn")
printer = logging.getLogger("printer")
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter(fmt=PRINTFORMAT, datefmt=DATEFORMAT))
printer.addHandler(handler)
printer.propagate = False

imdb._logging.setLevel("CRITICAL") # pylint: disable=protected-access
# Basically disables imdbpy logging, since it's being printed to the
# terminal.

def sanitize(data: str, lower_case_value: bool = False):
    """
    Sanitize and create a dictionary from a string.

    Each element is created from a line with a : in it. The key is left
    of the :, the value is right of it.

    *Parameters*
    ------------
        data: str
            The string to create a dict from.
        lower_case_value: bool = False
            Whether the value of each element should be lowercase.

    *Returns*
    ---------
        dct: dict
            The sanitized dictionary of elements.

    """
    data = data.splitlines()
    dct = {}
    for line in data:
        if line[0] != "#" and ":" in line:
            line_values = line.split(":")
            line_values[0] = line_values[0].lower()
            line_values[1] = line_values[1].replace(" ", "")
            if lower_case_value:
                line_values[1] = line_values[1].lower()

            if line_values[0] in ["testing guild ids", "admins"]:
                line_values[1] = line_values[1].split(",")
                if all(i.isnumeric() for i in line_values[1]):
                    line_values[1] = [int(i) for i in line_values[1]]

            if any(i == line_values[1] for i in ["true", "false"]):
                line_values[1] = (line_values[1] == "true")

            dct[line_values[0]] = line_values[1]

    return dct


def get_options():
    """
    Get the bot options as dict.

    *Returns*
    ---------
        options: dict
            The options of the bot.
    """
    with open("options.txt", "r", encoding="utf-8") as file_pointer:
        data = sanitize(file_pointer.read(), True)

    options = {}

    options["testing"] = data["testing"]
    options["guild_ids"] = data["testing guild ids"]
    options["admins"] = data["admins"]
    return options

def get_credentials():
    """
    Returns the credentials used by the bot as a dict.

    *Returns*
    ---------
        credentials: dict
            The credentials used by the bot.
    """
    with open("credentials.txt", "r", encoding="utf-8") as file_pointer:
        data = sanitize(file_pointer.read())

    credentials = {}

    credentials["token"] = data["bot token"]
    credentials["wordnik_key"] = data["wordnik api key"]
    credentials["mongo_db_user"] = data["mongodb user"]
    credentials["mongo_db_password"] = data["mongodb password"]
    credentials["wolfram_alpha_key"] = data["wolframalpha appid"]
    credentials["radarr_key"] = data["radarr api key"]
    credentials["sonarr_key"] = data["sonarr api key"]
    credentials["qbittorrent_username"] = data["qbittorrent username"]
    credentials["qbittorrent_password"] = data["qbittorrent password"]

    return credentials

def long_strings():
    """
    Get the data from gwendolyn/resources/long_strings.json.

    *Returns*
    ---------
        data: dict
            The long strings and their keys.
    """
    long_strings_path = "gwendolyn/resources/long_strings.json"
    with open(long_strings_path, "r", encoding="utf-8") as file_pointer:
        data = json.load(file_pointer)

    return data


def get_params():
    """
    Get the slash command parameters.

    *Returns*
    ---------
        params: dict
            The parameters for every slash command.
    """
    path = "gwendolyn/resources/slash_parameters.json"
    with open(path, "r", encoding="utf-8") as file_pointer:
        slash_parameters = json.load(file_pointer)

    options = get_options()

    if options["testing"]:
        for parameter in slash_parameters:
            slash_parameters[parameter]["guild_ids"] = options["guild_ids"]

    return slash_parameters


def log_this(messages, channel: str = "", level: int = 20):
    """
    Log something in Gwendolyn's logs.

    *Parameters*
    ------------
        messages: Union[str, list]
            A string or list of strings to be logged. If there are
            multiple strings and the level is PRINT (25) or higher,
            only the first string will be printed.
        channel: str = ""
            The channel the event to be logged occurred in. Will be
            logged along with the message(s).
        level: int = 20
            The level to log the message(s) at. If PRINT (25) or
            higher, the first message will be printed to the console.
    """
    channel = channel.replace("Direct Message with ", "")
    if isinstance(messages, str):
        messages = [messages]

    print_message = messages[0]

    for i, message in enumerate(messages):
        if channel != "":
            messages[i] = f"{message} - ({channel})"  # Adds channel ID
            # to log messages

    if len(messages) > 1:  # Tells user to check the log if there are
        # more messages there
        print_message += " (details in log)"

    if level >= 25:
        printer.log(level, print_message)

    for log_message in messages:
        logger.log(level, log_message)


def cap(input_string: str):
    """
    Capitalize a string like a movie title.

    That means "of" and "the" are not capitalized.

    *Parameters*
    ------------
        input_string: str
            The string to capitalized.

    *Returns*
    ---------
        return_string: str
            The capitalized string.
    """
    no_caps_list = ["of", "the"]
    word_number = 0
    string_list = input_string.split()
    return_string = ''
    for word in string_list:
        word_number += 1
        if word not in no_caps_list or word_number == 1:
            word = word.capitalize()
        return_string += word+" "
    return_string = return_string[:-1]
    return return_string


def make_files():
    """Create all the files and directories needed by Gwendolyn."""
    def make_json_file(path, content):
        """Create json file if it doesn't exist."""
        if not os.path.isfile(path):
            log_this(path.split("/")[-1]+" didn't exist. Making it now.")
            with open(path, "w", encoding="utf-8") as file_pointer:
                json.dump(content, file_pointer, indent=4)

    def make_txt_file(path, content):
        """Create txt file if it doesn't exist."""
        if not os.path.isfile(path):
            log_this(path.split("/")[-1]+" didn't exist. Making it now.")
            with open(path, "w", encoding="utf-8") as file_pointer:
                file_pointer.write(content)

    def directory(path):
        """Create directory if it doesn't exist."""
        if not os.path.isdir(path):
            os.makedirs(path)
            log_this("The "+path.split("/")[-1]+" directory didn't exist")

    file_path = "gwendolyn/resources/starting_files.json"
    with open(file_path, "r", encoding="utf-8") as file_pointer:
        data = json.load(file_pointer)

    for path, content in data["json"].items():
        make_json_file(path, content)

    for path, content in data["txt"].items():
        make_txt_file(path, content)

    for path in data["folder"]:
        directory(path)


def replace_multiple(main_string: str, to_be_replaced: list, new_string: str):
    """
    Replace multiple substrings in a string with the same substring.

    *Parameters*
    ------------
        main_string: str
            The string to replace substrings in.
        to_be_replaced: list
            The substrings to replace.
        new_string: str
            The string to replace the substrings with.

    *Returns*
    ---------
        main_string: str
            The string with the substrings replaced.
    """
    # Iterate over the strings to be replaced
    for elem in to_be_replaced:
        # Check if string is in the main string
        if elem in main_string:
            # Replace the string
            main_string = main_string.replace(elem, new_string)

    return main_string


def emoji_to_command(emoji: str):
    """
    Convert emoji to text.

    *Parameters*
    ------------
        emoji: str
            The emoji to decipher.

    *Returns*
    ---------
        : str
            The deciphered string.
    """
    if emoji == "1️⃣":
        return_value = 1
    elif emoji == "2️⃣":
        return_value = 2
    elif emoji == "3️⃣":
        return_value = 3
    elif emoji == "4️⃣":
        return_value = 4
    elif emoji == "5️⃣":
        return_value = 5
    elif emoji == "6️⃣":
        return_value = 6
    elif emoji == "7️⃣":
        return_value = 7
    elif emoji == "🎲":
        return_value = "roll"
    elif emoji == "❌":
        return_value = "none"
    elif emoji == "✔️":
        return_value = 1
    else:
        return_value = ""

    return return_value

def encode_id(info: list):
    letters = list(":".join(info))
    dec = 0
    for i, letter in enumerate(letters):
        try:
            dec += (37**i) * BASE_37.index(letter.upper())
        except ValueError:
            log_this(f"Could not encode letter {letter}", level=30)

    custom_id = []

    while dec:
        custom_id.append(BASE_128[dec % 128])
        dec = dec // 128

    custom_id = ''.join(custom_id)
    log_this(f"Encoded {info} to {custom_id}")
    return custom_id


def decode_id(custom_id: str):
    letters = list(custom_id)
    dec = 0
    for i, letter in enumerate(letters):
        dec += (128**i) * BASE_128.index(letter)

    info_string = []

    while dec:
        info_string.append(BASE_37[dec % 37])
        dec = dec // 37

    info = ''.join(info_string).split(':')
    log_this(f"Decoded {custom_id} to be {info}")
    return ''.join(info_string).split(':')
