/*
 * Copyright (c) 1991,1993 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the Computer Systems
 *      Engineering Group at Lawrence Berkeley Laboratory.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * @(#) $Header: /usr/src/mash/repository/vint/nam-1/netmodel.cc,v 1.48 1998/12/11 22:35:55 yaxu Exp $ (LBL)
 */

#include <stdlib.h>
#ifdef WIN32
#include <windows.h>
#endif
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <tcl.h>
#include <tclcl.h>

#include "netview.h"
#include "psview.h"
#include "animation.h"
#include "group.h"
#include "tag.h"
#include "queue.h"
#include "drop.h"
#include "packet.h"
#include "edge.h"
#include "lan.h"
#include "node.h"
#include "agent.h"
#include "feature.h"
#include "route.h"
#include "netmodel.h"
#include "monitor.h"
#include "trace.h"
#include "paint.h"
#include "sincos.h"
#include "state.h"
#include "editview.h"
#include "address.h"
#include "animator.h"

#include <float.h>

extern int lineno;
// not used
//static int next_pat;

/*XXX */
double defsize = 0.;

class NetworkModelClass : public TclClass {
public:
	NetworkModelClass() : TclClass("NetworkModel") {}
	TclObject* create(int argc, const char*const* argv) {
		if (argc < 5) 
			return 0;
		return (new NetModel(argv[4]));
	}
} networkmodel_class;

NetModel::NetModel(const char *animator) : TraceHandler(animator),
	drawables_(0), animations_(0), queues_(0),
	views_(0), nodes_(0), lans_(0), mon_count_(0), monitors_(NULL),
	nGroup_(0), nTag_(0)
{
	int i;
	for (i = 0; i < EDGE_HASH_SIZE; ++i)
		hashtab_[i] = 0;

	/*XXX*/
	nymin_ = 1e6;
	nymax_ = -1e6;

	Paint *paint = Paint::instance();
	int p = paint->thin();
	// nclass_ = 64; // Shouldn't this be 256??
	nclass_ = 256;
	paint_ = new int[nclass_];
	for (i = 0; i < nclass_; ++i)
		paint_[i] = p;

	addrHash_ = new Tcl_HashTable;
	Tcl_InitHashTable(addrHash_, TCL_ONE_WORD_KEYS);
	grpHash_ = new Tcl_HashTable;
	Tcl_InitHashTable(grpHash_, TCL_ONE_WORD_KEYS);
	tagHash_ = new Tcl_HashTable;
	Tcl_InitHashTable(tagHash_, TCL_STRING_KEYS);

	objnameHash_ = new Tcl_HashTable;
	Tcl_InitHashTable(objnameHash_, TCL_STRING_KEYS);
	registerObjName("ALL", ClassAllID);
	registerObjName("ANIMATION", ClassAnimationID);
	registerObjName("NODE", ClassNodeID);
	registerObjName("PACKET", ClassPacketID);
	registerObjName("EDGE", ClassEdgeID);
	registerObjName("QUEUEITEM", ClassQueueItemID);
	registerObjName("LAN", ClassLanID);
	registerObjName("TAG", ClassTagID);
}

NetModel::~NetModel()
{
	// We should delete everything here, if we want deletable netmodel...
	delete paint_;
	Animation *a, *n;
	for (a = animations_; a != 0; a = n) {
		n = a->next();
		delete a;
	}
	for (a = drawables_; a != 0; a = n) {
		n = a->next();
		delete a;
	}

	Tcl_DeleteHashTable(grpHash_);
	delete grpHash_;
	Tcl_DeleteHashTable(tagHash_);
	delete tagHash_;
	Tcl_DeleteHashTable(objnameHash_);
	delete objnameHash_;
}

void NetModel::update(double now)
{
	Animation *a, *n;
	for (a = animations_; a != 0; a = n) {
		n = a->next();
		a->update(now);
	}

	/*
	 * Draw all animations and drawables on display to reflect
	 * current time.
	 */
	now_ = now;
	for (View* p = views_; p != 0; p = p->next_) {
		p->draw();
	}
}

void NetModel::update(double now, Animation* a)
{
	// XXX Seems we should have a->update(now) here.
	a->update(now);
	for (View* p = views_; p != 0; p = p->next_)
		a->draw(p, now);
}

/* Reset all animations and queues to time 'now'. */
void NetModel::reset(double now)
{
	Animation* a;
	for (a = animations_; a != 0; a = a->next())
		a->reset(now);
	for (a = drawables_; a != 0; a = a->next())
		a->reset(now);
	for (Queue* q = queues_; q != 0; q = q->next_)
		q->reset(now);
}

/*
 * Draw this NetModel's animations and drawables (nodes, edges,
 * packets, queues).
 */
void NetModel::render(View* view)
{
	Animation *a;
	Monitor *m;
	for (a = drawables_; a != 0; a = a->next()) 
	  a->draw(view, now_);

	for (a = animations_; a != 0; a = a->next())
	  a->draw(view, now_);
	for(m=monitors_; m != NULL; m = m->next())
	  m->draw_monitor(view, nymin_, nymax_);
}

void NetModel::render(PSView* view)
{
	Animation *a;
	// Monitor *m;
	for (a = drawables_; a != 0; a = a->next())
	  a->draw(view, now_);
	for (a = animations_; a != 0; a = a->next())
	  a->draw(view, now_);
}

/* Return a pointer to the edge between 'src' and 'dst'. */
NetModel::EdgeHashNode* NetModel::lookupEdge(int src, int dst) const
{
	EdgeHashNode* h;
	for (h = hashtab_[ehash(src, dst)]; h != 0; h = h->next)
		if (h->src == src && h->dst == dst)
			break;
	return (h);
}

int NetModel::addAddress(int id, int addr) const
{
	int newEntry = 1;
	Tcl_HashEntry *he = 
		Tcl_CreateHashEntry(addrHash_, (const char *)addr, &newEntry);
	if (he == NULL)
		return -1;
	if (newEntry) {
		Tcl_SetHashValue(he, (ClientData)id);
	}
	return 0;
}

int NetModel::addr2id(int addr) const 
{
	Tcl_HashEntry *he = Tcl_FindHashEntry(addrHash_, (const char *)addr);
	if (he == NULL)
		return -1;
	return (int)Tcl_GetHashValue(he);
}

void NetModel::enterEdge(Edge* e)
{
	int src = e->src();
	int dst = e->dst();
	EdgeHashNode *h = lookupEdge(src, dst);
	if (h != 0) {
		/* XXX */
		fprintf(stderr, "nam: duplicate edge (%d,%d)\n", src, dst);
		//exit(1);
		return;
	}
	h = new EdgeHashNode;
	h->src = src;
	h->dst = dst;
	h->queue = 0;
	h->edge = e;
	int k = ehash(src, dst);
	h->next = hashtab_[k];
	hashtab_[k] = h;
}

void NetModel::removeEdge(Edge* e)
{
	int src = e->src();
	int dst = e->dst();
        EdgeHashNode *h = lookupEdge(src, dst);
        if (h == 0) {
                /* XXX */
                fprintf(stderr, "nam: trying to delete nonesisting edge (%d,%d)\n", src, dst);
                exit(1);
        }

	//XXX do we need to process queue ? leave it to the future 10/01/98

  	EdgeHashNode *f,*g;	
	int k = ehash(src, dst);

        for (f = hashtab_[k]; f != 0; f = f->next) {
                if (h->src == f->src && h->dst == f->dst) {
		    if (f == hashtab_[k]) {
		 	hashtab_[k] = f->next;
			break;
		    } else {
			g->next = f->next;			
			break;
		    } 
		}
		g = f;
	}
	
	delete h;

}

/* XXX Make this cheaper (i.e. cache it) */
void NetModel::BoundingBox(BBox& bb)
{
	/* ANSI C limits, from float.h */
	bb.xmin = bb.ymin = FLT_MAX;
	bb.xmax = bb.ymax = -FLT_MAX;

	for (Animation* a = drawables_; a != 0; a = a->next()) 
		a->merge(bb);
}


// Animation* NetModel::inside(double now, float px, float py) const
// {
// 	for (Animation* a = animations_; a != 0; a = a->next())
// 		if (a->inside(now, px, py))
// 			return (a);
// 	for (Animation* d = drawables_; d != 0; d = d->next())
// 		if (d->inside(now, px, py))
// 			return (d);

// 	return (0);
// }

// Used exclusively for start_info() in nam.tcl. It ignores all tag objects
// and therefore should *not* be used for editing.
Animation* NetModel::inside(float px, float py) const
{
	for (Animation* a = animations_; a != 0; a = a->next())
		if ((a->inside(px, py)) && (a->classid() != ClassTagID))
			return (a);
	for (Animation* d = drawables_; d != 0; d = d->next()) {
		if ((d->inside(px, py) && (d->classid() != ClassTagID)))
			return (d);
	}

	return (0);
}

