# -*- coding: utf-8 -*-

# enum.py
# Part of enum, a package providing enumerated types for Python.
#
# Copyright © 2007–2009 Ben Finney <ben+python@benfinney.id.au>
# This is free software; you may copy, modify and/or distribute this work
# under the terms of the GNU General Public License, version 2 or later
# or, at your option, the terms of the Python license.

""" Robust enumerated type support in Python.

This package provides a module for robust enumerations in Python.

An enumeration object is created with a sequence of string arguments
to the Enum() constructor::

    >>> from enum import Enum
    >>> Colors = Enum('red', 'blue', 'green')
    >>> Weekdays = Enum('mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun')

The return value is an immutable sequence object with a value for each
of the string arguments. Each value is also available as an attribute
named from the corresponding string argument::

    >>> pizza_night = Weekdays[4]
    >>> shirt_color = Colors.green

The values are constants that can be compared only with values from
the same enumeration; comparison with other values will invoke
Python's fallback comparisons::

    >>> pizza_night == Weekdays.fri
    True
    >>> shirt_color > Colors.red
    True
    >>> shirt_color == "green"
    False

Each value from an enumeration exports its sequence index
as an integer, and can be coerced to a simple string matching the
original arguments used to create the enumeration::

    >>> str(pizza_night)
    'fri'
    >>> shirt_color.index
    2
"""

__author_name__ = "Ben Finney"
__author_email__ = "ben+python@benfinney.id.au"
__author__ = "%(__author_name__)s <%(__author_email__)s>" % vars()

_copyright_year_begin = "2007"
__date__ = "2009-08-26"
_copyright_year_latest = __date__.split('-')[0]
_copyright_year_range = _copyright_year_begin
if _copyright_year_latest > _copyright_year_begin:
	_copyright_year_range += "–%(_copyright_year_latest)s" % vars()
__copyright__ = (
	"Copyright © %(_copyright_year_range)s"
	" %(__author_name__)s") % vars()
__license__ = "Choice of GPL or Python license"

__url__ = "http://pypi.python.org/pypi/enum/"
__version__ = "0.4.4"


class EnumException(Exception):
	""" Base class for all exceptions in this module. """

	def __init__(self, *args, **kwargs):
		if self.__class__ is EnumException:
			class_name = self.__class__.__name__
			raise NotImplementedError(
				"%(class_name)s is an abstract base class" % vars())
		super().__init__(*args, **kwargs)


class EnumEmptyError(AssertionError, EnumException):
	""" Raised when attempting to create an empty enumeration. """

	def __str__(self):
		return "Enumerations cannot be empty"


class EnumBadKeyError(TypeError, EnumException):
	""" Raised when creating an Enum with non-string keys. """

	def __init__(self, key):
		self.key = key

	def __str__(self):
		return "Enumeration keys must be strings: %(key)r" % vars(self)


class EnumImmutableError(TypeError, EnumException):
	""" Raised when attempting to modify an Enum. """

	def __init__(self, *args):
		self.args = args

	def __str__(self):
		return "Enumeration does not allow modification"


def _comparator(func):
	""" Decorator for EnumValue rich comparison methods. """
	def comparator_wrapper(self, other):
		try:
			assert self.enumtype == other.enumtype
			result = func(self.index, other.index)
		except (AssertionError, AttributeError):
			result = NotImplemented

		return result
	comparator_wrapper.__name__ = func.__name__
	comparator_wrapper.__doc__ = getattr(float, func.__name__).__doc__
	return comparator_wrapper


class EnumValue:
	""" A specific value of an enumerated type. """

	def __init__(self, enumtype, index, key):
		""" Set up a new instance. """
		self._enumtype = enumtype
		self._index = index
		self._key = key

	@property
	def enumtype(self):
		return self._enumtype

	@property
	def key(self):
		return self._key

	def __str__(self):
		return str(self.key)

	@property
	def index(self):
		return self._index

	def __repr__(self):
		return "EnumValue(%(_enumtype)r, %(_index)r, %(_key)r)" % vars(self)

	def __hash__(self):
		return hash(self._index)

	@_comparator
	def __eq__(self, other):
		return (self == other)

	@_comparator
	def __ne__(self, other):
		return (self != other)

	@_comparator
	def __lt__(self, other):
		return (self < other)

	@_comparator
	def __le__(self, other):
		return (self <= other)

	@_comparator
	def __gt__(self, other):
		return (self > other)

	@_comparator
	def __ge__(self, other):
		return (self >= other)


class Enum:
	""" Enumerated type. """

	def __init__(self, *keys, **kwargs):
		""" Create an enumeration instance. """

		value_type = kwargs.get('value_type', EnumValue)

		if not keys:
			raise EnumEmptyError()

		keys = tuple(keys)
		values = [None] * len(keys)

		for i, key in enumerate(keys):
			value = value_type(self, i, key)
			values[i] = value
			try:
				super().__setattr__(key, value)
			except TypeError:
				raise EnumBadKeyError(key)

		self.__dict__['_keys'] = keys
		self.__dict__['_values'] = values

	@classmethod
	def get_extended(cls, base_enum, *keys, **kwargs):
		keys = base_enum._keys + keys
		return cls(*keys, **kwargs)

	def __setattr__(self, name, value):
		raise EnumImmutableError(name)

	def __delattr__(self, name):
		raise EnumImmutableError(name)

	def __len__(self):
		return len(self._values)

	def __getitem__(self, index):
		return self._values[index]

	def __setitem__(self, index, value):
		raise EnumImmutableError(index)

	def __delitem__(self, index):
		raise EnumImmutableError(index)

	def __iter__(self):
		return iter(self._values)

	def __contains__(self, value):
		is_member = False
		if isinstance(value, str):
			is_member = (value in self._keys)
		else:
			is_member = (value in self._values)
		return is_member

	def get_item_for_string(self, key):
		"""Get an enum value for a string
		@throws KeyError on key not found"""
		try:
			index = self._keys.index(key)
		except ValueError:
			raise KeyError # keyerror is more natural here, since the value is a key
		return self[index]
