#########################################################################
#
# Date: Aug 2003 Author: Daniel Stoffler
#
#    stoffler@scripps.edu
#
# Copyright: Daniel Stoffler and TSRI
#
#########################################################################

import sys, string, os
from time import sleep

from NetworkEditor.net import Network
from NetworkEditor.simpleNE import NetworkNode, NetworkBuilder
from NetworkEditor.macros import MacroNode, MacroInputNode, MacroOutputNode, \
     MacroNetwork
from NetworkEditor.Tests.nodes import PassNode, DialNode, PrintNode

ed = None
node1 = None
node2 = None

def setUp():
    global ed
    ed = NetworkBuilder("test builder1", withShell=0,
                        visibleWidth=400, visibleHeight=350)
    ed.master.update()
    ed.configure(withThreads=0)


def tearDown():
    ed.exit_cb()
    import gc
    gc.collect()


def pause(sleepTime=None):
    if sleepTime is None:
        from NetworkEditor.Tests import pauseLength as sleepTime
    ed.master.update()
    sleep(sleepTime)


def test_01_createAndDestroyMacro():
    """this test adds a macro node to a new network named 'test network1'.
    It then tests if a new network named 'test macro' is created and if a
    Macroinput and a MacroOutputNode are added. Then the MacroNetwork will
    collapse and expanded and finally the entire network gets deleted"""
    net = Network('createAndDestroyMacro')
    ed.addNetwork(net)

    # add macro node
    node0 = MacroNode(name='test macro')
    net.addNode(node0, 200, 200)

    # test that a MacroNode in the main network has been created
    macronode = net.nodes[0]
    assert isinstance(macronode, MacroNode), \
           "Expected %s, got %s"%(MacroNode, macronode.__class__)

    # test that MacroNode attribute network is set to the main
    # network and that the attribute macroNetwork is set to the MacroNetwork
    assert isinstance(macronode.network, Network),\
           "Expected %s, got %s"%(Network, macronode.network.__class__)
    assert hasattr(macronode, 'macroNetwork'),\
           "MacroNode has not attribute 'macroNetwork'"
    assert isinstance(macronode.macroNetwork, MacroNetwork),\
           "Expected %s, got %s"%(MacroNetwork, macronode.macroNetwork.__class__)
    macronet = macronode.macroNetwork

    # test MacroNetwork name, same as node0.name
    assert macronet.name == node0.name == 'test macro',\
           "MacroNetwork.name should be 'test macro', but name is '%s'"%macronet.name

    # test that a MacroInputNode and a MacroOutputNode have been added to the
    # MacroNetwork
    ipnode = macronet.nodes[0]
    opnode = macronet.nodes[1]
    assert isinstance(ipnode, MacroInputNode),\
           "Expected %s, got %s"%(MacroInputNode, ipnode.__class__)
    assert isinstance(opnode, MacroOutputNode),\
           "Expected %s, got %s"%(MacroOutputNode, opnode.__class__)

    # test that MacroInput and MacroOutputNode know about the MacroNode
    assert hasattr(ipnode, 'macroNode'),\
           "ipnode has not attr 'macroNode'"
    assert hasattr(opnode, 'macroNode'),\
           "opnode has no attr 'macroNode'"

    # test that the macroNetwork know about the MacroInput and MacroOutputNode
    assert hasattr(macronet, 'ipNode'),\
           "macronet has no attr 'ipNode'"
    assert hasattr(macronet, 'opNode'),\
           "macronet has not attr 'opNode'"

    # test that a MacroNetwork can be collapsed
    macronode.shrink()
    pause()
    # assert that the MacroNetwork with the name 'test macro' still exists
    assert 'test macro' in net.editor.networks.keys(),\
           "Current keys are: %s"%net.editor.networks.keys()
    assert 'createAndDestroyMacro' in net.editor.networks.keys(),\
           "Current keys are: %s"%net.editor.networks.keys()
           

    # test that a MacroNetwork can be expanded again
    macronode.expand()
    pause()
    # assert that the MacroNetwork is back in the list
    assert 'test macro' in net.editor.networks.keys(),\
           "Current keys are: %s"%net.editor.networks.keys()
    assert 'createAndDestroyMacro' in net.editor.networks.keys(),\
           "Current keys are: %s"%net.editor.networks.keys()

    # shrink macro again
    macronode.shrink()
    pause()
    # delete macro
    net.deleteNodes([macronode])
    # delete network
    ed.deleteNetwork(net)

   

