import random
from functools import reduce

from pathlib import Path
from typing import List, Dict, Any, Callable


def singleton(class_) -> Callable:
    """
    This function is a decorator to be used on classes, that adopts the singleton approach,
    where only a single entity of the class will exist.

    :rtype: Path
    """

    instances = {}

    def get_instance(*args, **kwargs):
        if class_ not in instances:
            instances[class_] = class_(*args, **kwargs)
        return instances[class_]

    return get_instance


def get_project_root() -> Path:
    """
    This function is for getting the root path location of the emberblast, that may be different depending
    where you placed it.

    :rtype: Path
    """
    return Path(__file__).parent.parent


def generate_random_adjacent_matrix(size: int) -> List[List[int]]:
    """
    This function generates a squared adjacent matrix, with different proportions for
    1 and 0 vertexes. As it's a square matrix, it means that the numbers of columns are always the
    same numbers of the rows.

    :param int size: The size of the matrix, for example 4, will generate a 4x4 matrix.
    :rtype: List[List[int]]
    """
    choices = [0] * 25 + [1] * 75
    return [[random.choice(choices) for x in range(size)] for y in range(size)]


def is_square_matrix(matrix: List[List[int]]) -> bool:
    """
    This is a basic validation function, to check whether a matrix has the same number of lines and columns.

    :param List[List[int]] matrix: The matrix to be validated.
    :rtype: bool
    """
    return len(matrix) == len(matrix[0])


def generate_visited_default_matrix(size: int) -> List[List[bool]]:
    """
    This function will generate a square matrix where all the value of all the vertexes
    are false, the matrix generated by this function it's used in matrix traversing algorithm for
    identifying which vertexes have been visited by the algorithm.

    :param int size: The size of the matrix, for example 4, will generate a 4x4 matrix.
    :rtype: List[List[bool]]
    """
    return [[False for x in range(size)] for x in range(size)]


def deep_get(dictionary, *keys):
    return reduce(lambda d, key: d.get(key) if d else None, keys, dictionary)


def find_key_recursively(obj: Dict, key: str) -> Any:
    """
    Thus function receives a dictionary, and iterates recursively until it finds the provided key.

    :param Dict obj: The dictionary to be traversed.
    :param str key: The key to be searched in the dictionary.
    :rtype: Any
    """
    if key in obj:
        return obj[key]
    for k, v in obj.items():
        if isinstance(v, dict):
            return find_key_recursively(v, key)


def convert_letter_to_number(letter: str) -> int:
    """
    This function receives a string representing a letter of the alphabet, and convert it to
    its respective number in ascending order, for example:
    A - 1
    B - 2
    C - 3
    D - 4

    :param str letter: The letter to be converted.
    :rtype: int
    """
    return [ord(char) - 96 for char in letter.lower()][0] - 1


def convert_number_to_letter(number: int) -> str:
    """
    This function receives a number, and converts it to its respective alphabet letter in ascending order,
    for example:
    1 - A
    2 - B
    3 - C
    4 - D

    :param int number: The number to be converted.
    :rtype: str
    """
    return chr(ord('@') + number + 1)
