#
# Copyright (c) 2003, 2004, 2005, 2006 Art Haas
#
# This file is part of PythonCAD.
#
# PythonCAD is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# PythonCAD 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with PythonCAD; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#
# This file contains simple classes meant to be used as base classes.
#

from PythonCAD.Generic import entity

class ModObject(object):
    """A base class for objects that store a modification state variable.

There are several methods for the modobject class:

isModified(): Test the state of the modified flag
modified(): Set the modified flag to True
reset(): Set the modified flag to False

This class is meant to be used as a base class for more complex
classes.
    """
    def __init__(self):
        """Initialize a modobject instance.

There are no arguments needed for this method.
        """
        self.__modified = False

    def isModified(self):
        """Tests the modified state flag of the modobject.

isModified()
        """
        return self.__modified

    def modified(self):
        """Set the modified state flag value to True.

modified()
        """
        self.__modified = True

    def reset(self):
        """Set the modified state flag value to False.

reset()
        """
        self.__modified = False

class SListObject(object):
    """Base class for objects emulating unidirectional linked lists.

A SListObject has the following attributes:

next: The next object in the list.

A SListObject has the following methods:

{get/set/del}Next(): Get/Set the next object in the list.
    """
    def __init__(self):
        """Initialize a SListObject object.
        """
        self.__next = None

    def getNext(self):
        """Get the object following this object.

getNext()
        """
        return self.__next

    def setNext(self, obj):
        """Set the object that will follow this object.

setNext(obj)

The object must be an instance of a ListObject.
        """
        if obj is not None:
            if not isinstance(obj, SListObject):
                raise TypeError, "Invalid SListObject: " + str(obj)
        self.__next = obj

    def delNext(self):
        """Delete the object that follows this object.

delNext()

This method returns the deleted object.
        """
        _next = self.__next
        if _next is not None:
            self.__next = _next.getNext()
            _next.setNext(None)
        return _next

    next = property(getNext, setNext, delNext, "Accessor to the next object.")

class DListObject(SListObject):
    """Base class for objects emulating doubly linked lists.

A DListObject is derived from a SListObject, so it shares the
attributes and methods of that class. Addtionally it has
the following attributes

prev: The previous object in the list

A DListObject has the following addtional methods:

{get/set/del}Prev(): Get/Set the previous object in the list.
    """
    def __init__(self):
        """Initialize a DListObject object.
        """
        SListObject.__init__(self)
        self.__prev = None

    def getPrev(self):
        """Get the object preceeding this object.

getPrev()
        """
        return self.__prev

    def setPrev(self, obj):
        """Set the object that will precede this object.

setPrev(obj)

The object must be an instance of a DListObject.
        """
        if obj is not None:
            if not isinstance(obj, DListObject):
                raise TypeError, "Invalid DListObject: " + str(obj)
            obj.setNext(self)
        self.__prev = obj

    def delPrev(self):
        """Delete the object preceding this object.

delPrev(obj)

This method returns the previous object.
        """
        _prev = self.__prev
        if _prev is not None:
            _new_prev = _prev.getPrev()
            self.__prev = _new_prev
            if _new_prev is not None:
                _new_prev.setNext(self)
            _prev.__prev = None
            _prev.setNext(None)
        return _prev

    prev = property(getPrev, setPrev, delPrev, "Accessor to preceding object.")

#
# base class for objects that are components of other
# objects
#