def test_02_deleteExpandedMacro():
    """This tests if an opended macro network is properly deleted when deleting
    the MacroNode in the main network.
    PLEASE NOTE: DELETING A MAIN NETWORK WITH A MACRO IS NOT POSSIBLE IN VIPER
    BECAUSE WE DISABLE THE 'DELETE' MENU ENTRY."""
    net = Network('deleteExpandedMacro')
    ed.addNetwork(net)

    # add macro node
    node0 = MacroNode(name='test macro2')
    net.addNode(node0, 200, 200)
    macronode = node0
    macronet = macronode.macroNetwork
    ipnode = macronet.nodes[0]
    opnode = macronet.nodes[1]
    # assert both networks are present
    assert 'deleteExpandedMacro' in net.editor.networks.keys(),\
           "Current keys are: %s"%net.editor.networks.keys()
           
    assert 'test macro2' in net.editor.networks.keys(),\
           "Current keys are: %s"%net.editor.networks.keys()
    pause()
    # delete the MacroNode
    net.deleteNodes([macronode])
    # assert the MacroNetwork 'test macro2' is gone
    assert 'test macro2' not in net.editor.networks.keys(),\
           "Current keys are: %s"%net.editor.networks.keys()
    pause()
    # delete network
    ed.deleteNetwork(net)
    

def test_03_addPorts():
    """This tests if new ports are created upon connecting nodes to a
    MacroInputNode or a MacroOutputode"""
    net = Network('addPorts')
    ed.addNetwork(net)

    # add macro node
    node0 = MacroNode(name='test macro3')
    net.addNode(node0, 200, 200)
    macronode = node0
    macronet = macronode.macroNetwork
    ipnode = macronet.nodes[0]
    opnode = macronet.nodes[1]

    # ipnode should have 1 special output port
    # opnode should have 1 special input port
    # macronode should have 0 input/output ports
    assert len(ipnode.outputPorts) == 1,\
           "Expected 1, got %s"%len(ipnode.outputPorts)
    assert len(opnode.inputPorts) == 1,\
           "Expected 1, got %s"%len(opnode.inputPorts)
    assert len(macronode.inputPorts) == 0,\
           "Expected 0, got %s"%len(macronode.inputPorts)
    assert len(macronode.outputPorts) == 0,\
           "Expected 0, got %s"%len(macronode.outputPorts)

    node1 = PassNode()
    macronet.addNode(node1, 150, 150)
    macronet.connectNodes(ipnode, node1, 0, 0)
    macronet.connectNodes(node1, opnode, 0, 0)

    # now, all nodes (except the Pass) should have 1 port more
    assert len(ipnode.outputPorts) == 2,\
           "Expected 2, got %s"%len(ipnode.outputPorts)
    assert len(opnode.inputPorts) == 2,\
           "Expected 2, got %s"%len(opnode.inputPorts)
    assert len(macronode.inputPorts) == 1,\
           "Expected 1, got %s"%len(macronode.inputPorts)
    assert len(macronode.outputPorts) == 1,\
           "Expected 1, got %s"%len(macronode.outputPorts)
    pause()

    net.deleteNodes([macronode])
    ed.deleteNetwork(net)


