"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/dulcinea/lib/typed_list.py $
$Id: typed_list.py 23443 2004-02-06 15:23:48Z dbinger $

Provides TypedList.
"""

from dulcinea.typeutils import typecheck
from dulcinea.base import DulcineaPersistent

class TypedList (DulcineaPersistent):
    """A list of named objects with find/add/remove operations.

    Instance attributes:
      list : [any]
        the actual list of objects
    """

    required_class = None
    nameattr = "name"

    # -- Initialize/overload methods -----------------------------------

    def __init__(self, values=None, check_types=1):
        # Note that list is not persistent.
        if values:
            if check_types:
                self.set_list(values)
            else:
                self.list = list(values)
        else:
            self.list = []

    def copy(self):
        return self.__class__(self.list, check_types=0)

    def _check_value(self, value):
        """Check that 'value' as an instance of the required class"""
        if self.required_class is not None:
            typecheck(value, self.required_class)

    def _check_name(self, name):
        if self.find(name) is not None:
            raise ValueError, \
                  "list already has a value named '%s'" % name

    def _get_name(self, value):
        if hasattr(value, self.nameattr):
            return getattr(value, self.nameattr)
        return getattr(value, 'get_%s' % self.nameattr)()

    def __str__(self):
        np = len(self.list)
        if np == 0:
            return "no values"
        elif np == 1:
            return "1 value"
        else:
            return "%d values" % np

    def __getitem__(self, i):
        return self.list[i]

    def __setitem__(self, i, value):
        self._check_value(value)
        old_name = self._get_name(self.list[i])
        new_name = self._get_name(value)
        if old_name != new_name:
            self._check_name(new_name)
        self.list[i] = value
        self._p_changed = 1

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

    def __add__(self, other):
        newlist = self.__class__()
        if isinstance(other, TypedList):
            newlist.list = self.list + other.list
        else:
            newlist.list = self.list + other
        return newlist

    # -- Accessor methods ----------------------------------------------

    def get_list(self):
        """() -> [any]
        Return the actual list of values buried in the list.  This is not a
        copy, so be careful what you do with it.  If you do update the list,
        make sure that you only add instances of the proper type to it.
        """
        return self.list

    def get_names(self):
        """() -> [string]

        Return the list of all names in the list.
        """
        return [ self._get_name(v) for v in self.list ]


    # -- Modifier methods ----------------------------------------------

    def set_list(self, values):
        """(values : [any])

        Set the actual list of values.
        Raises TypeError if any element of 'values' is invalid.
        Raises ValueError on name collisions.
        """
        self.list = []
        for value in values:
            self.add(value)


    # -- Search/update methods -----------------------------------------

    def find(self, name):
        """(name : string) -> any

        Searches the list for a value with name 'name'.  Returns the
        matching value, or None if it couldn't find one.
        """
        typecheck(name, str)
        for object in self.list:
            if name == self._get_name(object):
                return object
        return None

    def add(self, value):
        """(value : any)

        Add the value 'value' (which must be of type required_type).  Raise
        TypeError if 'value' is not of the required class; raise ValueError
        if this list already has a value with the same name.
        """
        self._check_value(value)
        self._check_name(self._get_name(value))
        self.list.append(value)
        self._p_changed = 1

    def merge(self, other, resolve='error'):
        """Add all the values from another list ('other') to the current
        list.  Any name conflicts between the two lists are resolved
        according to 'resolve': if it is "error", then any name conflict
        triggers a ValueError exception; if it is "replace", then the
        conflicting value in the current exception list is replaced with its
        same-named counterpart from 'other'; if it is 'ignore', the
        conflicting value from 'other' is ignored.

        Raise TypeError if 'other' is not a TypedList; raise ValueError if
        there are name conflicts between the two lists and 'resolve' is
        "error"."""

        assert (resolve in ['error', 'replace', 'ignore'])

        typecheck(other, TypedList)

        # Build a dictionary mapping name -> list index for the
        # current list -- makes it easy to detect conflicts
        self_values = {}
        for i in range(len(self.list)):
            self_values[self._get_name(self.list[i])] = i

        # Now walk over the 'other' list -- the prospective list of
        # additions to 'self'.  For non-conflicting values, it's a
        # doddle, just add 'em to the end of self's list.  Values
        # with name conflicts must be dealt with a bit more
        # intelligently, depending on the value of
        # 'resolve'.

        for o_value in other:
            o_name = self._get_name(o_value)
            conflict = self_values.get(o_name)
            if conflict is not None:
                if resolve == 'error':
                    raise ValueError, \
                          "name conflict between lists: '%s'" % o_name
                # replace same-named values in current list with
                # other's version
                elif resolve == 'replace':
                    self.list[conflict] = o_value

                # ignore other's version, keep current list's version
                elif resolve == 'ignore':
                    pass

            # No conflict -- no problem
            else:
                self.list.append (o_value)


    def remove(self, name):
        """(name : string)

        Find the value named by 'name', remove and return it.  If no such
        value was found, does nothing and returns None.
        """
        for i in range (len (self.list)):
            if self._get_name(self.list[i]) == name:
                goner = self.list[i]
                del self.list[i]
                self._p_changed = 1
                return goner
        else:
            return None

    def find_index(self, name):
        """(name : string)

        Find the value named by 'name', and return its index.
        If no such value was found, returns None.
        """
        for i in range(len(self.list)):
            if self._get_name(self.list[i]) == name:
                return i
        else:
            return None
