/* Node is a formal class for things that are connected as part
   of a Network.  Node defines the connection protocol. Nodes
   know their inputs and outputs, their network and their
   relative display position in the network. */!!

inherit(Object, #Node, #(name     /* node name */
desc     /* longer description */
inputs   /* collection of inputs */
outputs  /* collection of outputs */
network  /* who "owns" the node */
x        /* display x position */
y        /* display y position */), 2, nil)!!

now(NodeClass)!!

/* Create and initialize a new node. */
Def  new(self)
{ 
  ^init(new(self:Behavior));
} !! 

now(Node)!!

/* Return the network the node belongs to. */
Def  getNetwork(self)
{
  ^network;
}!!

/* Return a point of the x and y instance variables. */
Def  pos(self)
{
  ^point(x, y);
}!!

/* Get the description. */
Def  getDesc(self)
{ ^desc;
}   !!

/* Get the x position. */
Def  x(self)
{ ^x;
}   !!

/* Get the y position. */
Def  y(self)
{ ^y;
}   !!

/* Reset the position when disconnecting.
      x : the maximum level of any inputs + 1
      y : don't change it since it's already set
   If the x position changes, propogate it.
   Tell the network also.
*/
Def  resetPosn(self, inputNode | oldPos)
{  
  oldPos := pos(self);
  if x <= x(inputNode) + 1
    x := 0;
    do(inputs,
      {using(input) x := max(x(input)+1, x);
    });
    do(outputs,
      {using(output) 
       if inputNode <> output cand self <> output  /* avoid loop */
         resetPosn(output, self);
       endif;
    });
  endif;
  if x+y = 0    /* completely disconnected */
     y := 1;    /* use a "safe" location */
  endif;
  /* the new position could conflict with something else in the project */
  if oldPos <> pos(self)
    resetPosn(network, self, oldPos);
  endif;
}!!

/* Set the position when connecting.
      x : the maximum level of any inputs + 1
      y : the y of input plus the output number
   If the x position changes, propogate it.
   Also, tell the network.
 */
Def  setPosn(self, inputNode | oldPos)
{ 
  oldPos := pos(self);
  
  /* If the node position has not been set, set it's y. */
  if x = 0
    y := max(0,y(inputNode) + size(getOutputs(inputNode)) - 1);
  endif;
  
  /* If the level increases, set it and propogate. */
  if x(inputNode) + 1 > x
    x := x(inputNode) + 1;
    do(outputs,
      {using(output)    
       if inputNode <> output cand self <> output  /* avoid loop */
          setPosn(output, self);
       endif;
    });
  endif;
  /* the new position could conflict with something else in the project */
  if oldPos <> pos(self)
    resetPosn(network, self, oldPos);
  endif;
}!!

/* Set the network the node belongs to. */
Def  setNetwork(self, aNet)
{
  network := aNet;
}!! 

/* To disconnect node n1->n2, n1's outputs must remove n2. */
Def  removeOutputs(self, aNode)
{ remove(outputs, aNode);
}  !! 

/* To disconnect node n1->n2, n2's inputs must remove n1. */
Def  removeInputs(self, aNode)
{ remove(inputs, aNode);
}  !!

/* Disconnect n1->n2 by updating n1's outputs and n2's inputs. 
   The node n2 position must be reset based on its former input. */
Def  disconnect(self, aNode)
{ removeInputs(aNode, self);
  removeOutputs(self, aNode);
  resetPosn(aNode, self);
} !!

/* Recursively show all outputs, level by level.
   Keep track of visited nodes to avoid looping. 
   n is the level for formatting.  CurPos is a
   global of current print position for formatting.
   Note: This is for testing networks during development.
*/
Def  show(self, visited, n)
{ 
  do(n-CurPos,                      /* adjust position */
    {using(i) print("      ");
  });
  CurPos:=n;                        /* update position */
  print(self);                      /* show node */
  if size(outputs) == 0 then
     printLine("");print("   ");
     CurPos := 0;
  else
     CurPos := CurPos +1;
  endif;
  do(outputs,                       /* show outputs */
    {using(elem) 
      if(not(elem in visited))      /* not yet shown */
        add(visited, elem);         /* avoid looping */ 
        show(elem, visited, n+1);   /* recurse */
      else
        printLine("");
        print("   ");
        CurPos := 0;                /* reset position */
      endif;
    });
} !!

/* pass through method to return a node's outputs */
Def  getOutputs(self)
{ ^outputs;
} !! 

/* pass through method to return a node's inputs */
Def  getInputs(self)
{ ^inputs;
} !!

/* To connect node n1->n2, n1's outputs must contain n2. */
Def  addOutputs(self, aNode)
{ 
  add(outputs, aNode);
} !!

/* To connect n1->n2, n2's inputs must contain n1. */
Def  addInputs(self, aNode)
{ 
  add(inputs, aNode);
} !!

/* Connect self->aNode by updating self's outputs and 
   aNode's inputs. Also, aNode should know it's network
   and the position should be set based on it's input. */
Def  connect(self, aNode)
{ addInputs(aNode, self);
  addOutputs(self, aNode);
  setNetwork(aNode, network);
  setPosn(aNode, self);
} !!

/* Set the name. */
Def  setName(self, aName)
{ name := aName;
}   !!

/* Get the name. */
Def  getName(self)
{ ^name;
}   !!

/* Initialize a new node.  Set the name, desc
   and position to safe values.  Keep track of
   our connections in inputs and outputs.  These
   sets will grow as necessary. */
Def  init(self)
{ name := desc:= "";
  x := y := 0;
  inputs := new(Set, 2);
  outputs := new(Set, 2);
}  !!

/* Print the node.  
   This is used during development only. */
Def  printOn(self, aStrm)
{ 
  printOn(asString(class(self))+":"+asString(name), aStrm);
}!! 

