""" Tools for working with collections """

import itertools
import sys
from warnings import warn

if sys.version_info < (3, 3):
    from collections import Iterable
else:
    from collections.abc import Iterable

__author__ = "Vince Reuter"
__email__ = "vreuter@virginia.edu"


__all__ = ["is_collection_like", "powerset", "asciify_dict", "merge_dicts"]


def merge_dicts(x, y):
    """
    Merge dictionaries

    :param Mapping x: dict to merge
    :param Mapping y: dict to merge
    :return Mapping: merged dict
    """
    z = x.copy()
    z.update(y)
    return z


def is_collection_like(c):
    """

    Determine whether an object is collection-like.

    :param object c: Object to test as collection
    :return bool: Whether the argument is a (non-string) collection
    """
    return isinstance(c, Iterable) and not isinstance(c, str)


def uniqify(seq):  # Dave Kirby
    """
    Return only unique items in a sequence, preserving order

    :param list seq: List of items to uniqify
    :return list[object]: Original list with duplicates removed
    """
    # Order preserving
    seen = set()
    return [x for x in seq if x not in seen and not seen.add(x)]


def powerset(items, min_items=None, include_full_pop=True, nonempty=False):
    """
    Build the powerset of a collection of items.

    :param Iterable[object] items: "Pool" of all items, the population for
        which to build the power set.
    :param int min_items: Minimum number of individuals from the population
        to allow in any given subset.
    :param bool include_full_pop: Whether to include the full population in
        the powerset (default True to accord with genuine definition)
    :param bool nonempty: force each subset returned to be nonempty
    :return list[object]: Sequence of subsets of the population, in
        nondecreasing size order
    :raise TypeError: if minimum item count is specified but is not an integer
    :raise ValueError: if minimum item count is insufficient to guarantee
        nonempty subsets
    """
    if min_items is None:
        min_items = 1 if nonempty else 0
    else:
        if not isinstance(min_items, int):
            raise TypeError(
                "Min items count for each subset isn't an integer: "
                "{} ({})".format(min_items, type(min_items))
            )
        if nonempty and min_items < 1:
            raise ValueError(
                "When minimum item count is {}, nonempty subsets "
                "cannot be guaranteed.".format(min_items)
            )
    # Account for iterable burn possibility; besides, collection should be
    # relatively small if building the powerset.
    items = list(items)
    n = len(items)
    if n == 0 or n < min_items:
        return []
    max_items = len(items) + 1 if include_full_pop else len(items)
    return list(
        itertools.chain.from_iterable(
            itertools.combinations(items, k) for k in range(min_items, max_items)
        )
    )


def asciify_dict(data):
    """ https://gist.github.com/chris-hailstorm/4989643 """

    def _asciify_list(lst):
        ret = []
        for item in lst:
            if isinstance(item, unicode):
                item = item.encode("utf-8")
            elif isinstance(item, list):
                item = _asciify_list(item)
            elif isinstance(item, dict):
                item = asciify_dict(item)
            ret.append(item)
        return ret

    if sys.version_info[0] >= 3:
        warn("This operation is supported only in Python2", UserWarning)
        return data

    ret = {}
    for key, value in data.iteritems():
        if isinstance(key, unicode):
            key = key.encode("utf-8")
        if isinstance(value, unicode):
            value = value.encode("utf-8")
        elif isinstance(value, list):
            value = _asciify_list(value)
        elif isinstance(value, dict):
            value = asciify_dict(value)
        ret[key] = value
    return ret
