from vice.data import expr
from twisted.python import log
import exceptions, sys, os, re, StringIO
from Ft import Lib
from Ft.Xml import XPath, Domlette, EMPTY_NAMESPACE
from vice import VICE_XRC_PATH, VICE_BITMAP_PATH, VICE_PALETTE_PATH

XSDNS = 'http://www.w3.org/2001/XMLSchema'
NSS = {'xsd': XSDNS}
        

ending_numeric = re.compile("^([^0-9]+)([0-9]+)$")

class Id:
    def __init__(self, _id=None):
        if _id:
            self._id = _id
        else:
            self._id = Lib.Uuid.UuidAsString(Lib.Uuid.GenerateUuid())

    def GetId(self):
        return self._id

class CallbackMessageType:
    pass

class NETWORK_CREATE_BEGIN(CallbackMessageType): pass
class NETWORK_CREATE_END(CallbackMessageType): pass
class NETWORK_SET_SIZE(CallbackMessageType): pass
class NETWORK_ADD_NODE(CallbackMessageType): pass
class NETWORK_DELETE_NODE(CallbackMessageType): pass
class NETWORK_ADD_RELATION(CallbackMessageType): pass
class NETWORK_DELETE_RELATION(CallbackMessageType): pass
class PORT_CREATE_BEGIN(CallbackMessageType): pass
class PORT_CREATE_END(CallbackMessageType): pass
class NODE_CREATE_BEGIN(CallbackMessageType): pass
class NODE_CREATE_END(CallbackMessageType): pass
class NODE_ADD_IN_PORT(CallbackMessageType): pass
class NODE_ADD_OUT_PORT(CallbackMessageType): pass
class NODE_SET_PARAMETER_VALUE(CallbackMessageType): pass
class NODE_SET_PARAMETER_DOM(CallbackMessageType): pass
class NODE_SET_POSITION(CallbackMessageType): pass
class NODE_ADD_SOURCE_LINK(CallbackMessageType): pass
class NODE_ADD_DEST_LINK(CallbackMessageType): pass
class LINK_CREATE_BEGIN(CallbackMessageType): pass
class LINK_CREATE_END(CallbackMessageType): pass
class RELATION_CREATE_BEGIN(CallbackMessageType): pass
class RELATION_CREATE_END(CallbackMessageType): pass

class CallbackMessageException(exceptions.Exception): pass
class NODE_CREATE_CALLBACK_FAILED(CallbackMessageException): pass

class MonitorCallback:
    def __init__(self):
        pass

    def callback(self, *args, **kwargs):
        log.debug("Callback: message=%s, object classes=%s" % (args[0], map(lambda x: x.__class__, args[1:])))

m = MonitorCallback()

#CB_DEFAULT=m.callback
CB_DEFAULT=None

## TODO: someday, serialization and deserialization will be
## simplified: the code will just walk the moml dom
## recursively, instantiating classes as they are defined in
## the moml file, based on vice.actor.whatever,
## vice.data.expr.whatever, rather than building native Node
## objects.
        

class Network:
    class NodeAlreadyInNetwork(exceptions.Exception): pass
        

    def __init__(self, path=None, cb=CB_DEFAULT):
        self._cb = cb
        self._nodes = {}
        self._relations = []
        self._size_x = 1024
        self._size_y = 768
        
        if self._cb:
            self._cb(*[NETWORK_CREATE_BEGIN, self])

        if path:
            self._path = path
            self.deserialize(path)
        else:
            self._path = None

        if self._cb:
            self._cb(*[NETWORK_CREATE_END, self])

    def deserialize(self, path):
        file_uri = Lib.Uri.OsPathToUri(path, attemptAbsolute=1)
        doc = Domlette.NonvalidatingReader.parseUri(file_uri)

        ## TODO: replace w/ local reference, so this doesn't require an outgoing network connection
        ctx = XPath.Context.Context(doc, processorNss=NSS)

        ## collect network attributes
        network_size_node = XPath.Evaluate(u'/entity/property[@class="vice.actor.gui.SizeAttribute"]', ctx)
        if network_size_node:
            network_size_value = network_size_node[0].getAttributeNS(EMPTY_NAMESPACE, "value")
            x, y = map(int, network_size_value[1:-1].split(","))
        else:
            x, y = 1024, 768
        self.SetSize(x, y)
        

        ## retrieve all entities and convert to nodes
        entities = XPath.Evaluate(u'/entity/entity', ctx)

        ## instantiate nodes
        for entity in entities:
            location_node = entity.xpath('property[@name="_location"]', EMPTY_NAMESPACE)[0]
            location_value = location_node.getAttributeNS(EMPTY_NAMESPACE, "value")
            x, y = map(int, location_value[1:-1].split(","))

            actor_class = entity.getAttributeNS(EMPTY_NAMESPACE, "class")
            actor_id= entity.getAttributeNS(EMPTY_NAMESPACE, "id")
            
            node = self.AddNode(entity, actor_id, actor_class, (x,y))

            ## No longer need to do this (?) since the node constructor calls AddParameters
