from .simple_methods import *
from .utils import list_to_bin_dict
import random


class PermutationGenerator:
    """
    PermutationGenerator is designed to generates all possible permutations of elements in a list with a given number of selections.
    The permuation is generated by a given iterator number and it is the n-th permutation of the all possible permutations.
    As default, the n-th permutation is generated by the lexicographic order.
    But, if the priority of the elements is given, the permutation is generated by the given priority.

    The elements can be duplicated in the list.

    .. _permutationgenerator:

    """

    def __init__(self):
        """
        Initialize the PermutationGenerator.
        """
        self.element_list_initialized = False
        self.number_of_selection_initialized = False
        self.must_have_elements = False

    def set_parameters(self, element_list: list, in_number_of_selection: int):
        """
        Initialize the PermutationGenerator with the given number of selections and the element list.

        Args:
            in_number_of_selection (int): The number of selections.
            element_list (list): The list of elements.
        """
        self.in_number_of_selection = in_number_of_selection
        self.element_list = element_list

        self.element_list_initialized = True
        self.number_of_selection_initialized = True
        self.max_possible = self.__duplicated_case_recursive(
            list_to_bin_dict(self.element_list, True)[0], self.in_number_of_selection
        )

    def __duplicated_case_recursive(self, in_list: list, in_number_of_select: int):
        """
        Recursive function to calculate all possible number of permutation cases.

        Args:
            in_list (list): The list of elements.
            in_number_of_select (int): The number of selections.

        Returns:
            int: The number of all possible cases.
        """
        if sum(in_list) == 1:
            return 1

        result_sum = 0
        if in_number_of_select == 1:
            for i in in_list:
                if i != 0:
                    result_sum += 1
            return result_sum
        for i in range(len(in_list)):
            if in_list[i] != 0:
                result_sum += self.__duplicated_case_recursive(
                    in_list[:i] + [in_list[i] - 1] + in_list[i + 1 :],
                    in_number_of_select - 1,
                )
        return result_sum

    def all_cases(self) -> list:
        """
        Return all possible permutations from a set parameters.

        Returns:
            list: All possible permutations.
        """
        if not self.element_list_initialized:
            raise Exception("element_list is not initialized")
        if not self.number_of_selection_initialized:
            raise Exception("number_of_selection is not initialized")

        result_list = []
        for i in range(self.max_possible):
            result_list.append(self.__permutation_core(i))
        return result_list

    def n_th_case(self, in_iterator: int) -> list:
        """
        Return the n-th permutation.

        Args:
            in_iterator (int): The iterator that indicates the n-th permutation.

        Returns:
            list: The n-th permutation.
        """
        if not self.element_list_initialized:
            raise Exception("element_list is not initialized")
        if not self.number_of_selection_initialized:
            raise Exception("number_of_selection is not initialized")

        return self.__permutation_core(in_iterator)

    def n_to_m_th_cases(self, in_n_iterator: int, in_m_iterator: int) -> list:
        """
        Return the n-th to m-th permutations.

        Args:
            in_n_iterator (int): The iterator that indicates the n-th permutation.
            in_m_iterator (int): The iterator that indicates the m-th permutation.

        Returns:
            list: The n-th to m-th permutations.
        """
        if not self.element_list_initialized:
            raise Exception("element_list is not initialized")
        if not self.number_of_selection_initialized:
            raise Exception("number_of_selection is not initialized")

        result_list = []
        for i in range(in_n_iterator, in_m_iterator + 1):
            result_list.append(self.__permutation_core(i))
        return result_list

    def __permutation_recursive(
        self, in_iterator: int, in_list: list, in_number_of_select: int
    ):
        result_list = []
        if in_number_of_select == 1:
            filtered_list = []
            # print("in 0 -> ", in_list, in_iterator)
            for i, data in enumerate(in_list):
                if data != 0:
                    filtered_list.append(i)
            result_list.append(filtered_list[in_iterator])
            # print("in 1 -> ", filtered_list, result_list)
            return result_list
        if in_number_of_select == 0:
            return result_list
        # results = list(filter(lambda x: x.startswith('f'), animals))

        for i in range(len(in_list)):
            if in_list[i] != 0:
                test_list = in_list[:i] + [in_list[i] - 1] + in_list[i + 1 :]
                test_list_possible_cases = self.__duplicated_case_recursive(
                    test_list, in_number_of_select - 1
                )
                if test_list_possible_cases > in_iterator:
                    result_list.append(i)
                    result_list += self.__permutation_recursive(
                        in_iterator,
                        test_list,
                        in_number_of_select - 1,
                    )

                    break
                else:
                    in_iterator -= test_list_possible_cases

        return result_list

    def __permutation_core(self, in_iterator: int):
        if not in_iterator < self.max_possible:
            return []

        bin_list, element_list = list_to_bin_dict(self.element_list, True)

        result_iterator = self.__permutation_recursive(
            in_iterator, bin_list, self.in_number_of_selection
        )

        result_list = []
        for i in result_iterator:
            result_list.append(element_list[i])

        return result_list
