import inspect
import yaml


def entity_pretty_yaml(cls):
	"""Set up YAML library to dump/load entity class as mapping."""
	if cls.yaml_prefix is not None:
		prefix_str = "%s." % cls.yaml_prefix
	else:
		prefix_str = ""

	tag = u'!%s%s' % (prefix_str, cls.__name__)

	def namedtuple_representer(dumper, data):
		return dumper.represent_mapping(tag, data.as_dictionary())

	def namedtuple_constructor(loader, node):
		return cls(**loader.construct_mapping(node))

	yaml.add_representer(cls, namedtuple_representer)
	yaml.add_constructor(tag, namedtuple_constructor)

	return cls


class EntityMetaclass(type):
	def __new__(mcs, class_name, class_parents, class_attr):
		c = super(EntityMetaclass, mcs).__new__(mcs, class_name, class_parents, class_attr)
		entity_pretty_yaml(c)
		return c


class Entity(object):
	yaml_prefix = None

	__metaclass__ = EntityMetaclass

	@property
	def properties_list(self):
		"""List names of properties of this entity

		By default all constructor arguments are considered as entity properties.

		:rtype: set[str]
		"""
		return set(inspect.getargspec(self.__init__).args) - {'self'}

	def as_dictionary(self):
		return {
			p: getattr(self, p)
			for p in self.properties_list
		}

	@staticmethod
	def _meta_attributes():
		return {'properties_list', 'as_dictionary', 'yaml_prefix'}

	def __str__(self):
		properties = ', '.join([
			"%s=%s" % (p, self._format_property_value(getattr(self, p)))
			for p in self.properties_list
		])
		return '%s(%s)' % (self.__class__.__name__, properties)

	def __repr__(self):
		return str(self)

	def _format_property_value(self, prop):
		if isinstance(prop, list):
			return '[%s]' % ', '.join([self._format_property_value(i) for i in prop])
		if isinstance(prop, Entity):
			return str(prop)
		else:
			return repr(prop)

	def __eq__(self, other):
		return type(self) == type(other) and all([
			getattr(self, prop) == getattr(other, prop) for prop in self.properties_list
		])