def test_04_deletePorts():
    """This tests if new ports that were created upon connecting nodes to a
    MacroInputNode or a MacroOutputode are getting destroyed if the connection
    is destroyed"""
    net = Network('deletePorts')
    ed.addNetwork(net)

    # add macro node
    node0 = MacroNode(name='test macro3')
    net.addNode(node0, 200, 200)
    macronode = node0
    macronet = macronode.macroNetwork
    ipnode = macronet.nodes[0]
    opnode = macronet.nodes[1]

    # add pass node and connect 
    node1 = PassNode()
    macronet.addNode(node1, 150, 150)
    macronet.connectNodes(ipnode, node1, 0, 0)
    macronet.connectNodes(node1, opnode, 0, 0)
    pause()

    # now delete the connection to MacroInputNode
    c = node1.inputPorts[0].connections[0]
    macronet.deleteConnections([c])
    pause()
    # macroinputnode should now have only 1 outputport
    assert len(ipnode.outputPorts) == 1,\
           "Expected 1, got %s"%len(ipnode.outputPorts)
    # macronode should have 0 inputports
    assert len(macronode.inputPorts) == 0,\
           "Expected 0, got %s"%len(macronode.inputPorts)

    # delete the connection to MacroOutputNode
    c = node1.outputPorts[0].connections[0]
    macronet.deleteConnections([c])
    pause()
    # macrooutputnode should now have only 1 inputport
    assert len(opnode.inputPorts) == 1,\
           "Expected 1, got %s"%len(opnode.inputPorts)
    # macronode should have 0 outputports
    assert len(macronode.outputPorts) == 0,\
           "Expected 0, got %s"%len(macronode.outputPorts) 

    net.deleteNodes([macronode])
    ed.deleteNetwork(net)


def test_05_deleteMacroConnections():
    """This tests if connections to the Macro node are destroyed when
    connections inside the macronet to the ip and op node are destroyed"""
    net = Network('deleteMacroConnections')
    ed.addNetwork(net)

    # add macro node
    node0 = MacroNode(name='test macro4')
    net.addNode(node0, 200, 200)
    macronode = node0
    macronet = macronode.macroNetwork
    ipnode = macronet.nodes[0]
    opnode = macronet.nodes[1]

    # add pass node and connect 
    node1 = PassNode()
    macronet.addNode(node1, 150, 150)
    macronet.connectNodes(ipnode, node1, 0, 0)
    macronet.connectNodes(node1, opnode, 0, 0)
    node0.shrink()
    pause()

    # connect pass nodes in the main network to the macro node
    pass1 = PassNode()
    net.addNode(pass1, 50, 50)
    pass2 = PassNode()
    net.addNode(pass2, 50, 250)
    net.connectNodes(pass1, node0, 0, 0)
    net.connectNodes(node0, pass2, 0, 0)
    pause()

    # now delete the pass node in the macro network
    node0.expand()
    macronet.deleteNodes([node1])
    pause()
    assert len(pass1.outputPorts[0].connections) == 0,\
           "Expected 0, got %s"%len(pass1.outputPorts[0].connections)
    assert len(pass2.inputPorts[0].connections) == 0,\
           "Expected 0, got %s"%len(pass2.inputPorts[0].connections)
    node0.shrink()
    pause()
    
    net.deleteNodes([macronode])
    ed.deleteNetwork(net)


