#!/usr/bin/env python3
# -*- coding: utf-8 -*-

__author__ = "Christian Heider Nielsen"
__doc__ = r"""

           Created on 14/01/2020
           """

__all__ = [
    "kws_sink",
    "sink",
    "prod",
    "collate_first_dim",
    "call_identity",
    "args_sink",
    "identity",
    "invert_shallow_mapping",
    "flip_two_level_mapping",
    "swap_mapping_order",
    "nop",
    "empty_str",
    "list_keys",
    "first_key",
    "last_key",
]

import operator
from collections import defaultdict
from copy import deepcopy
from functools import reduce
from typing import (
    Any,
    Callable,
    Dict,
    Iterable,
    Iterator,
    Mapping,
    Sequence,
    Tuple,
    List,
)

from warg import Number, drop_unused_kws


def list_keys(d: Dict) -> List[Any]:
    return list(d.keys())


def first_key(d: Dict) -> Any:
    return list_keys(d)[0]


def last_key(d: Dict) -> Any:
    return list_keys(d)[-1]


def empty_str() -> str:
    """

    :return:
    :rtype:
    """
    return ""


def nop() -> None:
    """
    :rtype: None
    """
    pass


def identity(a: Any) -> Any:
    """description"""
    return a


@drop_unused_kws
def kws_sink(*args) -> Tuple[Any, ...]:
    """
    Returns args without any modification what so ever. Drops kws
    :return:"""
    return args


def call_identity(*args, **kwargs) -> Tuple[Tuple[Any, ...], Dict[str, Any]]:
    """

    :param args:
    :param kwargs:
    :return:"""
    return args, kwargs


# noinspection PyUnusedLocal
def args_sink(*args, **kwargs) -> Dict[str, Any]:
    """

    :param args:
    :param kwargs:
    :return:"""
    return kwargs


# noinspection PyUnusedLocal
def sink(*args, **kwargs) -> None:
    """
    Returns None, but accepts everything

    :param args:
    :param kwargs:
    :return:"""
    return


def prod(iterable: Iterable[Number]) -> Number:
    """
    Calculate the product of the a Iterable of int or floats
    :param iterable:
    :return:"""
    return reduce(operator.mul, iterable, 1)


def collate_first_dim(batch: Iterable) -> Tuple:
    """

    :param batch:
    :return:"""
    return tuple(zip(*batch))


def invert_shallow_mapping(m: Mapping) -> Dict:
    """

    :param m:
    :return:
    """
    return {v: k for k, v in m.items()}


def flip_two_level_mapping(m: Mapping) -> Dict:
    """
    result = {}
    [result.setdefault(a, {}).update({k:b}) for k, v in m.items() for a, b in v.items()]
    return result

    :param m:
    :return:
    """

    flipped = defaultdict(dict)
    for key, val in m.items():
        for sub_key, sub_val in val.items():
            flipped[sub_key][key] = sub_val
    return flipped


def swap_mapping_order(m: Mapping, order: Sequence[int]) -> Mapping:
    """

    :param m:
    :param order:
    :return:
    """
    order = [*order]

    def deep_swap(dict_, level):
        """

        :param dict_:
        :param level:
        :return:
        """

        def swap_two_level_dict(a):
            """

            :param a:
            :return:
            """
            b = defaultdict(dict)
            for key1, value1 in a.items():
                for key2, value2 in value1.items():
                    b[key2].update({key1: value2})
            return b

        dict_ = deepcopy(dict_)
        if level == 0:
            dict_ = swap_two_level_dict(dict_)
        else:
            for key in dict_:
                dict_[key] = deep_swap(dict_[key], level - 1)
        return dict_

    for pas_no in range(len(order) - 1, 0, -1):
        for i in range(pas_no):
            if order[i] > order[i + 1]:
                temp = order[i]
                order[i] = order[i + 1]
                order[i + 1] = temp
                m = deep_swap(m, i)
    return m


def chain_filter(it: Iterable, *filters: Callable) -> Iterator:
    """
    Apply a sequence of callables to an iterable through filter; filtering the iterable to the subset of a callable
    returns

    Args:
        it (Iterable):
            iterable to be filtered
        filters (Callable):
            The filter callables

    Returns:
        Iterator:
            returns an iterator yielding those items of iterable for which all(filters(item)) is true. If filters are
            None, return the items that are true.
    """
    for f in filters:
        it = filter(f, it)
    return it


def chain_apply(it: Iterable, *callables: Callable) -> Iterable:
    """
    Apply a sequence of callables to an iterable; apply the iterable sequentially in callables order

    Args:
        it (Iterable):
            iterable to be applied to
        callables (Callable):
             The applying callables

    Returns:
        Iterable:
            returns the iterable with all the callables applied.
    """
    for f in callables:
        it = f(it)
    return it


if __name__ == "__main__":

    def asud() -> None:
        """
        :rtype: None
        """
        a = {"b": 1, "h": 2}
        print(invert_shallow_mapping(a))

    def asjdnasid() -> None:
        """
        :rtype: None
        """
        a = {
            "b": {"c": {"d": [0, 1, 2], "e": [3, 4, 5, 6]}, "f": [7, 8], "g": [9]},
            "h": {"j": [10, 11]},
        }
        print(flip_two_level_mapping(a))

    def asidj() -> None:
        """
        :rtype: None
        """
        test_dict = {
            "a": {"c": {"e": 0, "f": 1}, "d": {"e": 2, "f": 3}},
            "b": {"c": {"g": 4, "h": 5}, "d": {"j": 6, "k": 7}},
        }
        result = swap_mapping_order(test_dict, [2, 0, 1])
        print(result)

    # asud()
    asidj()
    # asjdnasid()
