from itertools import islice
import collections, functools, time, string


def validate_media(type):
    if not type in ('manga', 'anime'):
        raise ValueError("type has to be 'anime' or 'manga'")


def valid_mal_username(user):
    legal_characters = string.digits + string.ascii_letters + '-_'
    valid_chars_test = all((c in legal_characters for c in user))
    length_test = len(user) > 1 and len(user) < 17
    return valid_chars_test and length_test


def search(context, root, predicate=lambda _: True):
    visited = {root,}
    queue = [root]
    while queue:
        node, *queue = queue
        if predicate(node):
            yield node
        for edge in context(node):
            if edge in visited:
                continue
            visited.add(edge)
            queue += [edge]


# itertools recipes
def take(n, gen):
    "Return first n items of the iterable as a list"
    return list(islice(gen, n))


def ignore(n, gen):
    "Advances the iterator n-steps ahead, and returns it."
    next(islice(gen, n, n), None)
    return gen


def tail(n, iterable):
    "Return an iterator over the last n items"
    # tail(3, 'ABCDEFG') --> E F G
    return iter(collections.deque(iterable, maxlen=n))


def nth(iterable, n, default=None):
    "Returns the nth item or a default value"
    return next(islice(iterable, n, None), default)


def Maybe(fn, *args):
    """
    Shoots trouble
    """
    try:
        return (fn(*args))
    except Exception:
        return


def coroutine(func):
    """
    A decorator function that takes care of starting a coroutine
    automatically on call.
    """
    def start(*args, **kwargs):
        cr = func(*args, **kwargs)
        next(cr)
        return cr.send
    return start


@coroutine
def timeouts_coroutine(cycle=5, threshold=3, sleep=2, quiet=True):
    """
    A hack to factor out some timeout logic
    """
    timeouts = 0
    updates = [True for _ in range(cycle)]
    while True:
        response = yield
        updates = list(tail(cycle, updates + [response]))

        if sum(updates) <= (cycle-threshold):
            timeouts += 1
            if not quiet:
                print(f"Timeout, going to sleep ({timeouts*sleep})")
            time.sleep(sleep * timeouts)
        else:
            timeouts = 0