int NetModel::add_monitor(Animation *a)
{
  Monitor *m = new Monitor(mon_count_, a, node_size_);
  m->next(monitors_);
  monitors_=m;
  return mon_count_++;
}

int NetModel::monitor(double now, int monitor, char *result, int len)
{
  /*XXX should get rid of this search*/
  for(Monitor *m=monitors_; m!=NULL; m=m->next()) {
    if (m->monitor_number()==monitor) {
      m->update(now, result, len);
      if (strlen(result)==0)
	delete_monitor(m);
      return 0;
    }
  }
  result[0]='\0';
  return -1;
}

void NetModel::check_monitors(Animation *a)
{
  /*A new animation just got created.  Check to see if we should already*/
  /*have a monitor on it*/

  MonState *ms=a->monitor_state();
  if (ms==NULL) return;
  for(Monitor *m=monitors_; m!=NULL; m=m->next()) {
    if ((m->mon_state_!=NULL)&&(m->mon_state_->type=ms->type)) {
      switch (ms->type) {
      case MON_PACKET:
	if (m->mon_state_->pkt.id==ms->pkt.id) {
	  m->anim(a);
	  delete ms;
	  return;
	}
	break;
      case MON_ROUTE: 
	if ((m->mon_state_->route.src==ms->route.src)&&
	    (m->mon_state_->route.group==ms->route.group)) {
	  m->anim(a);
	  delete ms;
          return;
	}
	break;
      }
    }
  }
}

void NetModel::delete_monitor(int monitor)
{
  /*this version of delete_monitor is called from the GUI*/
  /*given a monitor, remove it from the model's monitor list*/
  Monitor *tm1, *tm2;
  if (monitors_==NULL)
    return;
  tm1=monitors_;
  tm2=monitors_;
  while ((tm1!=NULL)&&(tm1->monitor_number()!=monitor))
    {
      tm2=tm1;
      tm1=tm1->next();
    }
  if (tm1!=NULL)
    {
      tm2->next(tm1->next());
      if (tm1==monitors_)
	monitors_=tm1->next();
      if (tm1->anim()!=NULL)
	tm1->anim()->remove_monitor();
      delete tm1;
    }
}

void NetModel::delete_monitor(Monitor *m)
{
  /*given a monitor, remove it from a node's agent list*/
  Monitor *tm1, *tm2;
  if (monitors_==NULL)
    return;
  tm1=monitors_;
  tm2=monitors_;
  while ((tm1!=m)&&(tm1!=NULL))
    {
      tm2=tm1;
      tm1=tm1->next();
    }
  if (tm1!=NULL)
    {
      tm2->next(tm1->next());
      if (tm1==monitors_)
	monitors_=tm1->next();
      tm1->anim()->remove_monitor();
      delete tm1;
    }
}

void NetModel::delete_monitor(Animation *a)
{
  /*given a monitor, remove it from a node's agent list*/
  /*this version gets called when an animation deletes itself*/
  /*XXX animations sometimes get deleted when the packet changed link*/
  Monitor *tm1, *tm2;
  if (monitors_==NULL)
    return;
  tm1=monitors_;
  tm2=monitors_;
  while ((tm1!=NULL)&&(tm1->anim()!=a))
    {
      tm2=tm1;
      tm1=tm1->next();
    }
  if (tm1!=NULL)
    {
      tm2->next(tm1->next());
      if (tm1==monitors_)
	monitors_=tm1->next();
      delete tm1;
    }
}

void NetModel::saveState(double tim)
{
	State* state = State::instance();
	StateInfo min;
	min.time = 10e6;
	min.offset = 0;
	Animation *a, *n;
	StateInfo si;

	/*
	 * Update the animation list first to remove any unnecessary
	 * objects in the list.
	 */
	for (a = animations_; a != 0; a = n) {
		n = a->next();
		a->update(tim);
	}
	for (a = animations_; a != 0; a = n) {
		n = a->next();
		si = a->stateInfo();
		if (si.time < min.time)
			min = si;
	}
	if (min.offset)
		state->set(tim, min);
}

