from __future__ import absolute_import
import random
import string
import itertools

from funcfinder.utils import *


def group_by_key_func(func):
    """
    Create a dictionary from an iterable such that the keys are the result of evaluating a key function on elements
    of the iterable and the values are lists of elements all of which correspond to the key.
    """
    assertEqual(func("a bb ccc d ee fff".split(), len),
                {1: ["a", "d"],
                 2: ["bb", "ee"],
                 3: ["ccc", "fff"]})

    assertEqual(func([-1, 0, 1, 3, 6, 8, 9, 2], lambda x: x % 2),
                {0: [0, 6, 8, 2],
                 1: [-1, 1, 3, 9]})


def copy_dict(func):
    """
    Returns a new separate dict equal to the original. Updates to the copy don't affect the original.
    """
    original = {'a': 1, 'd': 2, 'b': 3, 'c': 4}
    copy = func(original)

    # An equal but separate copy has been made
    assertEqual(original, copy)
    assertIsNot(original, copy)

    # Usual key access still works
    assertEqual(copy['d'], 2)

    # Deletion works
    del copy['d']
    assertIsNone(copy.get('d'))

    # But it doesn't delete the key in the original
    assertEqual(original['d'], 2)

    # Insertion works
    copy['x'] = 5
    assertEqual(copy['x'], 5)

    # And again, doesn't affect the original
    assertIsNone(original.get('x'))


def sort_dict_by_key(func):
    """
    Return a copy of a dict which still supports all the standard operations with the usual API,
    plus can be iterated over in sorted order by key. Adding keys to the dict may not necessarily preserve this order -
    for that, see always_sorted_dict_by_key.
    """
    copy_dict(func)

    # On my machine at least, this dict does not look sorted
    original_dict = {'a': 0, 's': 1, 'd': 2, 'f': 3}
    sorted_dict = func(original_dict)

    # Iteration is now ordered
    assertEqual(sorted_dict.items(), [('a', 0), ('d', 2), ('f', 3), ('s', 1)])

    # Larger test
    sorted_keys = list(itertools.product(string.ascii_lowercase, string.ascii_lowercase))
    shuffled_keys = list(sorted_keys)
    for i in xrange(10):
        random.shuffle(shuffled_keys)
        original_dict = dict(itertools.izip(shuffled_keys, itertools.count()))
        sorted_dict = func(original_dict)
        assertEqualIters(sorted_keys, sorted_dict.iterkeys())


def always_sorted_dict_by_key(func):
    """
    Return a copied dict sorted by key which preserves its order upon updates.
    """
    sort_dict_by_key(func)
    sorted_dict = func({})
    keys = list(itertools.product(string.ascii_lowercase, string.ascii_lowercase))
    random.shuffle(keys)
    for key in keys:
        sorted_dict[key] = key
        assertEqualIters(sorted(sorted_dict.keys()), sorted_dict.iterkeys())


def sort_dict_by_value(func):
    """
    Return a copy of a dict which still supports all the standard operations with the usual API,
    plus can be iterated over in sorted order by value. Adding keys to the dict may not necessarily preserve this order.
    """
    copy_dict(func)

    # On my machine at least, this dict does not look sorted by value
    original_dict = dict(zip(range(4), "dcba"))
    sorted_dict = func(original_dict)

    # Iteration is now ordered by value
    assertEqual(sorted_dict.items(), [(3, 'a'), (2, 'b'), (1, 'c'), (0, 'd')])

    # Larger test
    sorted_values = list(itertools.product(string.ascii_lowercase, string.ascii_lowercase))
    shuffled_values = list(sorted_values)
    for i in xrange(10):
        random.shuffle(shuffled_values)
        original_dict = dict(itertools.izip(itertools.count(), shuffled_values))
        sorted_dict = func(original_dict)
        assertEqualIters(sorted_values, sorted_dict.itervalues())