import logging
import functools
import itertools

from .declerror import DeclarationError

#

L = logging.getLogger(__name__)

#

_IdGenerator = itertools.count(1)


class Expression(object):

	Attributes = None  # Must be a list, if missing, then the Expression class is not complete

	Category = 'Others'

	def __init__(self, app):
		if self.Attributes is None:
			raise NotImplementedError("Missing 'Attributes' in {}".format(self))

		self.Id = '{}.E{}'.format(self.__class__.__name__, next(_IdGenerator))
		self.App = app
		self.Location = None
		self.Node = None  # The YAML node, assigned by a builder during YAML parsing

	def __call__(self, context, event, *args, **kwargs):
		raise RuntimeError("Call of the abstract method")

	def set_location(self, location):
		self.Location = location

	def get_location(self):
		return self.Location

	def initialize(self):
		"""
		Initialize the expression after the syntax tree was build.
		:return:
		"""
		return

	def optimize(self):
		return None

	def walk(self, parent=None, key=None):
		'''
		key in the Expression is the string, which is a name of the attribute with the child expression
		'''

		yield (parent, key, self)

		# Attributes may change during iteration, which could produce an error
		current_attributes = self.Attributes.copy()

		for key in current_attributes:
			v = getattr(self, key, None)
			if isinstance(v, Expression):
				for x in v.walk(self, key):
					yield x
			else:
				yield (self, key, v)

	def set(self, key, value):
		setattr(self, key, value)

	def get_outlet_type(self):
		return '???'

	def consult_inlet_type(self, key, child):
		raise NotImplementedError("Parent consultation at '{}'".format(self))


class SequenceExpression(Expression):
	'''
	Convenient class that fits onto SequenceNode from Yaml.

	Example of __call__() method implementation.

	def __call__(self, context, event, *args, **kwargs):
		return functools.reduce(
			lambda x, y: evaluate(x, context, event, *args, **kwargs) + evaluate(y, context, event, *args, **kwargs),
			self.Items
		)
	'''

	Attributes = {
		"Items": []
	}

	def __init__(self, app, *, sequence):
		super().__init__(app)
		self.Items = []

		for i in sequence:
			if isinstance(i, Expression):
				self.Items.append(i)

			else:
				assert isinstance(i, (int, str, float, bytes, bool, tuple, list, dict)) or i is None

				# So far used in the WHEN expression
				if isinstance(i, dict):
					continue

				from .expression import VALUE
				self.Items.append(
					VALUE(app, value=i)
				)


	def reduce(self, operator, context, event, *args, **kwargs):
		iterator = [evaluate(item, context, event, *args, **kwargs) for item in self.Items]
		return functools.reduce(operator, iterator)


	def walk(self, parent=None, key=None):
		'''
		key in the Sequence expression is the integer, which is a position in self.Items list
		'''
		yield(parent, key, self)

		for key, i in enumerate(self.Items):
			if isinstance(i, Expression):
				for x in i.walk(self, key):
					yield x
			else:
				raise NotImplementedError(":-(")


	def set(self, key, value):
		self.Items[key] = value


	def get_items_inlet_type(self):
		'''
		This method evaluate the inlet type for `Items` sequence
		'''
		return '?'


def evaluate(value, context, event, *args, **kwargs):
	'''
	This is OBSOLETED method and will be removed.
	DO NOT USE IT!
	'''
	try:
		return value(context, event, *args, **kwargs)
	except Exception as e:
		raise DeclarationError(original_exception=e, location=value.get_location())