/* Trace event handler. */
void NetModel::handle(const TraceEvent& e, double now, int direction)
{
  QueueItem *q;
  EdgeHashNode *ehn, *ehnrev;
  Edge* ep;
  Node *n;
  //Packet *p;
  Route *r;
  Agent *a;
  Feature *f;
  float x, y;
  int pno;
  double txtime;
  if (e.time > State::instance()->next())
    saveState(e.time);

  switch (e.tt) 
    {
    case 'v':	/* 'variable' -- just execute it as a tcl command */ {
	    if (nam_ == 0) {
		    fprintf(stderr, "Couldn't evaluate %s without animator\n",
			    e.image);
		    break;
	    }
	    const char *p = e.image + e.ve.str;
	    char *q = new char[strlen(nam_->name()) + strlen(p) + 2];
	    sprintf(q, "%s %s", nam_->name(), p);
	    Tcl::instance().eval(q);
	    delete []q;
	    break;
    }
		
    case 'h':
      if (direction==BACKWARDS) break;
      //ehn = lookupEdge(addr2id(e.pe.src), addr2id(e.pe.dst));
      ehn = lookupEdge(e.pe.src, e.pe.dst);
      if (ehn == 0 || (ep = ehn->edge) == 0)
	return;

      /*
       * If the current packet will arrive at its destination
       * at a later time, insert the arrival into the queue
       * of animations and set its paint id (id for X graphics
       * context.
       */
      txtime = ep->txtime(e.pe.pkt.size);
      if (e.time + txtime + ep->GetDelay() >= now) {
	/* XXX */
	Packet *p = new Packet(ep, e.pe.pkt, e.time, txtime,
			       e.offset);
	p->insert(&animations_);
	p->paint(paint_[e.pe.pkt.attr & 0xff]);
        check_monitors(p);
#ifdef DEBUG
	printf("packet %d sent from %d towards %d\n", e.pe.pkt.id, 
	       e.pe.src, e.pe.dst);
#endif
      }
      break;
    case 'a':
      n = lookupNode(e.ae.src);
      if (n == 0)
        return;
      a = n->find_agent((char *) e.ae.agent.name);
      if ((direction==FORWARDS)&&(e.ae.agent.expired==0)) {
	      if (a == NULL) {
		      // will only create an agent if there isn't one already 
		      // there with the same name
		      a = new BoxAgent(e.ae.agent.name, n->size());
		      a->insert(&animations_);
		      n->add_agent(a);
		      placeAgent(a, n);
	      }
      } else {
	if (a != NULL) {
		n->delete_agent(a);
		delete a;
	}
      }
      break;

    case 'f':
	    // We don't need any redraw for this, because it's always 
	    // displayed in monitors in a separate pane
      n = lookupNode(e.fe.src);
      if (n == 0)
        return;
      a = n->find_agent((char *)e.fe.feature.agent);
      if (a == 0)
	return;
      f = a->find_feature((char *)e.fe.feature.name);
      if (f == 0) {
	switch (e.fe.feature.type) {
	case 'v':
	  f = new VariableFeature(a, e.fe.feature.name);
	  break;
	case 'l':
	  f = new ListFeature(a, e.fe.feature.name);
	  break;
	case 's':
	case 'u':
	case 'd':
	  f = new TimerFeature(a, e.fe.feature.name);
	  break;
	}
	a->add_feature(f);
      }
      if (((direction==FORWARDS)&&(e.fe.feature.expired==0))||
	  ((direction==BACKWARDS)&&(strlen(e.fe.feature.oldvalue)>0))) {
	char *value;
	double time_set;
	if (direction==FORWARDS) {
	  value=(char *)e.fe.feature.value;
	  time_set=now;
	} else {
	  value=(char *)e.fe.feature.oldvalue;
	  /*XXX need an extra value here*/
	  time_set=0.0;
	}
	switch (e.fe.feature.type) {
	case 'v':
	case 'l':
	  f->set_feature(value);
	  break;
	case 's':
	  f->set_feature(atof(value), TIMER_STOPPED, time_set);
	  break;
	case 'u':
	  f->set_feature(atof(value), TIMER_UP, time_set);
	  break;
	case 'd':
	  f->set_feature(atof(value), TIMER_DOWN, time_set);
	  break;
	}
      } else {
	a->delete_feature(f);
	delete f;
      }
      break;

    case 'r':
      if (direction==FORWARDS) break;
      //ehn = lookupEdge(addr2id(e.pe.src), addr2id(e.pe.dst));
      ehn = lookupEdge(e.pe.src, e.pe.dst);
      if (ehn == 0 || (ep = ehn->edge) == 0)
	return;

      /*
       * If the current packet will arrive at its destination
       * at a later time, insert the arrival into the queue
       * of animations and set its paint id (id for X graphics
       * context.
       */
      txtime = ep->txtime(e.pe.pkt.size);
      if (e.time - (txtime + ep->GetDelay()) <= now) {
	/* XXX */
	Packet *p = new Packet(ep, e.pe.pkt, 
			       e.time-(ep->GetDelay()+txtime), txtime,
			       e.offset);
	p->insert(&animations_);
	p->paint(paint_[e.pe.pkt.attr & 0xff]);
        check_monitors(p);
      }
      break;

    case '+':
      ehn = lookupEdge(e.pe.src, e.pe.dst);
      if (direction==FORWARDS)
	{
	  if (ehn != 0 && ehn->queue != 0) {
	    QueueItem *qi = new QueueItem(e.pe.pkt, e.time,
					  e.offset);
	    qi->paint(paint_[e.pe.pkt.attr & 0xff]);
	    ehn->queue->enque(qi,QUEUE_TAIL);
	    qi->insert(&animations_);
	    check_monitors(qi);
	  }
	}
      else
	{
	  if (ehn != 0 && ehn->queue != 0) {
	    q = ehn->queue->remove(e.pe.pkt);
	    delete q;
	  }
	}
      break;
    case '-':
      ehn = lookupEdge(e.pe.src, e.pe.dst);
      if (direction==FORWARDS)
        {
	  if (ehn != 0 && ehn->queue != 0) {
	    q = ehn->queue->remove(e.pe.pkt);
	    delete q;
	  }
	}
      else
	{
	  if (ehn != 0 && ehn->queue != 0) {
	    QueueItem *qi = new QueueItem(e.pe.pkt, e.time,
					  e.offset);
	    qi->paint(paint_[e.pe.pkt.attr & 0xff]);
	    ehn->queue->enque(qi,QUEUE_HEAD);
	    qi->insert(&animations_);
	    check_monitors(qi);
	  }
	}
      break;

    case 'E': {
	    // Nothing for now
	    Group *grp = lookupGroup(e.pe.dst);
	    if (grp == NULL) {
		    fprintf(stderr, "Packet destination group %d not found\n",
			    e.pe.dst);
		    return;
	    }
	    int *mbr = new int[grp->size()];
	    grp->get_members(mbr);
	    for (int i = 0; i < grp->size(); i++) {
		    QueueItem *qi = new QueueItem(e.pe.pkt, e.time,
						  e.offset);
		    qi->paint(paint_[e.pe.pkt.attr & 0xff]);
		    n = lookupNode(mbr[i]);
		    if (n == 0) {
			    fprintf(stderr, 
				    "Group member %d not found\n",
				    mbr[i]);
			    return;
		    }
		    if (direction == FORWARDS) {
			    n->queue()->enque(qi, QUEUE_TAIL);
			    qi->insert(&animations_);
			    check_monitors(qi);
		    } else {
			    qi = n->queue()->remove(e.pe.pkt);
			    delete qi;
		    }
	    }
	    delete mbr;    
	    break;
    }

    case 'D': {
	    n = lookupNode(e.pe.dst);
	    if (n == NULL) {
		    fprintf(stderr, "Bad node id %d for session deque event\n",
			    e.pe.dst);
		    return;
	    }
	    if (direction == FORWARDS) {
		    q = n->queue()->remove(e.pe.pkt);
		    delete q;
	    } else {
		    QueueItem *qi = new QueueItem(e.pe.pkt, e.time, e.offset);
		    qi->paint(paint_[e.pe.pkt.attr & 0xff]);
		    n->queue()->enque(qi, QUEUE_HEAD);
		    qi->insert(&animations_);
		    check_monitors(qi);
	    }
	    break;
    }

    case 'P':	/* session drop */
      /* Get packet to drop. */
      if (direction==FORWARDS)
	{
#ifdef DEBUG
	  printf("drop on %d -> %d\n", e.pe.src, e.pe.dst);
#endif
	  n = lookupNode(e.pe.dst);
	  if (n == 0)
	    return;
	  q=0;
	  if (n->queue()!=0) {
		  /* Remove packet from session queue */
		  q = n->queue()->remove(e.pe.pkt);
		  if (q == 0) {
			  /* No such packet in queue??? */
			  fprintf(stderr, "No packet drop %id in queue\n",
				  e.pe.pkt.id);
			  return;
		  }
#ifdef DEBUG
		  printf("packet %d dropped from queue\n", e.pe.pkt.id);
#endif
		  q->position(x, y);
		  pno = q->paint();
		  delete q;
	  }
	  /*
	   * Compute the point at which the dropped packet disappears.
	   * Let's just make this sufficiently far below the lowest
	   * thing on the screen.
	   *
	   * Watch out for topologies that have all their nodes lined
	   * up horizontally. In this case, nymin_ == nymax_ == 0.
	   * Set the bottom to -0.028. This was chosen empirically.
	   * The nam display was set to the maximum size and the lowest
	   * position on the display was around -0.028.
	   */
	  float bot;
	  if (nymin_ == nymax_ && nymin_ == 0.)
	    bot = -0.03;
	  else
	    bot = nymin_ - (nymax_ - nymin_);
	  
	  Drop *d = new Drop(x, y, bot, n->size()*0.5, /* This is a hack */
			     e.time, e.offset, e.pe.pkt);
	  d->paint(pno);
	  d->insert(&animations_);
	  check_monitors(d);
	  break;
	} else {
	  /*direction is BACKWARDS - need to create the packet*/
	}

    case 'G': {
	    /* Group event */
	    Group *grp = lookupGroup(e.ge.src);
	    if (grp == NULL) {
		    grp = new Group(e.ge.grp.name, e.ge.src);
		    add_group(grp);
	    }
	    
	    if (e.ge.grp.flag == GROUP_EVENT_JOIN) {
		    grp->join(e.ge.grp.mbr);
		    // XXX
		    // Hard-coded queue angle for session queues. :(
		    // Create session queue here because they are not like
		    // traditional queues which are created when nam
		    // started. Group member may dynamically join/leave,
		    // so may session queues. We create them here because 
		    // there's a 1-1 relationship between join and creating
		    // session queues.
		    n = lookupNode(e.ge.grp.mbr);
		    if (n == NULL) {
			    fprintf(stderr, "Bad node %d for group event\n",
				    e.ge.grp.mbr);
			    return;
		    }
		    // Need more consideration on the placement of these queues
		    Queue *q = new Queue(0.5);
		    q->next_ = queues_;
		    queues_ = q;
		    n->add_sess_queue(e.ge.src, q);
	    } else if (e.ge.grp.flag == GROUP_EVENT_LEAVE)
		    // No deletion of session queues.
		    grp->leave(e.ge.grp.mbr);
	    break;
    }
    case 'l':
      /*link event*/
      ehn = lookupEdge(e.le.src, e.le.dst);
      ehnrev = lookupEdge(e.le.dst, e.le.src);
      /*note: many link events are bidirectional events so be careful to
        apply them to both a link and the reverse of it*/
      if (ehn == 0)
	return;

      if (direction==FORWARDS)	{
        // Always save the color before the last DOWN event
	if (strncmp(e.le.link.state, "DOWN", 4)==0) {
	  ehn->edge->set_down("red");
	  ehnrev->edge->set_down("red");
	} else if (strncmp(e.le.link.state, "UP", 2)==0) {
	  /* XXX Assume an UP event always follows a DOWN event */
	  ehn->edge->set_up();
	  ehnrev->edge->set_up();
	} else if (strncmp(e.le.link.state, "COLOR", 5) == 0) {
		ehn->edge->color((char *)e.le.link.color);
		ehnrev->edge->color((char *)e.le.link.color);
	}
      } else {
	if (strncmp(e.le.link.state, "UP", 2)==0) {
	  ehn->edge->set_down("red");
	  ehnrev->edge->set_down("red");
	} else if (strncmp(e.le.link.state, "DOWN", 4)==0) {
	  ehn->edge->set_up();
	} else if (strncmp(e.le.link.state, "COLOR", 5) == 0) {
		ehn->edge->color((char *)e.le.link.oldColor);
		ehnrev->edge->color((char *)e.le.link.oldColor);
	}
      }
      break;
    case 'n':
      /*node event*/
      n = lookupNode(e.ne.src);
      if (n == 0)
	return;
      if (direction==FORWARDS) {
	if (strncmp(e.ne.node.state, "DOWN", 4)==0) {
	  n->set_down("gray");
	} else if (strncmp(e.ne.node.state, "UP", 2)==0) {
	  n->set_up();
	} else if (strncmp(e.ne.node.state, "COLOR", 5) == 0) {
	      // normal color can be defined by user
	      // XXX: why color is opposite when direction is opposite?
	      n->color((char *)e.ne.node.color);
	} else if (strncmp(e.ne.node.state, "DLABEL", 6) == 0) {
		n->dlabel((char *)e.ne.node.dlabel);
	}
      } else {
	if (strncmp(e.ne.node.state, "UP", 4)==0) {
	  n->set_down("gray");
	} else if (strncmp(e.ne.node.state, "DOWN", 2)==0) {
	  n->set_up();
	} else if (strncmp(e.ne.node.state, "COLOR", 5) == 0) {
	      n->color((char *)e.ne.node.oldColor);
	}  else if (strncmp(e.ne.node.state, "DLABEL", 6) == 0) {
		n->dlabel((char *)e.ne.node.odlabel);
	}
      }	
      break;

    case 'm':
	    /* node mark event */
	    NodeMark *cm;
	    n = lookupNode(e.me.src);
	    if (n == 0)
		    return;
	    cm = n->find_mark((char *) e.me.mark.name);
	    if (direction == FORWARDS){
		    if (e.me.mark.expired == 0) {
			    /* once created, a node mark cannot be changed*/
			    if (cm == NULL) 
				    n->add_mark((char *)e.me.mark.name, 
						(char *)e.me.mark.color,
						(char *)e.me.mark.shape);
		    } else 
			    /* expired */
			    n->delete_mark((char *)e.me.mark.name);
	    } else {
		    /* 
		     * backward: 
		     * (1) create it if expired == 1
		     * (2) delete it if expired == 0
		     */
		    if (e.me.mark.expired == 0) 
			    n->delete_mark((char *)e.me.mark.name);
		    else {
			    /* re-create the circle */
			    if (cm == NULL)
				    n->add_mark((char *)e.me.mark.name, 
						(char *)e.me.mark.color,
						(char *)e.me.mark.shape);
		    }
	    }
	    break;

    case 'R':
      /*route event*/
      if (((e.re.route.expired==0)&&(direction==FORWARDS))||
	  ((e.re.route.expired==1)&&(direction==BACKWARDS)))
	{
	  /*this is a new route*/
	  n = lookupNode(e.re.src);
	  if (n == 0)
	    return;
	  // XXX: What's these src and dst??? Are they id or address? */
	  ehn = lookupEdge(e.re.src, e.re.dst);
	  if (ehn == 0)
	    return;
	  int oif=1;
	  if (strncmp(e.re.route.mode, "iif", 3)==0)
	    oif=0;
	  r = new Route(n, ehn->edge, e.re.route.group, e.re.route.pktsrc, 
			e.re.route.neg, oif, e.re.route.timeout, now);
	  n->add_route(r);
	  placeRoute(r, n);
	  r->insert(&animations_);
	  r->paint(paint_[e.re.route.group & 0xff]);
	  check_monitors(r);
	}
      else
	{
	  /*an old route expired*/
	  n = lookupNode(e.re.src);
          if (n == 0)
            return;
	  // XXX: What's these src and dst??? Are they id or address? */
	  ehn = lookupEdge(e.re.src, e.re.dst);
          if (ehn == 0)
            return;
	  int oif=1;
	  if (strncmp(e.re.route.mode, "iif", 3)==0)
	    oif=0;
	  r = n->find_route(ehn->edge, e.re.route.group, e.re.route.pktsrc,
			    oif);
	  if (r==0)
	    {
	      fprintf(stderr, "nam: attempt to delete non-existent route\n");
	      abort();
	    }
	  n->delete_route(r);
	  delete r;
	}
      break;
    case 'd':
      add_drop(e, now, direction);
    }
}

