"""
Functions to deal with orderings, rankings, and permutations.

Orderings are lists of values where the order of these values is important.
It is assumed that values inside orderings are distinct.

Rankings are lists of integers indicating the indexes of some values.
Rankings are basically the index-wise reprsentation of orderings.
A ranking is built from an ordering, and using a "reference ordering", which
you index to get back the values from the indexes.

Rankings are similar to mathematical "permutations".
"""
import numpy as np
from tprint import tprint
from kemeny_young import rank_aggregation

VERBOSE=False

# def inverse_permutation(permutation: list[int]) -> list[int]:
#     """Return the inverse of a given permutation."""
#     permutation = np.array(permutation)
#     inverse = np.empty_like(permutation)
#     inverse[permutation] = np.arange(permutation.size)
#     return inverse


def inverse_permutation(permutation: list[int]) -> list[int]:
    """Return the inverse of a given permutation.
    Args:
        permutation (list[int]): A permutation to get the inverse of. It can be
            represented as a list[int] or a numpy array of integers.
    Returns:
        list[int]: A numpy array of integers representing the inverse permutation.
    """
    permutation = np.array(permutation)
    inverse = np.arange(permutation.size)
    inverse[permutation] = inverse.copy()
    return inverse



def get_orderings_from_table(table: np.ndarray, column_index: int =0) -> list:
    """Extract a list of orderings from a table coming out of a sql query.
    This basically means that you extract values of the given column, while
    keeping order but removing duplicates.

    Args:
        table: The table, coming from an sql query, containing ordered values.
        column_index: The index of the column in the table from which to
            extract the orderings from.
    """
    table = np.array(table)
    values = table[:,column_index]
    ranking, indexes = np.unique(values, return_index=True)
    return values[np.sort(indexes)]  # distinct ordered values


def get_all_orderings_from_table(table: list[tuple]) -> dict:
    orders = dict()
    for line in table:
        parameter, criteria, sum_value = line
        if orders.get(criteria) is None:
            orders[criteria] = []
        orders[criteria].append(parameter)
    return orders


def rankings_from_orderings(orderings: list[list[str]]) -> list[list[int]]:
    """Return a list of rankings from a given list of orderings.
    Orderings are lists[str], where order is important.
    Rankings are list[int], and represent the permutation that would sort the
    matching ordering into alphabetical order.
    """
    orderings = np.array(orderings)
    rankings = np.argsort(orderings, axis=1)
    if VERBOSE:
        print("found rankings :")
        tprint(rankings)
    return rankings


def ranking_from_ordering(ordering: list[str]) -> list[int]:
    return rankings_from_orderings([ordering])[0]

def ordering_from_ranking(ranking: list[int], values_to_order: list[str]) -> list[str]:
    """Get an order of values from a ranking of these values.
    This is basically the inverse function of *rankings_from_orderings*.
    Args:
        ranking (list[int]): The ranking (permutation) you want to convert into
                             an ordering.
        values_to_order (list[str]): The values to put in the ordering.
                                        Order of these values doens't matter.
    Returns:
        list[str]: The ordering of the values, matching the given ranking (permutation).
    """
    # applying the inverse permutation of *ranking* on sorted values_to_order
    inversed_ranking = inverse_permutation(ranking)
    return np.sort(values_to_order)[inversed_ranking]


# def ordering_from_ranking(ranking: list[int],
#                           reference_ordering: list[str],
#                           reference_ranking: list[int]):
#     """Get an ordering of values from a ranking, using a reference ordering and
#     ranking (the ranking must match the ordering)."""
#     # make sure you are using numpy arrays
#     ref_ordering = np.array(reference_ordering)
#     ref_ranking = np.array(reference_ranking)
#     # get back the best order from the best ranking
#     ordering = ref_ordering[ref_ranking[[ranking]]][0]
#     if VERBOSE: print("best ordering :", ordering)
#     return ordering


def aggregate_rankings(rankings: list[list[int]]) -> tuple[int, ...]:
    """Calculate the aggregation of all given rankings, that is the ranking
    that is the nearest to all given rankings."""
    min_dist, best_ranking = rank_aggregation(rankings)
    if VERBOSE: print("best ranking :", best_ranking)
    return best_ranking


