# Path: generators/multi_ops.py
# @noglobals
# def multi_ops(data_stream, *funcs):
#     """ fork a generator with multiple operations/functions
# 
#         data_stream  -  an iterable data structure (ie: list/generator/tuple)
#         funcs        -  every function that will be applied to the data_stream """
# 
#     assert all(callable(func) for func in funcs), 'multi_ops can only apply functions to the first argument'
#     assert len(funcs), 'multi_ops needs at least one function to apply to data_stream'
# 
#     for i in data_stream:
#         if len(funcs) > 1:
#             yield tuple(func(i) for func in funcs)
#         elif len(funcs) == 1:
#             yield funcs[0](i)
# 
# Path: generators/window.py
# @strict_globals(deque=deque, islice=islice)
# def window(iterable, size=2):
#     ''' yields wondows of a given size '''
#     iterable = iter(iterable)
#     d = deque(islice(iterable, size-1), maxlen=size)
#     for _ in map(d.append, iterable):
#         yield tuple(d)


from .multi_ops import multi_ops
from .window import window
from strict_functions import noglobals, strict_globals

"""
what does map do

- run first argument (the function) against the input iterables
- if multiple iterables are defined, multiple inputs are required for the function

what does our map do

- run multi-ops if multiple functions are given
- run window or chunks if multiple args in function

"""


@noglobals
def function_arg_count(fn):
    """ returns how many arguments a funciton has """
    assert callable(fn), 'function_arg_count needed a callable function, not {0}'.format(repr(fn))
    if hasattr(fn, '__code__') and hasattr(fn.__code__, 'co_argcount'):
        return fn.__code__.co_argcount
    else:
        return 1 # not universal, but for now, enough... :/



# Path: generators/multi_ops.py
# @noglobals
# def multi_ops(data_stream, *funcs):
#     """ fork a generator with multiple operations/functions
# 
#         data_stream  -  an iterable data structure (ie: list/generator/tuple)
#         funcs        -  every function that will be applied to the data_stream """
# 
#     assert all(callable(func) for func in funcs), 'multi_ops can only apply functions to the first argument'
#     assert len(funcs), 'multi_ops needs at least one function to apply to data_stream'
# 
#     for i in data_stream:
#         if len(funcs) > 1:
#             yield tuple(func(i) for func in funcs)
#         elif len(funcs) == 1:
#             yield funcs[0](i)
# 
# Path: generators/window.py
# @strict_globals(deque=deque, islice=islice)
# def window(iterable, size=2):
#     ''' yields wondows of a given size '''
#     iterable = iter(iterable)
#     d = deque(islice(iterable, size-1), maxlen=size)
#     for _ in map(d.append, iterable):
#         yield tuple(d)


# Path: generators/map.py
from .multi_ops import multi_ops
from .window import window
from strict_functions import noglobals, strict_globals


"""
what does map do

- run first argument (the function) against the input iterables
- if multiple iterables are defined, multiple inputs are required for the function

what does our map do

- run multi-ops if multiple functions are given
- run window or chunks if multiple args in function

"""


@noglobals
def function_arg_count(fn):
    """ returns how many arguments a funciton has """
    assert callable(fn), 'function_arg_count needed a callable function, not {0}'.format(repr(fn))
    if hasattr(fn, '__code__') and hasattr(fn.__code__, 'co_argcount'):
        return fn.__code__.co_argcount
    else:
        return 1 # not universal, but for now, enough... :/



@strict_globals(function_arg_count=function_arg_count, multi_ops=multi_ops, window=window)