void NetModel::add_drop(const TraceEvent &e, double now, int direction)
{
  EdgeHashNode *ehn;
  QueueItem *q;
  Packet *p;
  float x, y;
  int pno;
  
  /* Get packet to drop. */
  if (direction==FORWARDS)
    {
#ifdef DEBUG
      printf("drop on %d -> %d\n", e.pe.src, e.pe.dst);
#endif
      ehn = lookupEdge(e.pe.src, e.pe.dst);
      if (ehn == 0)
	return;
      q=0;
      if (ehn->queue!=0)
	{
	  /*if there's a queue, try removing it from the queue first*/
	  /*queue drops are more common than link drops...*/
	  q = ehn->queue->remove(e.pe.pkt);
	}
      if (q == 0) {
	/*perhaps it's a link packet drop*/
	p=lookupPacket(e.pe.src, e.pe.dst, e.pe.pkt.id);
	if (p!=NULL)
	  {
	    /*it was a link packet drop*/
#ifdef DEBUG
	    printf("packet %d dropped on link %d->%d\n", e.pe.pkt.id, e.pe.src, e.pe.dst);
#endif
	    p->position(x, y, now);
	    ehn->edge->DeletePacket(p);
	    pno = p->paint();
	    delete p;
	  }
	else 
	  {
	    Lan *lan = lookupLan(e.pe.src);
	    if (lan != NULL) {
	      /* 
	       * If it's a Lan (selective) packet drop, which means
	       * the packet is only dropped on some of the lan links,
	       * register the packet on the lan and drop it when the
	       * packet is actually transmitted to that lan link.
	       *
	       * When the packet is actually dropped, this function will be 
	       * called again, but at that time the packet will be actually
	       * on the link and this code will not be executed.
	       */
	      lan->register_drop(e);
	      printf("packet %d will be dropped on lan link %d->%d\n",
		     e.pe.pkt.id, e.pe.src, e.pe.dst);
	      return;
	    }

	    /*probably it was a queue drop, but we're not displaying */
	    /*that queue*/
	    return;
	  }
      } else {
#ifdef DEBUG
	printf("packet %d dropped from queue\n", e.pe.pkt.id);
#endif
	q->position(x, y);
	pno = q->paint();
	delete q;
      }
      /*
       * Compute the point at which the dropped packet disappears.
       * Let's just make this sufficiently far below the lowest
       * thing on the screen.
       *
       * Watch out for topologies that have all their nodes lined
       * up horizontally. In this case, nymin_ == nymax_ == 0.
       * Set the bottom to -0.028. This was chosen empirically.
       * The nam display was set to the maximum size and the lowest
       * position on the display was around -0.028.
       */
      float bot;
      if (nymin_ == nymax_ && nymin_ == 0.)
	bot = -0.03;
      else
	bot = nymin_ - (nymax_ - nymin_);
	  
      Drop *d = new Drop(x, y, bot, ehn->edge->PacketHeight(),
			 e.time, e.offset, e.pe.pkt);
      d->paint(pno);
      d->insert(&animations_);
      check_monitors(d);
      return;
    } else {
      /*direction is BACKWARDS - need to create the packet*/
      Lan *lan = lookupLan(e.pe.src);
      if (lan != NULL) {
	/* 
	 * We need to remove drop status in lans
	 */
	lan->remove_drop(e);
	printf("lan dropped packet %d is removed on lan link %d->%d\n",
	       e.pe.pkt.id, e.pe.src, e.pe.dst);
	return;
      }
    }
}

void NetModel::addView(NetView* p)
{
	p->next_ = views_;
	views_ = p;
}

Node *NetModel::lookupNode(int nn) const
{
	for (Node* n = nodes_; n != 0; n = n->next_)
		if (n->num() == nn)
			return (n);
	/* XXX Do not exit, we need normal return. */
	//fprintf(stderr, "nam: no such node %d\n", nn);
	//exit(1);
	return (0);// make visual c++ happy
}

void NetModel::removeNode(Node *n) 
{
	Node *p, *q;
	// Remove node n from nodes_ list, then delete it
	for (p = nodes_; p != 0; q = p, p = p->next_)
		if (p == n)
			break;
	if (p == nodes_)
		nodes_ = p->next_;
	else
		q->next_ = p->next_;
	delete p;
}

Lan *NetModel::lookupLan(int nn) const
{
	for (Lan* l = lans_; l != 0; l = l->next_)
		if (l->num() == nn)
			return (l);
	/* XXX */
	//fprintf(stderr, "nam: no such lan %d\n", nn);
	//exit(1);
	return (0);// make visual c++ happy
}

Packet *NetModel::newPacket(PacketAttr &pkt, Edge *e, double time)
{
  /*this is called when we get a triggered event such as a packet
    getting duplicated within a LAN*/
  Packet *p = new Packet(e, pkt, time,
			  e->txtime(pkt.size), 0);
  p->insert(&animations_);
  p->paint(paint_[pkt.attr & 0xff]);
  check_monitors(p);
  return p;
}

Packet *NetModel::lookupPacket(int src, int dst, int id) const
{
  EdgeHashNode *h = lookupEdge(src, dst);
  if (h == 0)
    return NULL;
  int ctr=0;
  for (Packet *p=h->edge->packets(); p!=NULL; p=p->next())
    {
#define PARANOID
#ifdef PARANOID
      ctr++;
      if (ctr>h->edge->no_of_packets()) abort();
#endif
      if (p->id() == id)
	return p;
    }
  /*have to fail silent or we can't cope with link drops when doing settime*/
  return 0;
}