class Subpart(entity.Entity):
    """
        A base class for objects that store references to other objects.
        The Subpart class is meant to be a base class for other classes defining
        simple objects that will be used in other objects but are not subclasses
        of those other objects. The Subpart objects that are in those classes
        can be used to store references to the larger object. The Subpart
        class has the following methods:
        storeUser(): Save a reference to some object.
        freeUser(): Release a reference to some object
        getUsers(): Return the list of objects that have been stored.
        hasUsers(): Test if the subpart has any 
    """
    __messages = {
        'added_user' : True,
        'removed_user' : True,
        }
    
    def __init__(self, **kw):
        super(Subpart, self).__init__(**kw)
        self.__users = None

    def finish(self):
        if self.__users is not None:
            print "%d refs in users" % len(self.__users)
            for _user in self.__users:
                print "stray object reference to: " + `_user`
        super(Subpart, self).finish()
        
    def storeUser(self, obj):
        """
            Save a reference to another object.
            Argument 'obj' can be any type of object.
        """
        if self.__users is None:
            self.__users = []
        _users = self.__users
        _seen = False
        for _user in _users:
            if _user is obj:
                _seen = True
                break
        if not _seen:
            self.startChange('added_user')
            _users.append(obj)
            self.endChange('added_user')
            self.sendMessage('added_user', obj)

    def freeUser(self, obj):
        """Release a reference to another object.

freeObject(obj)

This method does nothing if the Component object has not
stored references to any object or if the argument 'obj'
had not been stored with storeObject().
        """
        if self.__users is not None:
            _users = self.__users
            for _i in range(len(_users)):
                if obj is _users[_i]:
                    self.startChange('removed_user')
                    del _users[_i]
                    self.endChange('removed_user')
                    self.sendMessage('removed_user', obj)
                    break
            if not len(_users):
                self.__users = None

    def getUsers(self):
        """
            Return the list of stored objects.
            This method returns a list of references stored by
            calling the storeObject() method.
        """
        if self.__users is not None:
            return self.__users[:]
        return []

    def countUsers(self):
        """
            Return the number of stored objects.
            countUsers()        
        """
        _count = 0
        if self.__users is not None:
            _count = len(self.__users)
        return _count

    def hasUsers(self):
        """Test if the Subpart has any users.

hasUsers()

This method returns True if there are any users of this Subpart,
otherwise this method returns False.
        """
        return self.__users is not None

    def canParent(self, obj):
        """
            Test if an Entity can be the parent of another Entity.
            This method overrides the Entity::canParent() method
        """
        return False

    def getValues(self):
        """Return values comprising the Subpart.

getValues()

This method extends the Entity::getValues() method.
        """
        return super(Subpart, self).getValues()
    
    def sendsMessage(self, m):
        if m in Subpart.__messages:
            return True
        return super(Subpart, self).sendsMessage(m)

#
# TypedDict Class
#
# The TypedDict class is built from the dict object. A TypedDict
# instance has a defined object type for a key and value and will
# only allow objects of that type to be used for these dictionary
# fields.
#

class TypedDict(dict):
    def __init__(self, keytype=None, valtype=None):
        super(TypedDict, self).__init__()
        self.__keytype = keytype
        self.__valtype = valtype
        
    def __setitem__(self, key, value):
        _kt = self.__keytype
        if _kt is not None:
            if not isinstance(key, _kt):
                raise TypeError, "Invalid key type %s" % type(key)
        _vt = self.__valtype
        if _vt is not None:
            if not isinstance(value, _vt):
                raise TypeError, "Invalid value type %s" % type(value)
        super(TypedDict, self).__setitem__(key, value)

#
# ConstDict class
#
# The ConstDict class is a TypedDict based class that allows
# the setting of a key only once and does not permit the
# deletion of the key. The idea with a ConstDict class is to
# store a non-modifiable set of key/value pairs.
#

class ConstDict(TypedDict):
    def __init__(self, keytype=None, valtype=None):
        super(ConstDict, self).__init__(keytype, valtype)

    def __setitem__(self, key, value):
        if key in self:
            raise KeyError, "Key already used: " + key
        super(ConstDict, self).__setitem__(key, value)

    def __delitem__(self, key):
        pass # raise an exception?

#
# LockedDict class
#
# The LockedDict class is a TypedDict based class that allows
# the setting of keys/values until the dictionary is locked.
# After then the dictionary is cannot be altered. There is
# not an unlock method to allow changing the LockedDict object
# once it has been locked.
#

class LockedDict(TypedDict):
    def __init__(self, keytype=None, valtype=None):
        super(LockedDict, self).__init__(keytype, valtype)
        self.__locked = False

    def __setitem__(self, key, value):
        if self.__locked:
            raise KeyError, "LockedDict object is locked: " + `self`
        super(LockedDict, self).__setitem__(key, value)

    def __delitem__(self, key):
        if self.__locked:
            raise KeyError, "LockedDict object is locked: " + `self`
        super(LockedDict, self).__delitem__(key)

    def lock(self):
        self.__locked = True
        
#
# TypedList Class
#
#
# The TypedList class is built from the list object. A TypedList
# instance has a defined object type for a occupant in the list and
# will only allow objects of that type to be stored.
#

class TypedList(list):
    def __init__(self, listtype=None):
        super(TypedList, self).__init__()
        self.__listtype = listtype

    def __setitem__(self, key, value):
        _lt = self.__listtype
        if _lt is not None:
            if not isinstance(value, _lt):
                raise TypeError, "Invalid list member %s" % type(value)
        super(TypedList, self).__setitem__(key, value)

    def append(self, obj):
        _lt = self.__listtype
        if _lt is not None:
            if not isinstance(obj, _lt):
                raise TypeError, "Invalid list member %s" % type(obj)
        super(TypedList, self).append(obj)

    def insert(self, idx, obj):
        _lt = self.__listtype
        if _lt is not None:
            if not isinstance(obj, _lt):
                raise TypeError, "Invalid list member %s" % type(obj)
        super(TypedList, self).insert(idx, obj)
