from ...abc import Expression

from ..value.valueexpr import VALUE
from ..value.eventexpr import EVENT


class IN(Expression):
	"""
	Checks if expression is of given list.
	"""

	Attributes = {
		"What": ["*"],  # TODO: 'What' is different from list, set and dict
		"Where": ["list", "set", "dict"],
	}

	Category = "Compare"


	def __init__(self, app, *, arg_what, arg_where):
		super().__init__(app)

		if isinstance(arg_what, Expression):
			self.What = arg_what
		else:
			self.What = VALUE(app, value=arg_what)

		if isinstance(arg_where, Expression):
			self.Where = arg_where
		else:
			self.Where = VALUE(app, value=arg_where)


	def optimize(self):
		if isinstance(self.Where, VALUE):

			if self.Where.get_outlet_type() == 'list':
				return IN_optimized_list_where(self)

			if self.Where.get_outlet_type() == 'set':
				return IN_optimized_set_where(self)


		if isinstance(self.What, VALUE) and isinstance(self.Where, EVENT):
			return IN_optimized_EVENT_VALUE(self)

		return None


	def get_items_inlet_type(self):

		# Find the first usable type in the items
		for item in self.Where.Value:

			if isinstance(item, str):
				return 'str'

			elif isinstance(item, int):
				return 'int'

			elif isinstance(item, Expression):
				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 __call__(self, context, event, *args, **kwargs):
		return self.What(context, event, *args, **kwargs) in self.Where(context, event, *args, **kwargs)


	def get_outlet_type(self):
		return bool.__name__


class IN_optimized_list_where(IN):

	def __init__(self, orig):
		super().__init__(
			orig.App,
			arg_what=orig.What,
			arg_where=orig.Where
		)

		self._where_value = frozenset(self.Where({}, {}))

	def optimize(self):
		# This is to prevent re-optimising the class
		return None


	def __call__(self, context, event, *args, **kwargs):
		return self.What(context, event, *args, **kwargs) in self._where_value


class IN_optimized_set_where(IN):

	def __init__(self, orig):
		super().__init__(
			orig.App,
			arg_what=orig.What,
			arg_where=orig.Where
		)

		self._where_value = self.Where({}, {})


	def optimize(self):
		# This is to prevent re-optimising the class
		return None


	def __call__(self, context, event, *args, **kwargs):
		return self.What(context, event, *args, **kwargs) in self._where_value


class IN_optimized_EVENT_VALUE(IN):

	def __init__(self, orig):
		super().__init__(
			orig.App,
			arg_what=orig.What,
			arg_where=orig.Where
		)

		self._what_value = self.What({}, {})

	def optimize(self):
		# This is to prevent re-optimising the class
		return None

	def __call__(self, context, event, *args, **kwargs):
		return self._what_value in event