/* Do not delete groups, because they are not explicitly deleted */
int NetModel::add_group(Group *grp)
{
	int newEntry = 1;
	Tcl_HashEntry *he = 
		Tcl_CreateHashEntry(grpHash_, (const char *)grp->addr(), 
				    &newEntry);
	if (he == NULL)
		return -1;
	if (newEntry) {
		Tcl_SetHashValue(he, (ClientData)grp);
		nGroup_++;
	}
	return 0;
}

Group* NetModel::lookupGroup(unsigned int addr)
{
	Tcl_HashEntry *he = Tcl_FindHashEntry(grpHash_, (const char *)addr);
	if (he == NULL)
		return NULL;
	return (Group *)Tcl_GetHashValue(he);
}

// Remove a view from the views link, but not delete it
void NetModel::remove_view(View *v)
{
	View *p, *q;
	p = q = views_;
	if (p == v) {
		views_ = p->next_;
		return;
	}
	while (p != NULL) {
		q = p;
		p = p->next_;
		if (p == v) {
			q->next_ = p->next_;
			return;
		}
	}
}

int NetModel::command(int argc, const char *const *argv)
{
	Tcl& tcl = Tcl::instance();
	
	if (argc == 2) {
		if (strcmp(argv[1], "layout") == 0) {
			/*
			 * <net> layout
			 * Compute reasonable defaults for missing node or edge
			 * sizes based on the maximum link delay. Lay out the
			 * nodes and edges as specified in the layout file.
			 */
			scale_estimate();
			layout();
			return (TCL_OK);
		}
		if (strcmp(argv[1], "showtrees") == 0) {
			/*
			 * <net> showtrees
			 */
			color_subtrees();
			for (View* p = views_; p != 0; p = p->next_) {
			  p->draw();
			}
			return (TCL_OK);
		}
	} else if (argc == 3) {
		if (strcmp(argv[1], "view") == 0) {
			/*
			 * <net> view <viewName>
			 * Create the window for the network layout/topology.
			 */
			NetView *v = new NetView(argv[2], this);
			v->next_ = views_;
			views_ = v;
			return (TCL_OK);
		}
		if (strcmp(argv[1], "psview") == 0) {
			/*
			 * <net> psview <fileName>
			 * Print the topology to a file
			 */
			PSView *v = new PSView(argv[2], this);
			v->render();
			delete(v);
			return (TCL_OK);
		}
		if (strcmp(argv[1], "editview") == 0) {
			/*
			 * <net> editview <viewname>
			 */
			EditView *v = new EditView(argv[2], this);
			// XXX should we insert it into views_???
			v->next_ = views_;
			views_ = v;
			return (TCL_OK);
		}
		if (strcmp(argv[1],"savelayout")==0) {
		        save_layout(argv[2]);
			return (TCL_OK);
		}
	} else if (argc >= 4 && strcmp(argv[1], "node") == 0) {
		/*
		 * <net> node <name> <shape> <color> <addr> [<size>]
		 * Create a node using the specified name
		 * and the default size and insert it into this
		 * NetModel's list of drawables.
		 */
		double size = defsize;
		if (argc > 6)
			size = atof(argv[6]);
		/* 
		 * Check whether this node is already there. If so, 
		 * Delete it first
		 */
		Node *n = lookupNode(atoi(argv[2]));
		if (n != NULL) {
			fprintf(stderr, "duplicate node %s.\n", argv[2]);
			removeNode(n);
		}
		if (!strcmp(argv[3], "circle"))
			n = new CircleNode(argv[2], size);
		else if (!strcmp(argv[3], "box"))
			n = new BoxNode(argv[2], size);
		else
			n = new HexagonNode(argv[2], size);
		int addr = Address::instance().str2addr((char *)argv[5]);
		n->setaddr(addr);
		addAddress(n->num(), addr);
		n->init_color((char *)argv[4]);
		if (argc > 7) {
			n->place(atof(argv[7]),atof(argv[8]));
			if (size != defsize) {
			    n->size(size);
			} else {
			    n->size(0.15);
			}
		}
		n->next_ = nodes_;
		nodes_ = n;
		n->insert(&drawables_);
		return (TCL_OK);
	} else if (argc == 4) {
		if (strcmp(argv[1], "new_monitor_agent") == 0) {
			/* 
			 * <net> new_monitor_agent <node_id> <agent_name>
			 */
			int node = atoi(argv[2]);
			Node *n = lookupNode(node);
			if (n == 0) {
				tcl.resultf("Node %d doesn't exist.", node);
				return (TCL_ERROR);
			}
			Agent *a = n->find_agent((char *)argv[3]);
			if (a == 0) {
				tcl.resultf("Agent %s not exist at node %d",
					    argv[3], node);
				return (TCL_ERROR);
			}
			tcl.resultf("%d", add_monitor(a));
			return (TCL_OK);
		}
		if (strcmp(argv[1], "color") == 0) {
			/*
			 * <net> color <packetClass> <colorName>
			 * Display packets of the specified class using
			 * the specified color.
			 */
			int c = atoi(argv[2]);
			if ((u_int)c > 1024) {
				tcl.resultf("%s color: class %d out of range",
					    argv[0], c);
				return (TCL_ERROR);
			}
			Paint *paint = Paint::instance();
			if (c > nclass_) {
				int n, i;
				for (n = nclass_; n < c; n <<= 1);
				int *p = new int[n];
				for (i = 0; i < nclass_; ++i)
					p[i] = paint_[i];
				delete paint_;
				paint_ = p;
				nclass_ = n;
				int pno = paint->thin();
				for (; i < n; ++i)
					paint_[i] = pno;
			}
			int pno = paint->lookup(argv[3], 1);
			if (pno < 0) {
			  fprintf(stderr,"%s color: no such color: %s\n",
				  argv[0], argv[3]);
			  pno = paint->lookup("black",1);
			  if (pno < 0) {
			    tcl.resultf("%s no black! - bailing");
			    return (TCL_ERROR);
			  }
			}
			paint_[c] = pno;
			return (TCL_OK);
		}
		if (strcmp(argv[1], "ncolor") == 0) {
			/*
			 * <net> ncolor <node> <colorName>
			 * set color of node to the specified color.
			 */
			Paint *paint = Paint::instance();
			Node *n = lookupNode(atoi(argv[2]));
			if (n == NULL) {
				fprintf(stderr, "No such node %s\n", argv[2]);
				exit(1);
			}
			int pno = paint->lookup(argv[3], 3);
			if (pno < 0) {
			  fprintf(stderr,"%s ncolor: no such color: %s\n",
				  argv[0], argv[3]);
			  int pno = paint->lookup("black",1);
			  if (pno < 0) {
			    tcl.resultf("%s no black! - bailing");
			    return (TCL_ERROR);
			  }
			}
			n->paint(pno);
			return (TCL_OK);
		}
	} else if (argc == 5) {
		if (strcmp(argv[1], "lookupColorName") == 0) {
			/*
			 * <net> lookupColorName red green blue
			 * get color name from its rgb value 
			 */   
			 Paint *paint = Paint::instance();
			 int r = atoi(argv[2]);
			 int g = atoi(argv[3]);
			 int b = atoi(argv[4]);
			 tcl.resultf("%s",paint->lookupName(r,g,b));
			 return(TCL_OK);
		}
		if (strcmp(argv[1], "queue") == 0) {
			/*
			 * <net> queue <src> <dst> <angle>
			 * Create a queue for the edge from 'src' to 'dst'.
			 * Add it to this NetModel's queue list.
			 * Display the queue at the specified angle from
			 * the edge to which it belongs.
			 */
			int src = atoi(argv[2]);
			int dst = atoi(argv[3]);
			EdgeHashNode *h = lookupEdge(src, dst);
			if (h == 0) {
				tcl.resultf("%s queue: no such edge (%d,%d)",
					    argv[0], src, dst);
				return (TCL_ERROR);
			}
			/* XXX can we assume no duplicate queues? */
			double angle = atof(argv[4]);
			Edge *e = h->edge;
			angle += e->angle();
			Queue *q = new Queue(angle);
			h->queue = q;
			q->next_ = queues_;
			queues_ = q;
			return (TCL_OK);
		}
		if (strcmp(argv[1], "ecolor") == 0) {
			/*
			 * <net> ecolor <src> <dst> <colorName>
			 * set color of edge to the specified color.
			 */
			Paint *paint = Paint::instance();
			EdgeHashNode* h = lookupEdge(atoi(argv[2]), atoi(argv[3]));
			if (h == 0) {
				tcl.resultf("%s ecolor: no such edge (%s,%s)",
					    argv[0], argv[2], argv[3]);
				return (TCL_ERROR);
			}
			int pno = paint->lookup(argv[4], 3);
			if (pno < 0) {
			  fprintf(stderr,"%s ncolor: no such color: %s\n",
				  argv[0], argv[3]);
			  pno = paint->lookup("black",1);
			  if (pno < 0) {
			    tcl.resultf("%s no black! - bailing");
			    return (TCL_ERROR);
			  }
			}
			h->edge->paint(pno);
			return (TCL_OK);
		}
		if (strcmp(argv[1], "lanlink") == 0) {
			/*
			 * <net> link <src> <lan> <angle>
			 * Create a link/edge between the specified source
			 * and a lan.
			 */
			Node *src = lookupNode(atoi(argv[2]));
			Lan *lan = lookupLan(atoi(argv[3]));
			if (lan == NULL) {
				fprintf(stderr, "no such lan %s", argv[3]);
				exit(1);
			}
			double angle = atof(argv[4]);
			double bw=lan->bw();
			double delay=(lan->delay())/2.0;
			Edge *e1 = new Edge(src, lan->virtual_node(), defsize,
					    bw, delay, 0, angle+1);
			e1->init_color("black");
			enterEdge(e1);
			e1->insert(&drawables_);
			src->add_link(e1);

			Edge *e2 = new Edge(lan->virtual_node(), src, defsize,
					   bw, delay, 0, angle);
			e2->init_color("black");
			enterEdge(e2);
			e2->insert(&drawables_);
			lan->add_link(e2);
			return (TCL_OK);
		}
	} else if (argc == 6) {
		if (strcmp(argv[1], "lan") == 0) {
			/*
			 * <net> lan <name> <bandwidth> <delay> <angle>
			 * Create a link/edge between the specified source
			 * and destination. Add it to this NetModel's list
			 * of drawables and to the source's list of links.
			 */
			double bw = atof(argv[3]);
			double delay = atof(argv[4]);
			double angle = atof(argv[5]);
			Lan *l = new Lan(argv[2], this, defsize, bw, 
					 delay, angle);
			l->next_=lans_;
			lans_=l;
			l->insert(&drawables_);
			Node *n=l->virtual_node();
			n->next_ = nodes_;
			nodes_ = n;
			return (TCL_OK);
		}
	} else if (argc == 8) {
		if (strcmp(argv[1], "link") == 0) {
			/*
			 * <net> link <src> <dst> <bandwidth> <delay> <angle>
			 * Create a link/edge between the specified source
			 * and destination. Add it to this NetModel's list
			 * of drawables and to the source's list of links.
			 */
			Node *src = lookupNode(atoi(argv[2]));
			Node *dst = lookupNode(atoi(argv[3]));
			double bw = atof(argv[4]);
			double delay = atof(argv[5]);
			double length = atof(argv[6]);
			double angle = atof(argv[7]);
			Edge *e = new Edge(src, dst, defsize,
					   bw, delay, length, angle);
			e->init_color("black");
			enterEdge(e);
			e->insert(&drawables_);
			src->add_link(e);
			tcl.resultf("%g", delay);
			return (TCL_OK);
		}
	}
	/* If no NetModel commands matched, try the Object commands. */
	return (TclObject::command(argc, argv));
}

