# Source code for taurus.core.util.enumeration

    #!/usr/bin/env python

    # ###########################################################################
    #
    # This file is part of Taurus
    #
    # http://taurus-scada.org
    #
    # Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
    #
    # Taurus is free software: you can redistribute it and/or modify
    # it under the terms of the GNU Lesser General Public License as published by
    # the Free Software Foundation, either version 3 of the License, or
    # (at your option) any later version.
    #
    # Taurus is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    # GNU Lesser General Public License for more details.
    #
    # You should have received a copy of the GNU Lesser General Public License
    # along with Taurus.  If not, see <http://www.gnu.org/licenses/>.
    #
    # ###########################################################################

    """Enumeration module.
    In C, enums allow you to declare a bunch of constants with unique values,
    without necessarily specifying the actual values (except in cases where you
    need to). Python has an accepted idiom that's fine for very small numbers of
    constants (A, B, C, D = range(4)) but it doesn't scale well to large numbers,
    and it doesn't allow you to specify values for some constants while leaving
    others unspecified. This approach does those things, while verifying that all
    values (specified and unspecified) are unique. Enum values then are attributes
    of an Enumeration class (Volkswagen.BEETLE, Volkswagen.PASSAT, etc.).
    """

    __all__ = ["EnumException", "Enumeration"]
    __docformat__ = "restructuredtext"

    class EnumException(Exception):
        """Exception thrown by :class:`Enumeration` when trying to declare an
        invalid enumeration.
        """

        pass

    class Enumeration(object):
        """Enumeration class intended to provide the 'enum' feature present in many
        programming languages.

        The elements of the enumeration can be accessed in an "object member way"
        or as elements of a dictionary.

        Usage::

            from taurus.core.util.enumeration import Enumeration

            Volkswagen = Enumeration("Volkswagen",
                ["JETTA",
                 "RABBIT",
                 "BEETLE",
                 ("THING", 400),
                 "PASSAT",
                 "GOLF",
                 ("CABRIO", 700),
                 "EURO_VAN",
                 "CLASSIC_BEETLE",
                 "CLASSIC_VAN"
                 ])

        In the command line::

            >>> my_car = Volkswagen.BEETLE
            >>> homer_car = Volkswagen.PASSAT

            >>> print(Volkswagen.BEETLE)
            2

            >>> print(Volkswagen['BEETLE'])
            2

            >>>print(Volkswagen.whatis(homer_car))
            'PASSAT'
        """

        def __init__(self, name, enumList, flaggable=False, no_doc=False):
            self._name = name
            lookup = {}
            reverseLookup = {}
            uniqueNames = set()
            self._flaggable = flaggable
            self._uniqueValues = uniqueValues = set()
            self._uniqueId = 0
            for x in enumList:
                if isinstance(x, tuple):
                    if flaggable:
                        raise EnumException(
                            "flagable enum does not accept tuple items"
                        )
                    x, i = x
                    if not isinstance(x, str):
                        raise EnumException("enum name is not a string: " + str(x))
                    if not isinstance(i, int):
                        raise EnumException(
                            "enum value is not an integer: " + str(i)
                        )
                    if x in uniqueNames:
                        raise EnumException("enum name is not unique: " + str(x))
                    if i in uniqueValues:
                        raise EnumException(
                            "enum value is not unique for " + str(x)
                        )
                    uniqueNames.add(x)
                    uniqueValues.add(i)
                    lookup[x] = i
                    reverseLookup[i] = x
            for x in enumList:
                if not isinstance(x, tuple):
                    if not isinstance(x, str):
                        raise EnumException("enum name is not a string: " + str(x))
                    if x in uniqueNames:
                        raise EnumException("enum name is not unique: " + str(x))
                    uniqueNames.add(x)
                    i = self._generateUniqueId()
                    uniqueValues.add(i)
                    lookup[x] = i
                    reverseLookup[i] = x
            self.lookup = lookup
            self.reverseLookup = reverseLookup
            if not no_doc:
                self.__doc_enum()

        def __call__(self, i):
            # TODO: Dummy implementation to simulate Python Enum behaviour.
            # It is not a complete replacement because although we can use
            # Enumeration as Callable, it still return an int instead of an
            # Enumeration member.
            return self.lookup[self.whatis(i)]

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

        def _generateUniqueId(self):
            if self._flaggable:
                n = 2**self._uniqueId
            else:
                while self._uniqueId in self._uniqueValues:
                    self._uniqueId += 1
                n = self._uniqueId
            self._uniqueId += 1
            return n

        def __contains__(self, i):
            if isinstance(i, int):
                return i in self.reverseLookup
            elif isinstance(i, str):
                return i in self.lookup

        def __getitem__(self, i):
            if isinstance(i, int):
                return self.whatis(i)
            elif isinstance(i, str):
                return self.lookup[i]

        def __getattr__(self, attr):
            if attr not in self:
                raise AttributeError
            return self.lookup[attr]

        def __doc_enum(self):
            rl = self.reverseLookup
            keys = list(rl)
            keys.sort()
            values = "\n".join(["    - {0} ({1})".format(rl[k], k) for k in keys])
            self.__doc__ = (
                self._name + " enumeration. " + "Possible values are:\n\n" + values
            )

        def __str__(self):
            rl = self.reverseLookup
            keys = list(rl)
            keys.sort()
            values = ", ".join([rl[k] for k in keys])
            return self._name + "(" + values + ")"

        def __repr__(self):
            rl = self.reverseLookup
            keys = list(rl)
            keys.sort()
            values = [rl[k] for k in keys]
            return "Enumeration('" + self._name + "', " + str(values) + ")"

        def has_key(self, key):
            """Determines if the enumeration contains the given key

            :param key: the key
            :type  key: str
            :return: True if the key is in the enumeration or False otherswise
            :rtype: bool
            """
            return key in self.lookup

        def keys(self):
            """Returns an iterable containning the valid enumeration keys

            :return: an interable containning the valid enumeration keys
            :rtype: iter<str>
            """
            return list(self.lookup.keys())

        def whatis(self, value):
            """Returns a string representation of the value in the enumeration.
            :param value: a valid enumeration element
            :return: a string representation of the given enumeration element
            :rtype: str
            """
            return self.reverseLookup[value]

        def get(self, i):
            """Returns the element for the given key/value"""
            return self[i]