##             ## TODO: switch this so that multiple types of parameters are supported
##             ## maybe try to iterate over all property[@class=, then try instantiating each klass by name
##             parameters = entity.xpath('property', EMPTY_NAMESPACE)
##             for parameter in parameters:
##                 self.AddParameter(node, parameter)
                
        ## retrieve all edges(links/relations)
        relations = XPath.Evaluate(u'/entity/relation', ctx)
        for relation in relations:
            name = relation.getAttributeNS(EMPTY_NAMESPACE, "name")
            ## support ViCE-style MoML
            if name.startswith("relation."):
                relation_name, relation_id = name.split(".")
            ## support (retch) Ptolemy-style MoML
            else:
                relation_name, relation_id = ending_numeric.match(name).groups()
            
            source, dest = XPath.Evaluate(u'/entity/link[@relation="%s"]' % name, ctx)
            source_port = source.getAttributeNS(EMPTY_NAMESPACE, "port")
            dest_port = dest.getAttributeNS(EMPTY_NAMESPACE, "port")

            source_nodename = str(source_port.split(".")[0])
            dest_nodename = str(dest_port.split(".")[0])

            source_id = str(source_port.split(".")[1])
            dest_id = str(dest_port.split(".")[1])

            source_port_name = str(source_port.split(".")[2])
            dest_port_name = str(dest_port.split(".")[2])
            
            source_nodename_id = ".".join([source_nodename, source_id])
            dest_nodename_id = ".".join([dest_nodename, dest_id])

            source_node = self.GetNode(source_id)
            dest_node = self.GetNode(dest_id)

            ## Consider adding a 'SetRelation' method on Links, so we can access all relations for a node's links 
            source_link = Link(source_node, source_node.GetOutPort(source_port_name), cb=self._cb)
            dest_link = Link(dest_node, dest_node.GetInPort(dest_port_name), cb=self._cb)

            r = Relation(relation_id, source_link, dest_link, cb=self._cb)
            self.AddRelation(r)

            source_node.AddSourceLink(source_link)
            dest_node.AddDestLink(dest_link)

    def serialize(self):
        NETWORK_DOC = """<?xml version="1.0" encoding="UTF-8"?>
        <entity name="model" class="vice.actor.TypedCompositeActor">
        </entity>
        """
        
        doc = Domlette.NonvalidatingReader.parseString(NETWORK_DOC)
        ctx = XPath.Context.Context(doc)

        root = XPath.Evaluate(u'//entity', context=ctx)[0]

        ## Add the director property (not actually used by vice yet)
        director = doc.createElementNS(EMPTY_NAMESPACE, 'property')
        director.setAttributeNS(EMPTY_NAMESPACE, 'name', 'PN Director')
        director.setAttributeNS(EMPTY_NAMESPACE, 'class', 'vice.domains.pn.kernel.PNDirector')
        director_location = doc.createElementNS(EMPTY_NAMESPACE, 'property')
        director_location.setAttributeNS(EMPTY_NAMESPACE, 'name', '_location')
        director_location.setAttributeNS(EMPTY_NAMESPACE, 'class', 'vice.kernel.util.Location')
        director_location.setAttributeNS(EMPTY_NAMESPACE, 'value', '{0,0}')
        director.appendChild(director_location)
        root.appendChild(director)
        
        windowProperties = doc.createElementNS(EMPTY_NAMESPACE, 'property')
        windowProperties.setAttributeNS(EMPTY_NAMESPACE, 'name', '_windowProperties')
        windowProperties.setAttributeNS(EMPTY_NAMESPACE, 'value', '{bounds={568, 195, 1038, 794}, maximized=false}')
        windowProperties.setAttributeNS(EMPTY_NAMESPACE, 'class', 'vice.actor.gui.WindowPropertiesAttribute')
        root.appendChild(windowProperties)
        
        vergilSize = doc.createElementNS(EMPTY_NAMESPACE, 'property')
        vergilSize.setAttributeNS(EMPTY_NAMESPACE, 'name', '_vergilSize')
        vergilSize.setAttributeNS(EMPTY_NAMESPACE, 'value', '[%d,%d]' % self.GetSize())
        vergilSize.setAttributeNS(EMPTY_NAMESPACE, 'class', 'vice.actor.gui.SizeAttribute')
        root.appendChild(vergilSize)

        vergilZoomFactor = doc.createElementNS(EMPTY_NAMESPACE, 'property')
        vergilZoomFactor.setAttributeNS(EMPTY_NAMESPACE, 'name', '_vergilZoomFactor')
        vergilZoomFactor.setAttributeNS(EMPTY_NAMESPACE, 'value', '"1.0"')
        vergilZoomFactor.setAttributeNS(EMPTY_NAMESPACE, 'class', 'vice.data.expr.ExpertParameter')
        root.appendChild(vergilZoomFactor)

        vergilCenter = doc.createElementNS(EMPTY_NAMESPACE, 'property')
        vergilCenter.setAttributeNS(EMPTY_NAMESPACE, 'name', '_vergilCenter')
        vergilCenter.setAttributeNS(EMPTY_NAMESPACE, 'value', '{411.5, 338.0}')
        vergilCenter.setAttributeNS(EMPTY_NAMESPACE, 'class', 'vice.data.expr.ExpertParameter')
        root.appendChild(vergilCenter)

        ## We maintain a random number index to uniquify nodes (unlike
        ## ptolemy, which uses an ambiguous numbering scheme)
        for node_id in self._nodes.keys():
            node = self._nodes[node_id]
            entity = doc.createElementNS(EMPTY_NAMESPACE, 'entity')
            entity.setAttributeNS(EMPTY_NAMESPACE, 'name', node.GetName())
            entity.setAttributeNS(EMPTY_NAMESPACE, 'class', node.__module__)
            entity.setAttributeNS(EMPTY_NAMESPACE, 'id', node.GetId())

            ## Add a location tag
            location = doc.createElementNS(EMPTY_NAMESPACE, 'property')
            location.setAttributeNS(EMPTY_NAMESPACE, 'name', '_location')
            location.setAttributeNS(EMPTY_NAMESPACE, 'class', 'vice.kernel.util.Location')
            x, y = node.GetPosition()
            location.setAttributeNS(EMPTY_NAMESPACE, 'value', '{%d, %d}' % (x,y))
            entity.appendChild(location)

            ## Add an icon representation
            icon = doc.createElementNS(EMPTY_NAMESPACE, 'property')
            icon.setAttributeNS(EMPTY_NAMESPACE, 'name', '_icon')
            icon.setAttributeNS(EMPTY_NAMESPACE, 'class', 'vice.vergil.icon.BoxedValueIcon')
            icon.setAttributeNS(EMPTY_NAMESPACE, 'value', 'value')
            entity.appendChild(icon)

            ## Visit all the parameters in the node (ptolemy only
            ## outputs params which have non-default values)
            ## TODO: support other types of parameters
            
            for parameter_name in node.GetParameters():
                value = node.GetParameterValue(parameter_name)
                param_type = node.GetParameterType(parameter_name)
                parameter_dom = node.GetParameterDom(parameter_name)
                property = doc.createElementNS(EMPTY_NAMESPACE, 'property')
                ## This doesn't belong here!
                if param_type == 'vice.data.expr.ChoiceParameter':
                    property.setAttributeNS(EMPTY_NAMESPACE, 'values', parameter_dom.getAttributeNS(EMPTY_NAMESPACE, "values"))
                property.setAttributeNS(EMPTY_NAMESPACE, 'name', parameter_name)
                ## Warning Will Robinson!  Crappy str() ahead!
                property.setAttributeNS(EMPTY_NAMESPACE, 'value', str(value))
                ## should do a lookup into the individual control here...
                ## (for example, to print values)
                property.setAttributeNS(EMPTY_NAMESPACE, 'class', param_type)
                entity.appendChild(property)

                attrName = doc.createElementNS(EMPTY_NAMESPACE, 'property')
                attrName.setAttributeNS(EMPTY_NAMESPACE, 'name', 'attributeName')
                attrName.setAttributeNS(EMPTY_NAMESPACE, 'value', parameter_name)
                attrName.setAttributeNS(EMPTY_NAMESPACE, 'class', 'vice.kernel.util.StringAttribute')
                icon.appendChild(attrName)

            ## Visit all the inports and outports in the node (ptolemy only outputs ports which have been added after creation, but we list them all)
            for port_pair in (('input', node.GetInPorts, node.GetInPort), ('output', node.GetOutPorts, node.GetOutPort)):
                port_type = port_pair[0]
                port_func = port_pair[1]
                port_get_func = port_pair[2]
                for port_name in port_func():
                    port = port_get_func(port_name)
                    port_elem = doc.createElementNS(EMPTY_NAMESPACE, 'port')
                    gn = port.GetName()
                    port_elem.setAttributeNS(EMPTY_NAMESPACE, 'name', port.GetName())
                    port_elem.setAttributeNS(EMPTY_NAMESPACE, 'class', 'vice.actor.TypedIOPort')
                    property = doc.createElementNS(EMPTY_NAMESPACE, 'property')
                    property.setAttributeNS(EMPTY_NAMESPACE, 'name', port_type)
                    port_elem.appendChild(property)
                    property = doc.createElementNS(EMPTY_NAMESPACE, 'property')
                    property.setAttributeNS(EMPTY_NAMESPACE, 'name', '_type')
                    property.setAttributeNS(EMPTY_NAMESPACE, 'class', 'vice.actor.TypeAttribute')
                    property.setAttributeNS(EMPTY_NAMESPACE, 'value', 'unknown')
                    port_elem.appendChild(property)
                    entity.appendChild(port_elem)

            root.appendChild(entity)

        for relation in self._relations:
            relation_element = doc.createElementNS(EMPTY_NAMESPACE, 'relation')
            ## Only write ViCE-style MoML ('relation' and the numeric ID are seperated by a dot)
            relation_element.setAttributeNS(EMPTY_NAMESPACE, 'name', 'relation.%s' % relation.GetId())
            relation_element.setAttributeNS(EMPTY_NAMESPACE, 'class', 'vice.actor.TypedIORelation')
            root.appendChild(relation_element)

        for relation in self._relations:
            link_source_element = doc.createElementNS(EMPTY_NAMESPACE, 'link')
            link_source = relation.GetSourceLink()
            node_source = link_source.GetNode()
            link_source_element.setAttributeNS(EMPTY_NAMESPACE, 'port', "%s.%s.%s" % (node_source.GetName(), node_source.GetId(), link_source.GetPort().GetName()))
            ## Only write ViCE-style MoML
            link_source_element.setAttributeNS(EMPTY_NAMESPACE, 'relation', 'relation.%s' % relation.GetId())

            link_dest_element = doc.createElementNS(EMPTY_NAMESPACE, 'link')
            link_dest = relation.GetDestLink()
            node_dest = link_dest.GetNode()
            link_dest_element.setAttributeNS(EMPTY_NAMESPACE, 'port', "%s.%s.%s" % (node_dest.GetName(), node_dest.GetId(), link_dest.GetPort().GetName()))
            ## Only write ViCE-style MoML
            link_dest_element.setAttributeNS(EMPTY_NAMESPACE, 'relation', 'relation.%s' % relation.GetId())

            root.appendChild(link_source_element)
            root.appendChild(link_dest_element)

        buf = StringIO.StringIO()
        Domlette.PrettyPrint(doc, buf)
        xmlString = buf.getvalue()
        buf.close()

        return xmlString

    def GetSize(self):
        return self._size_x, self._size_y

    def SetSize(self, x, y):
        self._size_x = x
        self._size_y = y
        if self._cb:
            self._cb(*[NETWORK_SET_SIZE, self, x, y])

    ## actor_class, actor_id, and xy are now redundant with actor_dom being passed in!
    def AddNode(self, actor_dom, actor_id, actor_class, xy):
        log.debug("AddNode: %s, %s" % (actor_id, actor_class))
        actor_classname = actor_class.split(".")[-1]

        ## make an instance of the defined class
        ## If the module was already loaded, do a reload (makes debugging easier)
        if actor_class in sys.modules:
            module = sys.modules[actor_class]
            reload(module)
        else:    
            __import__(actor_class, globals(), locals(), [])
            module = sys.modules[actor_class]
        klass = module.__dict__[actor_classname]

        try:
            node = klass(actor_dom, actor_id, xy, self._cb)
        except NODE_CREATE_CALLBACK_FAILED:
            ## we don't really handle this here; allow somebody using the model framework to use the exception intelligently
            raise

        ## Can't see how this could ever happen!
        if node.GetId() in self._nodes:
            raise Network.NodeAlreadyInNetwork, "Node %s already in network"

        self._nodes[node.GetId()] = node
        if self._cb:
            self._cb(*[NETWORK_ADD_NODE, self, node])

        return node
    
    def DeleteNode(self, node):
        del self._nodes[node.GetId()]

        ## this is wrong; each node should have reference to the
        ## relations its part of, or the link should have a pointer to
        ## its relation
        node_source_relations = filter(lambda x: x.GetSourceLink().GetNode() == node, self._relations)
        node_dest_relations = filter(lambda x: x.GetDestLink().GetNode() == node, self._relations)

        ## ## Remove all relations 
        map(self.DeleteRelation, node_source_relations)
        map(self.DeleteRelation, node_dest_relations)

        if self._cb:
            self._cb(*[NETWORK_DELETE_NODE, self, node])
        
    def GetNode(self, node_name):
        return self._nodes[node_name]

    def GetNodes(self):
        return self._nodes