// Place edges by their angles
void NetModel::placeEdgeByAngle(Edge* e, Node* src) const
{
	if (e->marked() == 0) {
		double nsin, ncos;
		Node *dst = e->neighbor();
		SINCOSPI(e->angle(), &nsin, &ncos);
		double x0 = src->x(e) + src->size() * ncos * .75;
		double y0 = src->y(e) + src->size() * nsin * .75;
		double x1 = dst->x(e) - dst->size() * ncos * .75;
		double y1 = dst->y(e) - dst->size() * nsin * .75;
		e->place(x0, y0, x1, y1);
		
		/* Place the queue here too.  */
		EdgeHashNode *h = lookupEdge(e->src(), e->dst());
		if (h->queue != 0)
			h->queue->place(e->size(), e->x0(), e->y0());

		e->mark();
	}
}

void NetModel::placeEdge(Edge* e, Node* src) const
{
	if (e->marked() == 0) {
		double hyp, dx, dy;
		Node *dst = e->neighbor();
		dx=dst->x(e)-src->x(e);
		dy=dst->y(e)-src->y(e);
		hyp=sqrt(dx*dx + dy*dy);
		e->setAngle(atan2(dy,dx));
		double x0 = src->x(e) + src->size() * (dx/hyp) * .75;
		double y0 = src->y(e) + src->size() * (dy/hyp) * .75;
		double x1 = dst->x(e) - dst->size() * (dx/hyp) * .75;
		double y1 = dst->y(e) - dst->size() * (dy/hyp) * .75;
		e->place(x0, y0, x1, y1);
		
		/* Place the queue here too.  */
		EdgeHashNode *h = lookupEdge(e->src(), e->dst());
		if (h->queue != 0)
			h->queue->place(e->size(), e->x0(), e->y0());

		e->mark();
	}
}

void NetModel::placeRoute(Route* r, Node* src) const
{
	if (r->marked() == 0) {
		double nsin, ncos;
		//Unused: Node *dst = r->edge()->neighbor();
		SINCOSPI(r->angle(), &nsin, &ncos);
		double x0 = src->x() + src->size() * ncos * .75;
		double y0 = src->y() + src->size() * nsin * .75;
		r->place(x0, y0);
		r->mark(1);
	}
}

void NetModel::placeAgent(Agent *a, Node *src) const
{
	double x0, y0, nsin, ncos;
	if (a->marked() == 0) {
		if (a->angle()==NO_ANGLE) {
			if (a->edge()==NULL) {
			  /* determine where to put the label so it
			     won't overlap a link (if possible) */
			        int choices[8], i, ix;
				for(i=0;i<8;i++)
				  choices[i]=1;
				Agent *agents = src->agents();
                                while (agents!=NULL) {
				  ix=int(agents->angle()*4);
				  choices[ix]=0;
				  agents=agents->next_;
				}
				Edge *links = src->links();
                                while (links!=NULL) {
				  ix=int(links->angle()*4);
				  choices[ix]=0;
				  links=links->next_;
				}
				if (choices[0]==1)
				  a->angle(0);
				else if (choices[4]==1)
				  a->angle(1.0);
				else if ((choices[1]==1)&&(choices[2]==1))
				  a->angle(0.5);
				else if ((choices[6]==1)&&(choices[7]==1))
				  a->angle(1.5);
				else if (choices[1]==1)
				  a->angle(0.25);
				else
				  a->angle(1.75);
			} else {
				a->angle(a->edge()->angle()+0.25);
			}
		}
		SINCOSPI(a->angle(), &nsin, &ncos);
		x0 = src->x() + src->size() * ncos * .75;
		y0 = src->y() + src->size() * nsin * .75;
		a->place(x0, y0);
		a->mark(1);
	}
}

/*
 * Compute reasonable defaults for missing node or edge sizes
 * based on the maximum link delay.
 */
void NetModel::scale_estimate()
{
	/* Determine the maximum link delay. */
	// XXX Must use length(). This is essential for appropriate 
	// packet height when using a pre-made layout.
	double emax = 0.;
	Node *n;
	for (n = nodes_; n != 0; n = n->next_) {
		for (Edge* e = n->links(); e != 0; e = e->next_)
			if (e->length() > emax)
				emax = e->length();
	}
	
	/*
	 * Check for missing node or edge sizes. If any are found,
	 * compute a reasonable default based on the maximum edge
	 * dimension.
	 */
	for (n = nodes_; n != 0; n = n->next_) {
		if (n->size() <= 0.)
			n->size(.1 * emax);
		for (Edge* e = n->links(); e != 0; e = e->next_)
			if (e->size() <= 0.)
				e->size(.03 * emax);
	}
	/*store this because we need it for monitors*/
	node_size_=.1 * emax;
}

void NetModel::layout()
{
	/* If there is no fixed node, anchor the first one entered at (0,0). */
	Node *n;

	for (n = nodes_; n != 0; n = n->next_)
		n->mark(0);
	if (nodes_)
		nodes_->place(0., 0.);

	int did_something;
	do {
		did_something = 0;
		for (n = nodes_; n != 0; n = n->next_)
			did_something |= traverse(n);
	} while (did_something);

	for (n = nodes_; n != 0; n = n->next_)
		for (Edge* e = n->links(); e != 0; e = e->next_)
			placeEdgeByAngle(e, n);
}

void NetModel::move(double& x, double& y, double angle, double d) const
{
	double ncos, nsin;
	SINCOSPI(angle, &nsin, &ncos);
	x += d * ncos;
	y += d * nsin;
}