def test_06_deleteOneOfTwoConnections():
    """If 2 or more nodes connect to the same macro ip or op node, the
    port of the ip or op node should NOT be deleted"""
    net = Network('deleteOneofTwoConnections')
    ed.addNetwork(net)

    # add macro node
    node0 = MacroNode(name='test macro5')
    net.addNode(node0, 200, 200)
    macronode = node0
    macronet = macronode.macroNetwork
    ipnode = macronet.nodes[0]
    opnode = macronet.nodes[1]

    # add pass node and connect 
    node1 = PassNode()
    macronet.addNode(node1, 150, 150)
    macronet.connectNodes(ipnode, node1, 0, 0)
    macronet.connectNodes(node1, opnode, 0, 0)
    pause()

    # add second pass node and connect 
    node2 = PassNode()
    macronet.addNode(node2, 250, 150)
    macronet.connectNodes(ipnode, node2, 1, 0)
    # no new outputport has been created
    assert len(ipnode.outputPorts) == 2,\
           "Expected 2, got %s"%len(ipnode.outputPorts)
    pause()

    # now delete the second pass node in the macro network
    macronet.deleteNodes([node2])
    pause()
    # no port should have been deleted
    assert len(ipnode.outputPorts) == 2,\
           "Expected 2, got %s"%len(ipnode.outputPorts) 
    assert len(macronode.inputPorts) == 1,\
           "Expected 1, got %s"%len(macronode.inputPorts)
    # connection should still be there
    assert len(node1.inputPorts[0].connections) == 1,\
           "Expected 1, got %s"%len(node1.inputPorts[0].connections)
    node0.shrink()
    pause()
    
    net.deleteNodes([macronode])
    ed.deleteNetwork(net)


def test_07_whoIsCurrentNetwork():
    """add 2 macros into the main network, expand both, go to the second
macronetwork and shrink it. We should end up in the main network and not
in the macronetwork of the first macro."""
    net = Network('whoIsCurrentNetwork')
    ed.addNetwork(net)
    # add macro node1
    node1 = MacroNode(name='macro1')
    net.addNode(node1, 200, 200)
    # add macro node2
    node2 = MacroNode(name='macro2')
    net.addNode(node2, 250, 200)
    node1.expand()
    node2.expand()
    # are we in macronetwork of node2?
    ed.setNetwork(node2.macroNetwork)
    pause()
    assert ed.currentNetwork.name == 'macro2',\
           "Expected 'macro2', got '%s'"%ed.currentNetwork.name
    # shrink node2
    node2.shrink()
    pause()
    assert ed.currentNetwork.name == 'whoIsCurrentNetwork',\
           "Expected 'whoIsCurrentNetwork', got '%s'"%ed.currentNetwork.name
    net.deleteNodes([node1, node2])
    ed.deleteNetwork(net)


def test_08_deleteNetwork():
    """This tests if the macro network is deleted when a network with
    a macro node is deleted"""
    net = Network('saveDelete')
    ed.addNetwork(net)
    numberNetworks = len(ed.networks) # store number of current networks
    # add macro node1
    node1 = MacroNode(name='macro1')
    net.addNode(node1, 200, 200)
    # now we should have one more network
    assert len(ed.networks) == numberNetworks+1,\
           "Expected %s, got %s"%(numberNetworks+1, len(ed.networks) )
    # add Pass node to macro network, connect
    node2 = PassNode()
    ed.currentNetwork.addNode(node2, 200, 150)
    macronode = node1
    macronet = macronode.macroNetwork
    ipnode = macronet.nodes[0]
    opnode = macronet.nodes[1]
    macronet.connectNodes(ipnode, node2, 0, 0)
    macronet.connectNodes(node2, opnode, 0, 0)
    pause()
    node1.shrink()
    ed.deleteNetwork(ed.currentNetwork)
    # now we should have one less network
    assert len(ed.networks) == numberNetworks-1,\
           "Expected %s, got %s"%(numberNetworks-1, len(ed.networks) )


