# encoding: utf-8

import time
import os
import platform
import sys
import errno
import string
import random
import codecs
from StringIO import StringIO
from contextlib import closing
from contextlib import contextmanager
from functools import wraps
from collections import defaultdict, OrderedDict, namedtuple

def if_not_none(value, func):
	return func(value) if value is not None else None

class Obj:
	def __init__(self, kw):
		self.__dict__.update(kw)
	def __repr__(self):
		# TODO: add unicode support, enquote string values in representation
		return "obj(" + ", ".join(["%s=%s" % (k, v) for k, v in self.__dict__.iteritems()]) + ")"
	# TODO: review all uses of obj as a dictionary key, replace obj with some immutable structure and then uncomment the following function:
	# until then, two objs built with the same arguments do differ.
	#def __eq__(self, other):
	#	return self.__dict__ == other.__dict__

def obj(**kw):
	return Obj(kw)

def parse_bool(string):
	if string in ('true', '1'):
		return True
	elif string in ('false', '0'):
		return False
	else:
		raise Exception("Invalid boolean value string '%s', expected 'true' or 'false'" % (string,))

utf8_tree_symbols  = {'|': u'│ ', '|-': u'├ ', '`-': u'└ ', ' ': '  '}
ascii_tree_symbols = {'|': u'|  ', '|-': u'|- ', '`-': u'`- ', ' ': '   '}

def draw_tree(root, symbols, spacing=0):
	def draw(node, indent, child_indent, stream):
		name, children = node
	
		lines = name.split('\n')
		stream.write(u'%s%s\n' % (indent, lines[0]))
		
		for line in lines[1:]:
			stream.write(u'%s%s\n' % (child_indent, line))
	
		if children:
			spacing_line = u'%s\n' % ('\n'.join([child_indent+symbols['|']] * spacing))
			if spacing > 0: 
				stream.write(spacing_line)
			for child in children[:-1]:
				draw(child, child_indent+symbols['|-'], child_indent+symbols['|'], stream)
				if spacing > 0: 
					stream.write(spacing_line)
			draw(children[-1], child_indent+symbols['`-'], child_indent+symbols[' '], stream)
	
	with closing(StringIO()) as stream:
		draw(root, u'', u'', stream)
		return stream.getvalue()

def generate_random_password():
	chars = [random.choice(string.lowercase) for _ in range(3)] + \
			[random.choice(string.uppercase) for _ in range(3)] + \
			[random.choice(string.digits) for _ in range(4)]
	random.shuffle(chars)
	return "".join(chars)

def sort_list_by_another(lst, order_lst):
	"""
	Sort one list with the order taken from another list
	"""
	return [value for value in order_lst if value in lst]

def ilen(iterable):
	"""Get number of elements in iterable collection.
	"""
	count = 0
	for _ in iterable:
		count += 1
	return count

def partition(iterable, func):
	condition_true = []
	condition_false = []

	for item in iterable:
		if func(item):
			condition_true.append(item)
		else:
			condition_false.append(item)
	return condition_true, condition_false

def try_until(condition_func, intervals, sleep=time.sleep):
	def func():
		return True if condition_func() else None
	return poll_data(func, intervals, sleep) is not None

def poll_data(func, intervals, sleep=time.sleep):
	value = func()
	if value is not None:
		return value

	for interval in intervals:
		sleep(interval)
		value = func()
		if value is not None:
			return value

	return None

def polling_intervals(starting, max_attempt_interval, max_total_time=None):
	"""Generate sequence of times for polling some potentially long-lasting process.
	"""
	interval = starting
	total = 0

	while interval < max_attempt_interval and (max_total_time is None or total < max_total_time):
		yield interval
		total += interval
		interval *= 2

	while max_total_time is None or total < max_total_time:
		yield max_attempt_interval
		total += max_attempt_interval

def unused(*args):
	"""This function does nothing and is used to prevent code analyzing tools from reporting
	variable or imported module as unused.

	Please describe why this unused variable is needed next to function call.
	"""
	args = args

def all_equal(lst):
	return len(lst) == 0 or all(item == lst[0] for item in lst[1:])

def cached(func):
	"""Cache function results.
	   (cached) call the function func as usual: func(args)
	   clear cache for function func as: func.clear()
	"""
	cache = {}

	@wraps(func)
	def wrapper(*args, **kw):
		key = (args, frozenset(kw.items()))
		if key in cache:
			return cache[key]
		else:
			value = func(*args, **kw)
			cache[key] = value
			return value

	wrapper.clear = lambda: cache.clear()
	return wrapper

def format_list(lst):
	return ", ".join(["'%s'" % (elem,) for elem in lst])

def format_multiline_list(lst):
	return "\n".join(["- %s" % (elem,) for elem in lst])

def group_by(seq, key_func):
	d = defaultdict(list)
	for item in seq:
		d[key_func(item)].append(item)
	return dict(d)

def group_by_id(seq, key_func):
	d = {}
	for item in seq:
		key = key_func(item)
		assert key not in d # assert that key is really "id" - so it is unique
		d[key] = item
	return d

def group_value_by_id(seq, key_func, value_func):
	d = {}
	for item in seq:
		key = key_func(item)
		assert key not in d # assert that key is really "id" - so it is unique
		d[key] = value_func(item)
	return d

def mkdir_p(path):
	try:
		os.makedirs(path)
	except OSError as exc:
		if exc.errno == errno.EEXIST:
			pass
		else: 
			raise

