import itertools, operator, collections.abc
from . import functional as ufunctional


if __debug__:
	import builtins

	def map(*args):
		return tuple(builtins.map(*args))

	def zip(*args):
		return tuple(builtins.zip(*args))

	def filter(function, iterable):
		return tuple(builtins.filter(function, iterable))

	def filterfalse(function, iterable):
		return tuple(itertools.filterfalse(function, iterable))

else:
	from builtins import map, filter, zip
	from itertools import filterfalse



__iterator_cookie = object()


def each(function, *iterables):
	if not iterables:
		return
	elif len(iterables) == 1:
		for args in iterables[0]:
			function(args)
	else:
		stareach(function, zip(*iterables))


def stareach(function, iterable):
	for args in iterable:
		function(*args)


def consume(iterable):
	iterator = iter(iterable)
	while next(iterator, __iterator_cookie) is not __iterator_cookie:
		pass


def __slice_to_tuple(slice):
	return (slice.start, slice.stop, slice.step)


def islice(iterable, *args):
	if not args:
		args = (None,)
	elif len(args) == 1 and isinstance(args[0], slice):
		args = __slice_to_tuple(args[0])
	return itertools.islice(iterable, *args)


def map_inplace(function, list, depth=0, slice=None):
	if depth <= 0:
		if slice is None:
			list[:] = map(function, list)
		else:
			list[slice] = map(function,
				itertools.islice(list, slice.start, slice.stop, slice.step))
	else:
		for item in list:
			map_inplace(function, item, depth - 1, slice)
	return list


def countif(function, *iterables):
	return sum(map(bool, itertools.starmap(function, *iterables)))


def teemap(iterable, *functions):
	return map(lambda item: [item if f is None else f(item) for f in functions], iterable)


def issorted(iterable, key=None, reverse=False):
	if key is not None:
		iterable = map(key, iterable)
	it = iter(iterable)
	last_item_key = next(it, __iterator_cookie)
	if last_item_key is not __iterator_cookie:
		not_in_order = operator.lt if reverse else operator.gt
		for item_key in it:
			if not_in_order(last_item_key, item_key):
				return False
			last_item_key = item_key
	return True


def order(sequence, key=None, reverse=False):
	key2 = sequence.__getitem__
	if key is not None:
		key2 = ufunctional.composefn(key2, key)
	return sorted(range(len(sequence)), key=key2, reverse=reverse)


def sorted_with_order(iterable, key=None, reverse=False, inplace=False):
	if not inplace:
		iterable = list(iterable)
	else:
		assert callable(getattr(iterable, '__setitem__', None))
	_order = order(iterable, key, reverse)
	iterable[:] = sort_by_order(iterable, _order)
	return _order, iterable


def sort_by_order(sequence, order):
	if __debug__:
		if not isinstance(order, collections.abc.Sized):
			order = tuple(order)
		assert \
			all(map((0).__le__, order)) and all(map(len(sequence).__gt__, order))

	return map(sequence.__getitem__, order)