def test_09_saveLoadRunsimpleMacro():
    """build a very simple macro (containing a pass node), connect this to a
    Dial and a Print node, save this macro, load it, and run it."""
    net = Network('saveLoadTest')
    net.runOnNewData.value = True
    ed.addNetwork(net)
    # add macro node1
    node1 = MacroNode(name='macro1')
    net.addNode(node1, 200, 200)
    # add Pass node to macro network, connect
    node2 = PassNode()
    ed.currentNetwork.addNode(node2, 200, 150)
    macronode = node1
    macronet = macronode.macroNetwork
    ipnode = macronet.nodes[0]
    opnode = macronet.nodes[1]
    macronet.connectNodes(ipnode, node2, 0, 0)
    macronet.connectNodes(node2, opnode, 0, 0)
    # we have only 1 connection, we want singleConnection so that data is not
    # wrapped into a list
    opnode.inputPorts[1].configure(singleConnection=True)
    pause()
    node1.shrink()
    # add Dial node, print node and connect
    node3 = DialNode()
    net.addNode(node3, 75, 75)
    net.connectNodes(node3, node1, 0, 0)
    node4 = PrintNode()
    net.addNode(node4, 75, 250)
    net.connectNodes(node1, node4, 0, 0)
    pause()
    # now save network
    # make sure there is no such file
    os.system("rm -f tmpMacro_net.py*")
    # save the file
    ed.saveNetwork('tmpMacro_net.py')
    # and delete the network
    ed.deleteNetwork(ed.currentNetwork)
    # load the file
    pause()
    ed.loadNetwork('tmpMacro_net.py')
    # can we run this network?
    dialNode = ed.currentNetwork.nodes[1]
    dialNode.inputPorts[0].widget.set(42.0)
    pause()
    printNode =  ed.currentNetwork.nodes[2]
    # is the value passed through the macro
    assert printNode.inputPorts[0].data == 42.0,\
           "Expected 42.0, got %s"%printNode.inputPorts[0].data
    # clean up
    os.system("rm -f tmpMacro_net.py*")


def test_10_saveLoadRunNestedMacro():
    """build a nested macro (a macro in a macro containing a pass node),
    connect this to a Dial and a Print node, save this macro, load it,
    and run it."""
    net = Network('saveLoadNestedTest')
    net.runOnNewData.value = True
    ed.addNetwork(net)
    # add macro node0
    node0 = MacroNode(name='macro0')
    net.addNode(node0, 200, 200)
    # add macro node inside this macro network
    node1 = MacroNode(name='macro1')
    ed.currentNetwork.addNode(node1, 200, 200)
    # add Pass node to macro network, connect
    node2 = PassNode()
    ed.currentNetwork.addNode(node2, 200, 150)
    macronode = node1
    macronet = macronode.macroNetwork
    ipnode = macronet.nodes[0]
    opnode = macronet.nodes[1]
    macronet.connectNodes(ipnode, node2, 0, 0)
    macronet.connectNodes(node2, opnode, 0, 0)
    # we have only 1 connection, we want singleConnection so that data is not
    # wrapped into a list
    opnode.inputPorts[1].configure(singleConnection=True)
    pause()
    node1.shrink()
    macronode = node0
    macronet = macronode.macroNetwork
    ipnode = macronet.nodes[0]
    opnode = macronet.nodes[1]
    macronet.connectNodes(ipnode, node1, 0, 0)
    macronet.connectNodes(node1, opnode, 0, 0)
    # configure the new input port to be singleConnection, so that the value
    # passed through the macro is not wrapped into a list
    opnode.inputPorts[1].configure(singleConnection=True)
    # we have only 1 connection, we want singleConnection so that data is not
    # wrapped into a list
    opnode.inputPorts[1].configure(singleConnection=True)
    pause()
    node0.shrink()
    # add Dial node, print node and connect
    node3 = DialNode()
    net.addNode(node3, 75, 75)
    net.connectNodes(node3, node0, 0, 0)
    node4 = PrintNode()
    net.addNode(node4, 75, 250)
    net.connectNodes(node0, node4, 0, 0)
    pause()
    # now save network
    # make sure there is no such file
    os.system("rm -f tmpMacro_net.py*")
    # save the file
    ed.saveNetwork('tmpMacro_net.py')
    # and delete the network
    ed.deleteNetwork(ed.currentNetwork)
    # load the file
    pause()
    ed.loadNetwork('tmpMacro_net.py')
    # can we run this network?
    dialNode = ed.currentNetwork.nodes[1]
    dialNode.inputPorts[0].widget.set(42.0)
    pause()
    printNode =  ed.currentNetwork.nodes[2]
    # is the value passed through the macro
    assert printNode.inputPorts[0].data == 42.0,\
           "Expected 42.0, got %s"%printNode.inputPorts[0].data
    # clean up
    os.system("rm -f tmpMacro_net.py*")