def between_inclusive(value, bottom, top):
	return value >= bottom and value <= top

def between_exclusive(value, bottom, top):
	return value > bottom and value < top

def find_only(lst, match_func, error_message):
	matching_items = [item for item in lst if match_func(item)]
	if len(matching_items) != 1:
		raise Exception(error_message)
	return matching_items[0]

def find_first(lst, match_func, default_value=None):
	for item in lst:
		if match_func(item):
			return item
	return default_value

def exists(lst, match_func):
	for item in lst:
		if match_func(item):
			return True

	return False

def replace_str_prefix(string, old_prefix, new_prefix):
	if string.startswith(old_prefix):
		return new_prefix + string[len(old_prefix):]
	else:
		raise Exception("String '%s': expected that is starts with '%s'" % (string, old_prefix))

def split_list_by_count(lst, count):
	sublst = []
	for item in lst:
		sublst.append(item)
		if len(sublst) >= count:
			yield sublst
			sublst = []

	if len(sublst) > 0: # the last part
		yield sublst

def safe_string_repr(string):
	"""If string can be interpreted as UTF-8, then return it as unicode string. Otherwise return its __repr__.
	Could be useful for safe logging, when usually you want to get just plain string, but if there are some issues
	with encoding - at least get its Python representation.
	"""
	try:
		return u"%s" % (string,)
	except UnicodeError:
		return u"%r" % (string,)

def strip_multiline_spaces(string):
	"""Utility function that strips leading tabs and spaces from multi-line strings.
	Useful when you want to indent multi-line string in a code, but don't want to split it over multiple strings for correct indentation
	"""
	return u"\n".join([line.strip() for line in string.strip(" \n\t").split("\n")])

def partition_list(the_list, chunk_size=20):	# borrowed from http://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks-in-python
	for idx in xrange(0, len(the_list), chunk_size):
		yield the_list[idx : idx + chunk_size]

def read_file(filename, reader_func, file_may_be_absent=False, default=None):
	try:
		with file(filename, 'r') as dump_file:
			return reader_func(dump_file)
	except IOError as e:
		if e.errno == errno.ENOENT and file_may_be_absent:
			return default
		else:
			raise

def list_get(lst, index, default=None):
	try:
		return lst[index]
	except IndexError:
		return default

def fill_none_namedtuple(tuple_class, **kwargs):
	for field in tuple_class._fields:
		if field not in kwargs:
			kwargs[field] = None
	return tuple_class(**kwargs)

def unique_list(lst):
	result = []
	for item in lst:
		if item not in result:
			result.append(item)
	return result

def sorted_dict(dictionary):
	return OrderedDict(sorted(dictionary.items(), key=lambda i: i[0]))

def mutable_namedtuple(name, fields):
	def __init__(self, *args, **kwargs):
		if len(args) > 0:
			for field, arg in zip(fields, args):
				self.__dict__.update({field: arg})
		assert set(list(fields[0 : len(args)]) + kwargs.keys()) == set(fields), "invalid arguments passed to constructor"
		self.__dict__.update(kwargs)
		
	def __repr__(self):
		return name + '(' + ', '.join("%s=%r" % (n, getattr(self, n)) for n in fields) + ')'

	def __eq__(self, other):
		for field in fields:
			if not hasattr(other, field):
				return False
			if getattr(self, field) != getattr(other,  field):
				return False
		return True

	def _asdict(self):
		return { field: getattr(self, field) for field in fields }
			
	return type(name, (object,), {'__init__': __init__, '__repr__': __repr__, '__eq__': __eq__, '_asdict': _asdict})

def is_ascii_string(string):
	try:
		string.decode('ascii')
	except UnicodeError:
		return False

	return True

def extend_namedtuple(base, typename, field_names=[]):
	return namedtuple(typename, list(base._fields) + list(field_names))

def get_executable_file_name():
	return os.path.basename(sys.argv[0])

def is_empty(value):
	return value is None or value == ''

@contextmanager
def open_unicode_file(filename):
    """Open file detecting if it is UTF-8 or UTF-8 with BOM

    See for more details:
    http://stackoverflow.com/questions/13590749/reading-unicode-file-data-with-bom-chars-in-python
    """
    minbytes = min(32, os.path.getsize(filename))
    with open(filename, 'rb') as fp_raw:
        raw = fp_raw.read(minbytes)
        if raw.startswith(codecs.BOM_UTF8):
            encoding = 'utf-8-sig'
        else:
            encoding = 'utf-8'

    with codecs.open(filename, 'r', encoding) as fp:
        yield fp

def unique_merge(key, lists):
	"""Merge several lists making unique by key
	
	Arguments:
	- key - function which accepts item and returns hashable key
	- lists - list of lists with items
	"""
	keys = set()
	result = []
	for lst in lists:
		for item in lst:
			if key(item) not in keys:
				keys.add(key(item))
				result.append(item)

	return result

def merge_dicts(*dicts):
	"""Merge several dictionaries, the latest has more priority

	Arguments:
	pass dictionaries as function's arguments, like: merge_dicts(d1, d2, d3)
	"""
	result = {}
	for d in dicts:
		for k, v in d.iteritems():
			result[k] = v

	return result

def is_string(obj):
	"""Check if object is string or not"""
	return isinstance(obj, basestring)

def is_run_on_windows():
	return platform.system() == 'Windows'