##     def AddParameter(self, node, parameter):
##         node.AddParameter(parameter)

    def AddRelation(self, relation):
        self._relations.append(relation)
        relation.GetSourceLink().SetRelation(relation)
        relation.GetDestLink().SetRelation(relation)

        if self._cb:
            self._cb(*[NETWORK_ADD_RELATION, self, relation])

    def DeleteRelation(self, relation):
        if self._cb:
            self._cb(*[NETWORK_DELETE_RELATION, self, relation])
        self._relations.remove(relation)

        ## Delete the links that compose this relation
        source_link = relation.GetSourceLink()
        source_node = source_link.GetNode()
        dest_link = relation.GetDestLink()
        dest_node = dest_link.GetNode()
        source_node.DeleteSourceLink(source_link)
        dest_node.DeleteDestLink(dest_link)

    def GetRelations(self):
        return self._relations

    def __str__(self):
        result= "Network: path=%s" % self._path

        node_result = "\t" + "\n\t".join(map(lambda x: str(self._nodes[x]), self._nodes.keys()))
        relation_result = "\t" + "\n\t".join(map(lambda x: str(x), self._relations))
        return "\n".join([result, node_result, relation_result])

    def __del__(self):
        return
    
##         try:
##             for node in self._nodes.keys():
## ##                 self.DeleteNode(node)
##                 no = self.GetNode(node)
##                 self.DeleteNode(no)
##         except AttributeError:
##             log.err("Error in Network destructor: attribute error looping on nodes")
##         except KeyError:
##             log.err("Error in Network destructor: cannot find node %s" % node)
##         try:
##             for relation in self._relations:
##                 self.DeleteRelation(relation)
##         except AttributeError:
##             log.err("Error in Network destructor: cannot attribute error looping on relations")