def test_11_cutPasteMacro():
    """build a simple macro with just a pass node, cut and paste it."""
    net = Network('cutPasteTest')
    ed.addNetwork(net)
    # add macro node
    node1 = MacroNode(name='macro1')
    net.addNode(node1, 200, 200)
    # add Pass node to macro network, connect
    node2 = PassNode()
    ed.currentNetwork.addNode(node2, 200, 150)
    macronode = node1
    macronet = macronode.macroNetwork
    ipnode = macronet.nodes[0]
    opnode = macronet.nodes[1]
    macronet.connectNodes(ipnode, node2, 0, 0)
    macronet.connectNodes(node2, opnode, 0, 0)
    pause()
    node1.shrink()
    # select the macro node, so we can use the cutNetwork_cb()
    net.selectNodes([node1])
    # now cut this node
    net.editor.cutNetwork_cb()
    pause()
    # now paste the macro back
    net.editor.pasteNetwork_cb()
    pause()
    

def test_12_connectNodewithPortCallback():
    """This tests a previous bug where connecting a pass node only to the
    macroinput node and then connecting a dial outside to the macro node
    would fail, because the pass node input port has an afterConnect()
    method which was also added to the newly created input port of the
    macro node"""
    net = Network('PortCallbackTest')
    ed.addNetwork(net)
    # add macro node
    node1 = MacroNode(name='macro1')
    net.addNode(node1, 200, 200)
    # add Pass node to macro network, connect only to ipnode
    node2 = PassNode()
    ed.currentNetwork.addNode(node2, 200, 150)
    macronode = node1
    macronet = macronode.macroNetwork
    ipnode = macronet.nodes[0]
    macronet.connectNodes(ipnode, node2, 0, 0)
    node1.shrink()
    # add Dial node, and connect
    node3 = DialNode()
    net.addNode(node3, 75, 75)
    net.connectNodes(node3, node1, 0, 0)
    pause()
    # assert that the new port of the macro node has no callbacks
    descr = macronode.inputPorts[0].getDescr()
    assert not descr.has_key("beforeConnect"),\
           "Current keys in descr: %s"%descr.keys()
    assert not descr.has_key("afterConnect"),\
           "Current keys in descr: %s"%descr.keys()
    assert not descr.has_key("beforeDisconnect"),\
           "Current keys in descr: %s"%descr.keys()
    assert not descr.has_key("afterDisconnect"),\
           "Current keys in descr: %s"%descr.keys()
    # and try to delete this connection
    c = node3.outputPorts[0].connections[0]
    net.deleteConnections([c])
    pause()

   
def test_14_saveRestoreFrozen():
    """Test if we can set frozen=True on the macro node, save and restore
the network and frozen is set to True. (Default value is False)"""
    net = Network('TestSaveRestoreFrozen')
    ed.addNetwork(net)
    # add macro node
    node1 = MacroNode(name='macro1')
    net.addNode(node1, 200, 200)
    node1.shrink()
    assert hasattr(node1, 'frozen'), \
           "Error: Macro node has now attr 'frozen'"
    # now set it to True
    node1.toggleFrozen_cb()
    # now save network
    # make sure there is no such file
    os.system("rm -f tmpMacroFrozen_net.py*")
    # save the file
    ed.saveNetwork('tmpMacroFrozen_net.py')
    # and delete the network
    ed.deleteNetwork(ed.currentNetwork)
    # load the file
    pause()
    ed.loadNetwork('tmpMacroFrozen_net.py')
    # test frozen status
    node = ed.currentNetwork.nodes[0]
    assert node.frozen == True,\
           "Error: macro node attribute 'frozen' not set to True"
    # clean up
    os.system("rm -f tmpMacroFrozen_net.py*")

   
