# Copyright 2017 <NAME>. This software is licensed under the MIT
# license. See the LICENSE file for more information.
"""A utility for describing enums in a DRY and Django style."""

import re
import stringcase


__metaclass__ = type

__all__ = [
    "Enum",
]


VALID_PYTHON_NAME_REGEX = re.compile("^[A-Z_][A-Z0-9_]*$")


class Enum:
    """Describes an enumerated set of items in various forms."""

    def __init__(self, *args):
        """Create a new Enum from passed arguments.

        Each argument can be either a 3-tuple of (id, python_name,
        display_name) or a two-tuple of (python_name, display_name). In
        the latter case, IDs are assigned as the Enum is created.

        Two-tuple and three-tuple forms cannot be mixed; trying to do so
        will raise a ValueError.
        """
        self._last_id = None
        self._id_mappings = {}
        self._display_name_mappings = {}

        last_arg_length = None
        for item in args:
            if last_arg_length is None:
                last_arg_length = len(item)
            elif len(item) != last_arg_length:
                raise ValueError(
                    "You can't mix 2- and 3-tuples when creating an Enum.")

            if len(item) == 2:
                id = next(self.ids)
                python_name, display_name = item
            else:
                id, python_name, display_name = item

            if not VALID_PYTHON_NAME_REGEX.match(python_name):
                raise ValueError(
                    "python_name `{0}` is invalid.".format(python_name))

            self._id_mappings[python_name] = id
            self._display_name_mappings[id] = display_name

    @property
    def ids(self):
        """A generator that yields linearly-increasing integers."""
        if self._last_id is None:
            self._last_id = 0
        else:
            self._last_id += 1
        yield self._last_id

    def __getattr__(self, name):
        """For a given `name` return correct ID value, assuming that
        name is registered.

        If `name` is not registered, raise an AttributeError.
        """
        if name not in self._id_mappings:
            raise AttributeError(
                "Enum does not have a key '{0}'.".format(name))
        return self._id_mappings[name]

    def as_choices(self):
        """Return this Enum's items as tuple of Django choices."""
        keys = sorted(self._display_name_mappings.keys())
        return tuple(
            (id, self._display_name_mappings[id]) for id in keys)

    def get_display_name(self, id):
        """Given an that's recorded in this Enum instance, return the
        display name for that value.
        """
        if id not in self._display_name_mappings:
            raise ValueError(
                "Enum does not have an ID '{0}'.".format(id))
        return self._display_name_mappings[id]


def enum_to_choices(
        enum_,
        name_as_value=False,
        stringifier=stringcase.sentencecase):
    """Given an Enum, return a tuple of Django choice two-tuples.

    :param enum_: The enum.Enum to convert.
    :param name_as_value: Boolean indicating whether the `name` of an
        enum item should be returned as its value, and the value ignored.
        This is useful where you want to store string values instead of
        integers for enums.
    :param stringifier: The function to use to convert enum names into
        strings for display. Defaults to stringcase.sentencecase().
    :return: A tuple of two-tuples in the form (`value`, `stringified_name`).
    """
    if name_as_value:
        return tuple((item.name, stringifier(item.name)) for item in enum_)
    else:
        return tuple((item.value, stringifier(item.name)) for item in enum_)

# Copyright 2017 <NAME>. This software is licensed under the MIT
# license. See the LICENSE file for more information.
"""A utility for describing enums in a DRY and Django style."""

import re
import stringcase


__metaclass__ = type

__all__ = [
    "Enum",
]


VALID_PYTHON_NAME_REGEX = re.compile("^[A-Z_][A-Z0-9_]*$")


class Enum:
    """Describes an enumerated set of items in various forms."""

    def __init__(self, *args):
        """Create a new Enum from passed arguments.

        Each argument can be either a 3-tuple of (id, python_name,
        display_name) or a two-tuple of (python_name, display_name). In
        the latter case, IDs are assigned as the Enum is created.

        Two-tuple and three-tuple forms cannot be mixed; trying to do so
        will raise a ValueError.
        """
        self._last_id = None
        self._id_mappings = {}
        self._display_name_mappings = {}

        last_arg_length = None
        for item in args:
            if last_arg_length is None:
                last_arg_length = len(item)
            elif len(item) != last_arg_length:
                raise ValueError(
                    "You can't mix 2- and 3-tuples when creating an Enum.")

            if len(item) == 2:
                id = next(self.ids)
                python_name, display_name = item
            else:
                id, python_name, display_name = item

            if not VALID_PYTHON_NAME_REGEX.match(python_name):
                raise ValueError(
                    "python_name `{0}` is invalid.".format(python_name))

            self._id_mappings[python_name] = id
            self._display_name_mappings[id] = display_name

    @property
    def ids(self):
        """A generator that yields linearly-increasing integers."""
        if self._last_id is None:
            self._last_id = 0
        else:
            self._last_id += 1
        yield self._last_id

    def __getattr__(self, name):
        """For a given `name` return correct ID value, assuming that
        name is registered.

        If `name` is not registered, raise an AttributeError.
        """
        if name not in self._id_mappings:
            raise AttributeError(
                "Enum does not have a key '{0}'.".format(name))
        return self._id_mappings[name]

    def as_choices(self):
        """Return this Enum's items as tuple of Django choices."""
        keys = sorted(self._display_name_mappings.keys())
        return tuple(
            (id, self._display_name_mappings[id]) for id in keys)

    def get_display_name(self, id):
        """Given an that's recorded in this Enum instance, return the
        display name for that value.
        """
        if id not in self._display_name_mappings:
            raise ValueError(
                "Enum does not have an ID '{0}'.".format(id))
        return self._display_name_mappings[id]


def enum_to_choices(
        enum_,
        name_as_value=False,
        stringifier=stringcase.sentencecase):
    """Given an Enum, return a tuple of Django choice two-tuples.

    :param enum_: The enum.Enum to convert.
    :param name_as_value: Boolean indicating whether the `name` of an
        enum item should be returned as its value, and the value ignored.
        This is useful where you want to store string values instead of
        integers for enums.
    :param stringifier: The function to use to convert enum names into
        strings for display. Defaults to stringcase.sentencecase().
    :return: A tuple of two-tuples in the form (`value`, `stringified_name`).
    """
    if name_as_value:
        return tuple((item.name, stringifier(item.name)) for item in enum_)
    else:
        return tuple((item.value, stringifier(item.name)) for item in enum_)

# Copyright 2017 <NAME>. This software is licensed under the MIT # license. See the LICENSE file for more information. A utility for describing enums in a DRY and Django style. Describes an enumerated set of items in various forms. Create a new Enum from passed arguments.          Each argument can be either a 3-tuple of (id, python_name,         display_name) or a two-tuple of (python_name, display_name). In         the latter case, IDs are assigned as the Enum is created.          Two-tuple and three-tuple forms cannot be mixed; trying to do so         will raise a ValueError. A generator that yields linearly-increasing integers. For a given `name` return correct ID value, assuming that         name is registered.          If `name` is not registered, raise an AttributeError. Return this Enum's items as tuple of Django choices. Given an that's recorded in this Enum instance, return the         display name for that value. Given an Enum, return a tuple of Django choice two-tuples.      :param enum_: The enum.Enum to convert.     :param name_as_value: Boolean indicating whether the `name` of an         enum item should be returned as its value, and the value ignored.         This is useful where you want to store string values instead of         integers for enums.     :param stringifier: The function to use to convert enum names into         strings for display. Defaults to stringcase.sentencecase().     :return: A tuple of two-tuples in the form (`value`, `stringified_name`).