#
# Copyright (c) 2004 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
#
#
# _The_ base class for all objects used in PythonCAD
#

import message
import logger

class Entity(message.Messenger):
    """The base class for PythonCAD entities.

An entity has the following properties:

parent: The parent of an entity - another entity or None

An entity has the following methods:

isLocked(): Returns the locked state of an entity.
lock()/unlock(): Set/Unset a flag indicating the entity should not be altered.
isModified(): Returns the flag indicating the entity has been modified.
modified()/reset(): Set/Unset a flag indicating the entity modified state.
isVisible(): Returns the visibity of the entity.
hide(): Make the entity invisible.
show()/expose(): Make the entity visible.
{set/get}Parent(): Set/Get the parent entity of an entity.
{add/del}Child(): Add/Remove a child entity to/from another entity.
hasChild(): Test if one entity is a child of another.
hasChildren(): Test if an entity has any children entities.
getChildren(): Return a list of children entities for an entity.
    """
    messages = {
        'locked' : True,
        'unlocked' : True,
        'modified' : True,
        'reset' : True,
        'reparented' : True,
        'hidden' : True,
        'exposed' : True,
        'added_child' : True,
        'removed_child' : True,
        }

    idcount = 0

    def __init__(self, **kw):
        _parent = _id = None
        if 'parent' in kw:
            _parent = kw['parent']
            if _parent is not None and not isinstance(_parent, Entity):
                raise TypeError, "Invalid parent: " + `_parent`
        if 'id' in kw:
            _id = kw['id']
            if not isinstance(_id, int):
                raise TypeError, "Invalid ID: " + `_id`
            if _id < 0 or _id >= Entity.idcount:
                raise ValueError, "Invalid ID: %d" % _id
        if _id is None:
            _id = Entity.idcount
            Entity.idcount = Entity.idcount + 1
        message.Messenger.__init__(self)
        self.__locked = False
        self.__modified = False
        self.__visible = True
        self.__parent = None
        self.__children = None
        self.__log = None
        self.__undoing = False
        self.__id = _id
        if _parent is not None:
            Entity.setParent(self, _parent)

    def getValues(self):
        _pid = None
        if self.__parent is not None:
            _pid = self.__parent.getID()
        return self.__id, _pid

    values = property(getValues, None, None, "Entity values")

    def getID(self):
        return self.__id

    id = property(getID, None, None, "Entity ID")

    def isLocked(self):
        return self.__locked

    def lock(self):
        if not self.__locked:
            self.__locked = True
        self.sendMessage('locked')

    def unlock(self):
        if self.__locked:
            self.__locked = False
        self.sendMessage('unlocked')

    def isModified(self):
        return self.__modified

    def modified(self):
        if not self.__modified:
            self.__modified = True
        # if not self.__undoing:
            # self.sendMessage('modified') # always send this message
        self.sendMessage('modified')

    def reset(self):
        if self.__modified:
            self.__modified = False
        if not self.__undoing:
            self.sendMessage('reset') # always send this message

    def isVisible(self):
        return self.__visible

    def hide(self):
        if self.__visible:
            self.__visible = False
        self.sendMessage('hidden')

    def show(self):
        if not self.__visible:
            self.__visible = True
        self.sendMessage('exposed')

    def expose(self):
        Entity.show(self)

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

canParent(obj)

Subclasses should override this method if the ability to be
the parent of another entity needs refinement.
        """
        return True

    def setParent(self, parent):
        if parent is not None:
            if not isinstance(parent, Entity):
                raise TypeError, "Parent is not an Entity: " + `parent`
            if not parent.canParent(self):
                raise ValueError, "Invalid parent for Entity: " + `parent`
        _oldparent = self.__parent
        if _oldparent is not parent:
            if _oldparent is not None:
                Entity.delChild(_oldparent, self)
            self.__parent = parent
            if parent is not None:
                Entity.addChild(parent, self)
            self.sendMessage('reparented', _oldparent)
            self.modified()

    def getParent(self):
        return self.__parent

    parent = property(getParent, setParent, None, "Parent of an Entity.")

    def addChild(self, child):
        if not isinstance(child, Entity):
            raise TypeError, "Invalid child: " + `child`
        if child.getParent() is not self:
            raise ValueError, "Invalid parent: " + `child`
        if self.__children is None:
            self.__children = []
        for _child in self.__children:
            if _child is child:
                raise ValueError, "Child entity already stored: " + `child`
        self.__children.append(child)
        self.sendMessage('added_child', child)
        self.modified()

    def delChild(self, child):
        if child.getParent() is not self:
            raise ValueError, "Invalid parent for child: " + `child`
        if self.__children is None:
            raise ValueError, "Entity has no children: " + `self`
        _seen = False
        for _child in self.__children:
            if _child is _child:
                _seen = True
                break
        if not _seen:
            raise ValueError, "Child entity not stored: " + `child`
        self.__children.remove(child)
        if len(self.__children) == 0:
            self.__children = None
        self.sendMessage('removed_child', child)
        self.modified()

    def hasChild(self, child):
        return self.__children is not None and child in self.__children

    def hasChildren(self):
        return self.__children is not None

    def getChildren(self):
        if self.__children is not None:
            return self.__children[:]
        return []

    def sendsMessage(self, m):
        if m in Entity.messages:
            return True
        return message.Messenger.sendsMessage(self, m)

    def clone(self):
        return Entity(parent=self.__parent)

    def inRegion(self, xmin, ymin, xmax, ymax, all=False):
        return False

    def getLog(self):
        return self.__log

    def setLog(self, log):
        if log is not None:
            if not isinstance(log, logger.Logger):
                raise TypeError, "Invalid log object: " + `log`
            if self.__log is not None:
                raise ValueError, "Entity already contains a log."
        self.__log = log

    def delLog(self):
        if self.__log is not None:
            self.__log.clear()
        self.__log = None

    def undo(self):
        if self.__log is not None:
            self.__log.undo()

    def startUndo(self, mute=True):
        if mute:
            self.mute()
        self.__undoing = True

    def endUndo(self):
        if self.isMuted():
            self.unmute()
        self.__undoing = False

    def inUndo(self):
        return self.__undoing

    def redo(self):
        if self.__log is not None:
            self.__log.redo()