class Port:
    def __init__(self, name, node, cb=CB_DEFAULT):
        log.debug("Creating port with name=%s" % name)
        self._name = name
        self._node = node
        self._cb = cb

        if self._cb:
            self._cb(*[PORT_CREATE_BEGIN, self])

        if self._cb:
            self._cb(*[PORT_CREATE_END, self])

    def GetName(self):
        return self._name

    def GetNode(self):
        return self._node
    
    def __str__(self):
        return "Port: name=%s" % self._name

    def __del__(self):
        pass

class Node(Id):
    
    class NodeCreationFailed(exceptions.Exception): pass
    class NonexistentParameterValue(exceptions.Exception): pass

    class MissingInputOrOutputProperty(exceptions.Exception):
        def __init__(self, portName):
            self._portName = portName

    class LinkAlreadyInNode(exceptions.Exception):
        def __init__(self, link):
            self._link = link

    class SourceLinkAlreadyInNode(LinkAlreadyInNode): pass

    class DestLinkAlreadyInNode(LinkAlreadyInNode): pass

    class PortNameAlreadyExists(exceptions.Exception):
        def __init__(self, portName):
            self._portName = portName

    class ParameterNameAlreadyExists(exceptions.Exception):
        def __init__(self, parameterName):
            self._parameterName = parameterName

    def __init__(self, dom=None, _id=None, xy=None, cb=CB_DEFAULT):
        self._cb = cb

        Id.__init__(self, _id)
        ## if no custom dom is provided, then use the default XML (in the actor's class definition)
        if not dom:
            self._dom = Domlette.NonvalidatingReader.parseString(self.xml).xpath("/entity")[0]
        ## Use a custom dom (from a saved network) to render this node
        else:
            self._dom = dom
        name = self._dom.getAttributeNS(EMPTY_NAMESPACE, "name")
        self.SetName(name)
        self._x = xy[0]
        self._y = xy[1]

        self._parameters = {}
        self._parameter_doms = {}
        self._parameter_order = []

        self._source_links = []
        self._dest_links = []
        self._in_ports = {}
        self._out_ports = {}

        if self._cb:
            self._cb(*[NODE_CREATE_BEGIN, self])

        log.debug("Node: calling AddPorts")
        self.AddPorts()
        self.AddParameters()
        
        if self._cb:
            self._cb(*[NODE_CREATE_END, self])

    def AddParameters(self):
        ## Pull out the DOM representing the parameters
        parameters = self._dom.xpath('property', EMPTY_NAMESPACE)
        for parameter in parameters:
            if parameter.getAttributeNS(EMPTY_NAMESPACE, "class"):
                self.AddParameter(parameter)

    def AddParameter(self, parameter_dom, parameter_name=None, parameter_value=None):
        p_type = parameter_dom.getAttributeNS(EMPTY_NAMESPACE, "class")
        ## If this dom node is of a type that's a registered param/control,
        ## add the param/control to the node
        control_name = None
        control_value = None

        if parameter_name or parameter_value:
            ## create a copy of the dom first, so it doesn't change the class definition of the dom!
            ## is there an easier way to deepcopy a dom?
            buf = StringIO.StringIO()
            Domlette.PrettyPrint(parameter_dom, buf)
            xmlString = buf.getvalue()
            buf.close()
            parameter_dom = Domlette.NonvalidatingReader.parseString(xmlString).firstChild

        if parameter_name:
            parameter_dom.setAttributeNS(EMPTY_NAMESPACE, "name", parameter_name)
        if parameter_value:
            parameter_dom.setAttributeNS(EMPTY_NAMESPACE, "value", parameter_value)

        if not (parameter_name and parameter_value):
            try:
                control_class = expr.find(p_type)
            except KeyError:
                log.debug("Unknown p_type: %s" % p_type)
            else:
                if not control_name:
                    control_name = parameter_dom.getAttributeNS(EMPTY_NAMESPACE, "name")
                if not control_value:
                    control_value = parameter_dom.getAttributeNS(EMPTY_NAMESPACE, "value")

                if control_name in self._parameters:
                    raise Node.ParameterNameAlreadyExists, control_name
                self._parameter_order.append(control_name)

                ## Set parameter control to have initial null values, so
                ## that the widget-to-parameter system has something to
                ## reference

                self.SetParameterDom(control_name, parameter_dom)
                self.SetParameterValue(control_name, control_value)

    ## This adds ports from the XML description of the actor, not the ports defined in the saved network.
    def AddPorts(self):
        ports = self._dom.xpath(u'port')
        log.debug("Node: AddPorts: %s" % ports)

        for port in ports:
        ## Pull out the DOM representing the node
            portName = port.getAttributeNS(EMPTY_NAMESPACE, "name")
            is_input = port.xpath(u'property[@name="input"]', EMPTY_NAMESPACE)
            is_output = port.xpath(u'property[@name="output"]', EMPTY_NAMESPACE)
            
            if is_input:
                self.AddInPort(portName)
            elif is_output:
                self.AddOutPort(portName)
            else:
                raise MissingInputOrOutputProperty, portName

    def AddInPort(self, portName):
        log.debug("AddInPort: %s, %s" % (self, portName))
        if unicode(portName) in self.GetInPorts():
            raise Node.PortNameAlreadyExists, portName
        port = Port(portName, self, cb=self._cb)
        self._in_ports[port.GetName()] = port
        if self._cb:
            self._cb(*[NODE_ADD_IN_PORT, port])
        return port
    
    def AddOutPort(self, portName):
        log.debug("AddOutPort, self='%s', portName='%s'" % (self, portName))
        if unicode(portName) in self.GetOutPorts():
            raise Node.PortNameAlreadyExists, portName
        port = Port(portName, self, cb=self._cb)
        self._out_ports[port.GetName()] = port
        if self._cb:
            self._cb(*[NODE_ADD_OUT_PORT, port])
        return port
    
    def GetInPorts(self):
        return self._in_ports.keys()
    
    def GetInPort(self, name):
        return self._in_ports[name]

    def GetOutPorts(self):
        return self._out_ports.keys()

    def GetOutPort(self, name):
        return self._out_ports[name]

    def SetPosition(self, xy):
        if self._cb:
            self._cb(*[NODE_SET_POSITION, self, xy])
        self._x = xy[0]
        self._y = xy[1]

    def GetPosition(self):
        return self._x, self._y
    
    def SetName(self, name):
        self._name = name
        
    def GetName(self):
        return self._name

    def GetPosition(self):
        return self._x, self._y

    def GetParameterValue(self, name):
        if name in self._parameters:
            return self._parameters[name]
        else:
            raise Node.NonexistentParameterValue, name

    def GetParameterType(self, name):
        return self._parameter_doms[name].getAttributeNS(EMPTY_NAMESPACE, "class")
        
    def GetParameters(self):
        return self._parameter_order

    ## TODO: figure out how to support mutiple control widget types here
    def SetParameterValue(self, name, value):
        self._parameters[name] = value
        if self._cb:
            self._cb(*[NODE_SET_PARAMETER_VALUE, self, name, value])

    def SetParameterDom(self, name, dom):
        self._parameter_doms[name] = dom
        if self._cb:
            self._cb(*[NODE_SET_PARAMETER_DOM, self, name, dom])

    def GetParameterDom(self, name):
        return self._parameter_doms[name]

    def AddSourceLink(self, link):
        if link in self._source_links:
            raise SourceLinkAlreadyInNode, link
        self._source_links.append(link)

    def DeleteSourceLink(self, link):
        self._source_links.remove(link)
        
    def AddDestLink(self, link):
        if link in self._dest_links:
            raise DestLinkAlreadyInNode, link
        self._dest_links.append(link)

    def DeleteDestLink(self, link):
        self._dest_links.remove(link)

    def GetSourceLinks(self):
        return self._source_links

    def GetDestLinks(self):
        return self._dest_links

    def __str__(self):
        return "Node: id=%s, name=%s, in_ports=%s, out_ports=%s" % (self.GetId(), self._name,
                                                                             #self._source_links,
                                                                             #self._dest_links,
                                                                             map(str, self._in_ports), map(str, self._out_ports))

    def __del__(self):
        pass
    