/*
 * Traverse node n's neighbors and place them based on the
 * delay of their links to n.  The two branches of the if..else
 * are to handle unidirectional links -- we place ourselves if
 * we haven't been placed & our downstream neighbor has.
 */

int NetModel::traverse(Node* n)
{
	int did_something = 0;
	for (Edge* e = n->links(); e != 0; e = e->next_) {
		Node *neighbor = e->neighbor();
		double d = e->length() + (n->size() + neighbor->size()) * 0.75;
		if (n->marked() && !neighbor->marked()) {
			double x = n->x(e);
			double y = n->y(e);
			move(x, y, e->angle(), d);
			neighbor->place(x, y);
			did_something |= traverse(neighbor);
			if (nymax_ < y)
				nymax_ = y;
			if (nymin_ > y)
				nymin_ = y;
		} else if (!n->marked() && neighbor->marked()) {
			double x = neighbor->x(e);
			double y = neighbor->y(e);
			move(x, y, e->angle(), -d);
			n->place(x, y);
			did_something = 1;
		}
	}
	return (did_something);
}

int NetModel::save_layout(const char *filename)
{
  FILE *file;
  Node *n;
  Edge *e;
  int ret;
  file=fopen(filename, "w");
  if (file==0)
  {
    fprintf(stderr, "nam: Couldn't open file: %s\n", filename);
    return -1;
  }
  for (n = nodes_; n != 0; n = n->next_)
  {
    ret = n->save(file);
    if (ret!=0) {
      fclose(file);
      return -1;
    }
  }
  for (n = nodes_; n != 0; n = n->next_)
    for(e= n->links(); e !=0; e = e->next_)
    {
      ret = e->save(file);
      if (ret!=0) {
	fclose(file);
	return -1;
      }
    }
  return(fclose(file));
}

void NetModel::color_subtrees()
{
  Node *n, *dst, *prevdst, *newdst;
  Edge* e;
  for (n = nodes_; n != 0; n = n->next_) {
    int ctr=0;
    for (e = n->links(); e != 0; e = e->next_)
      ctr++;
    if (ctr==1)
    {
      n->color("grey");
      n->links()->color("grey");
      dst=n->links()->neighbor();
      for (e = dst->links(); e != 0; e = e->next_)
	if (e->neighbor()==n)
	{
	  e->color("grey");
	  break;
	}
      ctr=2;
      prevdst=n;
      while(ctr==2) {
	ctr=0;
	for (e = dst->links(); e != 0; e = e->next_)
	  ctr++;
	if (ctr==2) {
	  dst->color("grey");
	  dst->links()->color("grey");
	  for (e = dst->links(); e != 0; e = e->next_)
	    if (e->neighbor()!=prevdst)
	    {
	      newdst=e->neighbor();
	      break;
	    }
	  e->color("grey");
	  for (e = newdst->links(); e != 0; e = e->next_)
	    if (e->neighbor()==dst)
	    {
	      e->color("grey");
	      break;
	    }
	  prevdst=dst;
	  dst=newdst;
	}
      }
    }
  }
}


int NetModel::add_tag(Tag *tag)
{
	int newEntry = 1;
	Tcl_HashEntry *he = 
		Tcl_CreateHashEntry(tagHash_, (const char *)tag->name(), 
				    &newEntry);
	if (he == NULL)
		return (TCL_ERROR);
	if (newEntry) {
		Tcl_SetHashValue(he, (ClientData)tag);
		nTag_++;
	}
	tag->insert(&drawables_);
	return (TCL_OK);
}

// Remove a tag from netmodel, and clear all its memberships but not 
// actually delete it
void NetModel::delete_tag(const char *tn)
{
	Tcl_HashEntry *he = 
		Tcl_FindHashEntry(tagHash_, (const char *)tn);
	if (he != NULL) {
		// Do *NOT* delete tag here.
		Tcl_DeleteHashEntry(he);
		nTag_--;
	}
				  
}

Tag* NetModel::lookupTag(const char *tn)
{
	Tcl_HashEntry *he = Tcl_FindHashEntry(tagHash_, tn);
	if (he == NULL)
		return NULL;
	return (Tag *)Tcl_GetHashValue(he);
}

int NetModel::registerObjName(const char *name, int id)
{
	int newEntry = 1;
	Tcl_HashEntry *he = 
		Tcl_CreateHashEntry(objnameHash_, name, &newEntry);
	if (he == NULL)
		return (TCL_ERROR);
	if (newEntry) {
		Tcl_SetHashValue(he, (ClientData)id);
		nTag_++;
	}
	return (TCL_OK);
}

//XXX: maximum name length is 256. 
int NetModel::lookupObjname(const char *name)
{
#define STATIC_NAMELEN 256
	char n[STATIC_NAMELEN];
	size_t len = strlen(name);
	len = (len < STATIC_NAMELEN) ? len : STATIC_NAMELEN;
	for (size_t i = 0; i < len; i++)
		n[i] = toupper(name[i]);

	Tcl_HashEntry *he = Tcl_FindHashEntry(objnameHash_, n);
	if (he == NULL)
		return -1;
	return (int)Tcl_GetHashValue(he);
#undef STATIC_NAMELEN
}

/*
 * Unite tag name space and animation id space:
 * 
 * (1) if tag is a number, we'll first look it up in our 
 *     animation objects table. 
 * (2) if tag is a string, it must be in the tag table
 */
Animation* NetModel::lookupTagOrID(const char *name)
{
	char end[256];
	if (name == NULL)
		return NULL;
	unsigned int id = (unsigned int)strtoul(name, (char **)&end, 0);
	if (*end != 0)
		// This must be a tag string name
		return lookupTag(name);
	else 
		return Animation::find(id);
}

/*
 * Handling Tcl command "addtags" of EditView
 *
 * <view> addtag tag <searchSpec> arg...
 *
 * searchSpec can be: 
 *   all <ObjectType>
 *   closest x y ?halo? 
 *   enclosed x1 y1 x2 y2
 *   overlapping x1 y1 x2 y2
 *   withTag tagOrID
 * specification copied from Tk's canvas
 *
 * If newTag isn't null, then first lookup the tag. If found, add
 * the searchSpec results to that tag, otherwise create a new tag
 * and add results to that tag.
 * Return error if newTag == NULL.
 *
 * Tagging policies, e.g., whether an object can simultaneously belong 
 * to multiple tags, are left to the users.
 */
