"""util functions for collections_extended."""
from functools import wraps
import textwrap
import warnings

__all__ = ('hash_iterable', 'deprecated', 'deprecation_warning')


def hash_iterable(it):
	"""Perform a O(1) memory hash of an iterable of arbitrary length.

	hash(tuple(it)) creates a temporary tuple containing all values from it
	which could be a problem if it is large.

	See discussion at:
	https://groups.google.com/forum/#!msg/python-ideas/XcuC01a8SYs/e-doB9TbDwAJ
	"""
	hash_value = hash(type(it))
	for value in it:
		hash_value = hash((hash_value, value))
	return hash_value


def deprecation_warning(msg):
	"""Raise a deprecation warning."""
	warnings.warn(msg, category=DeprecationWarning, stacklevel=2)


def deprecated(msg, dep_version):
	"""Decorate a function, method or class to mark as deprecated.

	Raise DeprecationWarning and add a deprecation notice to the docstring.

	Args:
		msg: The message to document
		dep_version: The version in which this was deprecated
	See:
		https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-deprecated

	"""
	def wrapper(func):
		docstring = func.__doc__ or ''
		docstring_msg = '.. deprecated:: {version} {msg}'.format(
			version=dep_version,
			msg=msg,
			)
		if docstring:
			# We don't know how far to indent this message
			# so instead we just dedent everything.
			string_list = docstring.splitlines()
			first_line = string_list[0]
			remaining = textwrap.dedent(''.join(string_list[1:]))
			docstring = '\n'.join([
				first_line,
				remaining,
				'',
				docstring_msg,
				])
		else:
			docstring = docstring_msg
		func.__doc__ = docstring

		@wraps(func)
		def inner(*args, **kwargs):
			deprecation_warning(msg)
			return func(*args, **kwargs)

		return inner

	return wrapper
