import operator

from ..abc import SequenceExpression, Expression
from ..declerror import DeclarationError
from .value.valueexpr import VALUE
from .datastructs.itemexpr import ITEM, ITEM_optimized_EVENT_VALUE

from .value.eventexpr import EVENT
from .utility.context import CONTEXT


class ComparisonExpression(SequenceExpression):


	Category = "Compare"


	def __call__(self, context, event, *args, **kwargs):
		it = iter(self.Items)

		i = next(it)
		try:
			a = i(context, event, *args, **kwargs)
		except Exception as e:
			raise DeclarationError(original_exception=e, location=i.get_location())

		for i in it:

			try:
				b = i(context, event, *args, **kwargs)
			except Exception as e:
				raise DeclarationError(original_exception=e, location=i.get_location())

			if not self.Operator(a, b):
				return False
			a = b

		return True


	def get_outlet_type(self):
		return bool.__name__


class LT(ComparisonExpression):
	'''
	Operator '<'
	'''
	Operator = operator.lt


class LE(ComparisonExpression):
	'''
	Operator '<='
	'''
	Operator = operator.le


class EQ(ComparisonExpression):
	'''
	Operator '=='
	'''
	Operator = operator.eq

	Attributes = {
		"Items": [
			'si64', 'si8', 'si16', 'si32', 'si64', 'si128', 'si256',
			'ui8', 'ui16', 'ui32', 'ui64', 'ui128', 'ui256',
			'str'
		]
	}


	def optimize(self):
		if len(self.Items) == 2 and isinstance(self.Items[1], VALUE):

			if isinstance(self.Items[0], ITEM_optimized_EVENT_VALUE):
				return EQ_optimized_EVENT_VALUE(self)

			# The nested objects may not be optimized yet when this parent optimization is called
			if isinstance(self.Items[0], ITEM):
				if isinstance(self.Items[0].With, EVENT) and isinstance(self.Items[0].Item, VALUE):
					return EQ_optimized_EVENT_VALUE(self)
				elif isinstance(self.Items[0].With, CONTEXT) and isinstance(self.Items[0].Item, VALUE) and "." in self.Items[0].Item.Value:
					# Skip nested context from optimization
					return None
				else:
					return EQ_optimized_simple(self)

		return None


	def get_items_inlet_type(self):
		# Find the first usable type in the items
		for item in self.Items:
			outlet_type = item.get_outlet_type()
			if outlet_type not in frozenset(['^']):
				return outlet_type
		raise NotImplementedError("Cannot decide on items inlet type '{}'".format(self))


	def consult_inlet_type(self, key, child):
		return self.get_items_inlet_type()


class EQ_optimized_simple(EQ):

	def __init__(self, orig):
		super().__init__(orig.App, sequence=orig.Items)
		self.A = self.Items[0]
		assert isinstance(self.A, Expression)

		assert isinstance(self.Items[1], VALUE)
		self.B = self.Items[1].Value
		assert isinstance(self.B, (bool, str, int, float))


	def __call__(self, context, event, *args, **kwargs):
		return self.A(context, event, *args, **kwargs) == self.B


	def optimize(self):
		return None


class EQ_optimized_EVENT_VALUE(EQ):

	def __init__(self, orig):
		super().__init__(orig.App, sequence=orig.Items)
		self.Akey = self.Items[0].Item.Value
		self.Adefault = self.Items[0].Default.Value

		assert isinstance(self.Items[1], VALUE)
		self.B = self.Items[1].Value
		assert isinstance(self.B, (bool, str, int, float))


	def __call__(self, context, event, *args, **kwargs):
		return event.get(self.Akey, self.Adefault) == self.B


	def optimize(self):
		return None


class NE(ComparisonExpression):
	'''
	Operator '!='
	'''
	Operator = operator.ne


class GE(ComparisonExpression):
	"""
	Operator '>='
	"""
	Operator = operator.ge


class GT(ComparisonExpression):
	"""
	Operator '>'
	"""
	Operator = operator.gt


	def get_items_inlet_type(self):
		return evaluate_items_inlet_type(self.Items)


# TODO: This operator is obsoleted and should be removed (AT Jan 2021)
class IS(ComparisonExpression):
	"""
	Operator 'is'
	"""
	Operator = operator.is_


# TODO: This operator is obsoleted and should be removed (AT Jan 2021)
class ISNOT(ComparisonExpression):
	"""
	Operator 'is not'
	"""
	Operator = operator.is_not


	def get_items_inlet_type(self):
		# Find the first usable type in the items
		for item in self.Items:
			outlet_type = item.get_outlet_type()
			if outlet_type not in frozenset(['^']):
				return outlet_type
		raise NotImplementedError("Cannot decide on items inlet type '{}'".format(self))


	def consult_inlet_type(self, key, child):
		return self.get_items_inlet_type()



def evaluate_items_inlet_type(items):

	strings = False
	signed_integers = False
	unsigned_integers = False
	max_integer_size = 0

	for item in items:
		outlet_type = item.get_outlet_type()
		if outlet_type == ['^']:
			continue

		iti = integer_type_info.get(outlet_type)
		if iti is not None:
			if max_integer_size < iti:
				max_integer_size = iti
			if outlet_type[:1] == 'u':
				unsigned_integers = True
			else:
				signed_integers = True
			continue


		raise NotImplementedError("Unsupported type '{}' in comparison operator".format(outlet_type))


	if strings is False and unsigned_integers is False and signed_integers is True:
		# Just signed integers found
		return 'si{}'.format(max_integer_size)

	if strings is False and signed_integers is False and unsigned_integers is True:
		# Just unsigned integers found
		return 'ui{}'.format(max_integer_size)

	if strings is False and signed_integers is True and unsigned_integers is True:
		if max_integer_size == 1:
			# Booleans are handed differently (this is weird combo anyway)
			return 'ui1'
		# Mix of signed and unsigned integers found
		return 'si{}'.format(max_integer_size << 1)

	return "?"


integer_type_info = {
	'ui1': 1,
	'si8': 8,
	'si16': 16,
	'si32': 32,
	'si64': 64,
	'si128': 128,
	'si256': 256,
	'ui8': 8,
	'ui16': 16,
	'ui32': 32,
	'ui64': 64,
	'ui128': 128,
	'ui256': 256,
}