class Relation(Id):
    def __init__(self, _id, source_link, dest_link, cb=CB_DEFAULT):
        Id.__init__(self, _id)
        self._source_link = source_link
        self._dest_link = dest_link

        self._cb = cb
        if self._cb:
            self._cb(*[RELATION_CREATE_BEGIN, self])

        if self._cb:
            self._cb(*[RELATION_CREATE_END, self])

    def GetSourceLink(self):
        return self._source_link

    def GetDestLink(self):
        return self._dest_link

    def __str__(self):
        return "Relation: id=%s, source %s dest %s" % (self.GetId(), self._source_link, self._dest_link)

    def __del__(self):
        pass
        
class Link:
    class DestinationPortOccupied(exceptions.Exception): pass

    ## TODO: 'port' arg should just be a string rather than a port object, to ensure the port belongs to the node
    def __init__(self, node, port, cb=CB_DEFAULT):
        node_dest_links = node.GetDestLinks()
        if [dest_link for dest_link in node_dest_links if dest_link.GetPort() == port]:
            raise Link.DestinationPortOccupied
        self._node = node
        self._port = port
        self._relation = None
        self._cb = cb
        if self._cb:
            self._cb(*[LINK_CREATE_BEGIN, self])
        if self._cb:
            self._cb(*[LINK_CREATE_END, self]) 
    
    def GetNode(self):
        return self._node

    def GetPort(self):
        return self._port

    def SetRelation(self, relation):
        self._relation = relation
        
    def GetRelation(self):
        return self._relation
    
    ## Don't invoke _node's str; otherwise, we'd get str loops
    def __str__(self):
        return "Link, nodename=%s, nodeid=%s, portname=%s" % (self._node.GetName(), self._node.GetId(), self._port.GetName())

    def __del__(self):
        pass

from vice.autolog import autolog_module
autolog_module()