int NetModel::tagCmd(View *v, int argc, char **argv, 
		     char *newTag, char *cmdName)
{
	Tag *tag = NULL;
	size_t length;
	Tcl& tcl = Tcl::instance();
	int res = (TCL_OK), bNew = 0;

	if (newTag != NULL) {
		tag = (Tag *)lookupTagOrID(newTag);
		if ((tag != NULL) && (tag->classid() != ClassTagID)) {
			Tcl_AppendResult(tcl.interp(),
					 newTag, "should be a tag ",
					 (char *)NULL);
			return TCL_ERROR;
		}
		if (tag == NULL) {
			tag = new Tag(newTag);
			bNew = 1;
		}
	}

	int c = argv[0][0];
	length = strlen(argv[0]);

	if ((c == 'a') && (strncmp(argv[0], "all", length) == 0)
	    && (length >= 2)) {
		if (argc > 2) {
			Tcl_AppendResult(tcl.interp(), 
					 "wrong # args: should be \"", cmdName,
					 " all ?Obj?", (char *) NULL);
			res = TCL_ERROR;
			goto error;
		}
		Animation *a;
		int objType = ClassAllID;
		if (argc == 2) {
			// Map object name to class id
			objType = lookupObjname(argv[1]);
			if (objType == -1) {
				Tcl_AppendResult(tcl.interp(),
						 "Bad object name ", argv[1]);
				res = (TCL_ERROR);
				goto error;
			}
		}
		if (objType == ClassAllID) {
			for (a = animations_; a != NULL; a = a->next())
				if (!a->isTagged())
					tagObject(tag, a);
			for (a = drawables_; a != NULL; a = a->next())
				if (!a->isTagged())
					tagObject(tag, a);
		} else {
			for (a = animations_; a != NULL; a = a->next())
				if (!a->isTagged() && a->classid() == objType)
					tagObject(tag, a);
			for (a = drawables_; a != NULL; a = a->next())
				if (!a->isTagged() && a->classid() == objType)
					tagObject(tag, a);
		}

	} else if ((c == 'e') && (strncmp(argv[0], "enclosed", length) == 0)) {
		/* 
		 * enclosed x1 y1 x2 y2
		 */
		if (argc != 5) {
			Tcl_AppendResult(tcl.interp(), 
					 "wrong # args: should be \"",
					 cmdName, " enclosed x1 y1 x2 y2", 
					 (char *) NULL);
			res = TCL_ERROR;
			goto error;
		}
		BBox bb;
		// Translation/scaling will not change the order of bbox. :)
		if ((v->getCoord(argv[1], argv[2],
				 bb.xmin, bb.ymin) != TCL_OK) ||
		    (v->getCoord(argv[3], argv[4], bb.xmax, bb.ymax)!=TCL_OK))
			res = TCL_ERROR;
		else 
			res = tagArea(bb, tag, 1);

	} else if ((c == 'o') && 
		   (strncmp(argv[0], "overlapping", length) == 0)) {
		/*
		 * Overlapping x1 y1 x2 y2 
		 */
		if (argc != 5) {
			Tcl_AppendResult(tcl.interp(), 
					 "wrong # args: should be \"",
					 cmdName, " overlapping x1 y1 x2 y2",
					 (char *) NULL);
			res = TCL_ERROR;
			goto error;
		}
		BBox bb;
		// Translation/scaling will not change the order of bbox. :)
		if ((v->getCoord(argv[1], argv[2],
				 bb.xmin, bb.ymin) != TCL_OK) ||
		    (v->getCoord(argv[3], argv[4], bb.xmax, bb.ymax)!=TCL_OK))
			res = TCL_ERROR;
		else 
			res = tagArea(bb, tag, 0);

	} else if ((c == 'w') && (strncmp(argv[0], "withtag", length) == 0)) {
		if (argc != 2) {
			Tcl_AppendResult(tcl.interp(), 
					 "wrong # args: should be \"",
					 cmdName, " withtag tagOrId", 
					 (char *) NULL);
			res = TCL_ERROR;
			goto error;
		}
		// Find that tag or ID
		Animation *p = lookupTagOrID(argv[1]);
		if (p == NULL) {
			// Wrong tag
			Tcl_AppendResult(tcl.interp(),
					 "wrong tag ", argv[1],
					 (char *)NULL);
			res = TCL_ERROR;
		} else {
			// Label them as this tag
			tagObject(tag, p);
		}

	} else if ((c == 'c') && (strncmp(argv[0], "closest", length) == 0)) {
		float coords[2], halo;
		if ((argc < 3) || (argc > 5)) {
			Tcl_AppendResult(tcl.interp(), 
					 "wrong # args: should be \"",
					 cmdName, " closest x y ?halo? ",
					 (char *) NULL);
			res = TCL_ERROR;
			goto error;
		}
		if (v->getCoord(argv[1], argv[2], 
				coords[0], coords[1]) != TCL_OK) {
			Tcl_AppendResult(tcl.interp(),
					 "bad coordinates ",
					 argv[1], " ", argv[2], (char *)NULL);
			res = TCL_ERROR;
			goto error;
		}
		if (argc > 3) {
			// XXX: It's impossible to convert length in 
			// screen space to world space with translation
			// and scaling. We think it's in world space
			halo = strtod(argv[3], NULL);
			if (halo < 0.0) {
				Tcl_AppendResult(tcl.interp(), 
				       "can't have negative halo value \"",
						 argv[3], "\"", (char *) NULL);
				res = TCL_ERROR;
				goto error;
			}
		} else {
			halo = 0.0;
		}
		Animation *p = findClosest(coords[0], coords[1], halo);
		if (p != NULL)
			tagObject(tag, p);
		else 
			res = TCL_ERROR;

	} else  {
		Tcl_AppendResult(tcl.interp(), 
				 "bad search command \"", argv[0],
			 "\": must be above, all, below, closest, enclosed, ",
				 "overlapping, or withtag", (char *) NULL);
		res = TCL_ERROR;
		goto error;
	}

	if (res == TCL_OK) {
		if (bNew)
			add_tag(tag);
		return TCL_OK;
	}
error:
	if (tag != NULL)
		delete tag;
	return res;
}

Animation* NetModel::findClosest(float dx, float dy, double halo)
{
	double closestDist;
	Animation *startPtr, *closestPtr, *itemPtr;
	BBox bb;

	startPtr = animations_;
	itemPtr = startPtr;
	if (itemPtr == NULL) {
		return NULL;
	}
	closestDist = itemPtr->distance(dx, dy) - halo;

	if (closestDist < 0.0) {
		closestDist = 0.0;
	}
	while (1) {
		double newDist;
		/*
		 * Update the bounding box using itemPtr, which is the
		 * new closest item.
		 */
		bb.xmin = dx - closestDist - halo - 1,
		bb.ymin = dy - closestDist - halo - 1,
		bb.xmax = dx + closestDist + halo + 1,
		bb.ymax = dy + closestDist + halo + 1;
		closestPtr = itemPtr;
		/*
		 * Search for an item that beats the current closest 
		 * one.
		 * Work circularly through the canvas's item list until
		 * getting back to the starting item.
		 */
		while (1) {
			itemPtr = itemPtr->next();
			if (itemPtr == NULL) {
				if (startPtr == animations_) {
					startPtr = drawables_;
					itemPtr = startPtr;
				} else 
					return closestPtr;
			}
			if (itemPtr->isTagged()|| !itemPtr->bbox().overlap(bb))
				continue;
			newDist = itemPtr->distance(dx, dy)-halo;
			if (newDist < 0.0) {
				newDist = 0.0;
			}
			if (newDist <= closestDist) {
				closestDist = newDist;
				break;
			}
		}
	}
}

void NetModel::tagObject(Tag *tag, Animation *obj)
{
	Tcl& tcl = Tcl::instance();

	if (tag == NULL) {
		char str[20];
		sprintf(str, "No tag for %d", obj->id());
		Tcl_AppendElement(tcl.interp(), str);
		return;
	}
	if (tag == obj)
		return;

	// Tag that object
	// XXX 
	// Should we keep a tag list in each animation object so that
	// each object can find tags to which it belongs?
	tag->add(obj);
}

int NetModel::tagArea(BBox &bb, Tag *tag, int bEnclosed)
{
	Animation *p;

	// Because the area is a rectangle, we only need to 
	// find out all objects whose bounding boxes are 
	// within/overlapping the given rectangle.
	if (bEnclosed == 0) {
		// overlapping and enclosed
		for (p = animations_; p != NULL; p = p->next()) 
			if (!p->isTagged() && p->bbox().overlap(bb) 
			    && (p != tag))
				tagObject(tag, p);
		for (p = drawables_; p != NULL; p = p->next())
			if (!p->isTagged() && p->bbox().overlap(bb)
			    && (p != tag))
				tagObject(tag, p);
	} else {
		// enclosed only
		for (p = animations_; p != NULL; p = p->next()) 
			if (!p->isTagged() && bb.inside(p->bbox())
			    && (p != tag))
				tagObject(tag, p);
		for (p = drawables_; p != NULL; p = p->next())
			if (!p->isTagged() && bb.inside(p->bbox())
			    && (p != tag))
				tagObject(tag, p);
	}
	return TCL_OK;
}

int NetModel::deleteTagCmd(char *tagName, char *tagDel)
{
	Tag *p;
	Animation *q = NULL;

	p = (Tag *)lookupTagOrID(tagName);
	if (tagDel != NULL)
		q = lookupTagOrID(tagDel);

	if (p == NULL) {
		Tcl_AppendResult(Tcl::instance().interp(),
				 "bad tag ", tagName, (char *)NULL);
		return (TCL_ERROR);
	}
	if ((p->classid() != ClassTagID) || (p == q) || (q == NULL)){
		// If given an ID, or given a group only
		// delete it and do nothing else
		delete p;
		return TCL_OK; 
	}
	// Now delete tag p from object q
	q->deleteTag(p);
	return TCL_OK;
}

// XXX: Partial redraw is only working for EditView! Not for other
// animation views for now. We need a method in each Animation object
// to decide their "dirtiness" to make this redraw work. :( And the
// computation of clipping box should be purely within NetModel but
// not fed in externally
void NetModel::render(EditView* view, BBox &bb)
{
	Animation *a;
	for (a = drawables_; a != 0; a = a->next()) {
		if (!a->bbox().overlap(bb))
			continue;
		a->draw(view, now_);
	}
	for (a = animations_; a != 0; a = a->next()) {
		if (!a->bbox().overlap(bb))
			continue;
		a->draw(view, now_);
	}
}

void NetModel::moveNode(Node *n)
{
	//fprintf(stderr, "Moving nodes can only be performed by NetModel.\n");
	for (Edge *e = n->links(); e != 0; e = e->next_) {
		e->unmark();
		placeEdge(e, n);
		Node *dst = e->neighbor();
		// Should place reverse edges too
		Edge *p = dst->find_edge(n->num());
		if (p != NULL) {
			p->unmark();
			placeEdge(p, dst);
		}
	}
	for (Agent *a = n->agents(); a != NULL; a = a->next()) {
		a->mark(0), a->angle(NO_ANGLE);
		placeAgent(a, n);
	}
}

