/*
 * Network model with automatic layout
 *
 * Layout code from nem by Elan Amir
 *
 * $Header: /usr/src/mash/repository/vint/nam-1/anetmodel.cc,v 1.21 1999/01/12 20:52:51 haoboy Exp $
 */

#include <stdlib.h>
#include <math.h>
#include <float.h>

#include "random.h"
#include "view.h"
#include "netview.h"
#include "animation.h"
#include "queue.h"
#include "edge.h"
#include "node.h"
#include "agent.h"
#include "sincos.h"
#include "state.h"
#include "packet.h"
#include "anetmodel.h"

class AutoNetworkModelClass : public TclClass {
public:
	AutoNetworkModelClass() : TclClass("NetworkModel/Auto") {}
	TclObject* create(int argc, const char*const* argv) {
		if (argc < 5) 
			return 0;
		return (new AutoNetModel(argv[4]));
	}
} autonetworkmodel_class;

int AutoNetModel::AUTO_ITERATIONS_ = 1;
int AutoNetModel::INCR_ITERATIONS_ = 200;
int AutoNetModel::RANDOM_SEED_ = 1;
int AutoNetModel::recalc_ = 1;
double AutoNetModel::INIT_TEMP_ = 0.30; // follow Elan's 
double AutoNetModel::MINX_ = 0.0; 
double AutoNetModel::MAXX_ = 1.0;
double AutoNetModel::MINY_ = 0.0;
double AutoNetModel::MAXY_ = 1.0;

AutoNetModel::AutoNetModel(const char *animator) : NetModel(animator)
{
	iterations_ = AUTO_ITERATIONS_;
	bind("KCa_", &kca_);
	bind("KCr_", &kcr_);
	bind("Recalc_", &recalc_);
	bind("INCR_ITERATIONS_", &INCR_ITERATIONS_);
	bind("AUTO_ITERATIONS_", &AUTO_ITERATIONS_);
	bind("RANDOM_SEED_", &RANDOM_SEED_);
}

AutoNetModel::~AutoNetModel()
{
}

int AutoNetModel::command(int argc, const char *const *argv)
{
	if (argc == 2) {
		if (strcmp(argv[1], "layout") == 0) {
			/*
			 * <net> layout
			 * compute layout using Elan's nem
			 */
			layout();
			return (TCL_OK);
		}
		if (strcmp(argv[1], "relayout") == 0) {
			relayout();
			return (TCL_OK);
		}
		if (strcmp(argv[1], "reset") == 0) {
			initEmbedding();
			return (TCL_OK);
		}
	} 
	return (NetModel::command(argc, argv));
}

void AutoNetModel::initEmbedding() 
{
	Node *n;
	double maxx, maxy;

	Random::seed_heuristically();

	maxx = MINX_ + (MAXX_-MINX_) * 0.4;
	maxy = MINY_ + (MAXY_-MINY_) * 0.4;	

	// randomize nodes' position within square [(0,0), (1,1)]
	for (nNodes_ = 0, n = nodes_; n != 0; n = n->next_, nNodes_++) {
		n->place(Random::uniform(MINX_, maxx),
			 Random::uniform(MINY_, maxy));
	}
	//optk_ = kc_ * sqrt((MAXX_-MINX_)*(MAXY_-MINY_) / (double)nNodes);
	optka_ = kca_ * sqrt((MAXX_-MINX_)*(MAXY_-MINY_) / (double)nNodes_);
	optkr_ = kcr_ * sqrt((MAXX_-MINX_)*(MAXY_-MINY_) / (double)nNodes_);
	temp_ = INIT_TEMP_;
}

void AutoNetModel::placeEverything()
{
	// Should re-initialize nymin_ and nymax_
	nymin_ = FLT_MAX;
	nymax_ = -FLT_MAX;
	for (Node *n = nodes_; n != 0; n = n->next_) {
		for (Edge *e = n->links(); e != 0; e = e->next_)
			placeEdge(e, n);
		placeAllAgents(n);
		if (nymin_ > n->y())
			nymin_ = n->y();
		if (nymax_ < n->y())
			nymax_ = n->y();
	}
}

// do more passes
void AutoNetModel::relayout()
{
	Node *n;
	Edge *e;
	temp_ = INIT_TEMP_;
	for (n = nodes_; n != 0; n = n->next_)
		for (e = n->links(); e != 0; e = e->next_)
			e->unmark();

	//weigh_subtrees();
	//bifucate_graph();

	// in case that the two constants changed
	optka_ = kca_ * sqrt((MAXX_-MINX_)*(MAXY_-MINY_) / (double)nNodes_);
	optkr_ = kcr_ * sqrt((MAXX_-MINX_)*(MAXY_-MINY_) / (double)nNodes_);

	for (int i = 0; i < 100; i++)
	{
	  //alternate doses of vigorous shaking and letting it settle a little
          //seem to work best.
	        if ((i==0)||(i==60)) temp_ = INIT_TEMP_;
	        if ((i==50)||(i==70)) temp_ = INIT_TEMP_/4.0;

	        if ((i<50)||((i>=60)&&(i<70)))
		{
		  embed_phase1();
		}
		else
		{
		  embed_phase2();
		}
		for (n = nodes_; n != 0; n = n->next_)
		  for (e = n->links(); e != 0; e = e->next_)
		    e->unmark();

		scale_estimate();
		placeEverything();
		for (View *p = views_; p != 0; p = p->next_)
		  if ((p->width() > 0) && (p->height() > 0))
			// XXX assume this means tk is being initialized
		  {
		    p->redrawModel();
		    p->draw();
		  }
	}

}

// do several passes of embedding
void AutoNetModel::layout()
{
	initEmbedding();
	for (int i = 0; i < iterations_; i++) {
		embed_phase2();
	}

	scale_estimate(); // find node size after layout
	placeEverything();
}

void AutoNetModel::cool()
{
	if (temp_ > 0.001)
		temp_ *= 0.95;
	else 
		temp_ = 0.001;
}

void AutoNetModel::placeAllAgents(Node *src) const
{
	// clear all marks and clear all angles
	for (Agent *a = src->agents(); a != NULL; a = a->next()) {
		a->mark(0), a->angle(NO_ANGLE);
		placeAgent(a, src);
	}
}

// we need to use edge length, instead of delay, to estimate scale
void AutoNetModel::scale_estimate()
{
	/* Determine the maximum link delay. */
	double emax = 0., emean = 0., dist;
	Node *n;
	int edges=0;
	for (n = nodes_; n != 0; n = n->next_) {
		for (Edge* e = n->links(); e != 0; e = e->next_) {
		        edges++;
			dist = n->distance(*(e->neighbor()));
			emean+=dist;
			if (dist > emax)
				emax = dist;
		}
	}
	emean/=edges;
	
	/*
	 * 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_) {
		n->size(NODE_EDGE_RATIO * emean);
		for (Edge* e = n->links(); e != 0; e = e->next_)
			e->size(.03 * emean);
	}
	/*store this because we need it for monitors*/
	node_size_=NODE_EDGE_RATIO * emean;
}

// Fruchterman, T.M.J and Reingold, E.M.,
// Graph Drawing by Force-directed Placement,
// Software-Practice and Experience, vol. 21(11), 1129-1164 (Nov. 91)
//
// Do one pass of embedding
void AutoNetModel::embed_phase1()
{
	Node *v, *u;	
	Edge* e;
	double dx, dy, mag, f, minn, rx, ry;
	/*
	 * Randomly jitter everything to try and break out of local minima
         */
	for (v = nodes_; v != 0; v = v->next_) {
	  v->displace(0., 0.);
	  rx=0.1*(MAXX_-MINX_)*
	    ((INIT_TEMP_-temp_)/INIT_TEMP_)*
	    ((Random::random()&0xffff)/32768.0 - 1.0);
	  ry=0.1*(MAXX_-MINX_)*
	    ((INIT_TEMP_-temp_)/INIT_TEMP_)*
	    ((Random::random()&0xffff)/32768.0 - 1.0);
	  v->displace(rx,ry);
	}
	/*
	 * Calculate repulsive forces between all vertices.
	 */
	for (v = nodes_; v != 0; v = v->next_) {
		for (u = nodes_; u != 0; u = u->next_) {
			if (u == v) 
				continue;
			dx = v->x() - u->x();
			dy = v->y() - u->y();
			if (dx == 0 && dy == 0)
				dx = dy = 0.001;
			mag = sqrt(dx * dx + dy * dy);
			f = REP(mag, optkr_);
			v->displace(v->dx() + ((dx / mag) * f),
				    v->dy() + ((dy / mag) * f));
		}
	}
	/* 
	 * Limit the maximum displacement to the temperature temp;
	 * and then prevent from being displaced outside frame.
	 */
	if (recalc_==1) {
	  for (v = nodes_; v != 0; v = v->next_) {
	    mag = sqrt(v->dx()*v->dx() + v->dy() * v->dy());
	    double posx = v->x(), posy = v->y();
	    if (mag != 0) {
	      minn = min(mag, temp_);
	      posx += v->dx() / mag * minn;
	      posy += v->dy() / mag * minn;
	    }
	    
	    v->place(posx, posy);
	    v->displace(0.,0.);
	  }
	}
	/*
	 * Calculate attractive forces between neighbors.
	 */
	for (v = nodes_; v != 0; v = v->next_) {
		for (e = v->links(); e != 0; e = e->next_) {
			u = e->neighbor();
			dx = v->x() - u->x();
			dy = v->y() - u->y();
			mag = sqrt(dx * dx + dy * dy);
			// XXX we consider single direction edge as 
			// of single direction attraction. So we only
			// attract one node toward the other. If later 
			// we have the other edge, the other node will be 
			// attracted too.
			if (mag >= 0) {
				f = ATT(mag, optka_);
				v->displace(v->dx() - dx / mag * f,
					    v->dy() - dy / mag * f);
			}
		}
	}
	/* 
	 * Limit the maximum displacement to the temperature temp;
	 * and then prevent from being displaced outside frame.
	 */
	for (v = nodes_; v != 0; v = v->next_) {
		mag = sqrt(v->dx()*v->dx() + v->dy() * v->dy());
		double posx = v->x(), posy = v->y();
		if (mag != 0) {
			minn = min(mag, temp_);
			posx += v->dx() / mag * minn;
			posy += v->dy() / mag * minn;
		}

#if 0
		posx = min(MAXX_, max(MINX_, posx));
		posy = min(MAXY_, max(MINY_, posy));
#endif

		v->place(posx, posy);
#if 0
		printf("Position of node %s: (%f, %f)\n", 
		       v->name(), posx, posy);
#endif
	}

	/* Cool the temperature */
	if (temp_ > 0.001)
		temp_ *= 0.95;
	else 
		temp_ = 0.001;

#if 0
	printf("------------------------------\n");
#endif
}

void AutoNetModel::embed_phase2()
{
	Node *v, *u;	
	Edge* e;
	double dx, dy, mag, f, minn;
	/*
	 * Calculate repulsive forces between all vertices.
	 */
	for (v = nodes_; v != 0; v = v->next_) {
		//XXX why not 0. as in paper?
	        v->displace(0.,0.);
		for (u = nodes_; u != 0; u = u->next_) {
			if (u == v) 
				continue;
			dx = v->x() - u->x();
			dy = v->y() - u->y();
			if (dx == 0 && dy == 0)
				dx = dy = 0.001;
			mag = sqrt(dx * dx + dy * dy);
			f = REP(mag, optkr_);
			if (f < 2*u->size()) {
			  if (f<temp_)
			  {
			    f=2*u->size()/(mag/* *temp_*/);
			  }
			  else 
			    f=2*u->size()/mag;
			}
			v->displace(v->dx() + ((dx / mag) * f),
				    v->dy() + ((dy / mag) * f));
		}
	}
	/* 
	 * Limit the maximum displacement to the temperature temp;
	 * and then prevent from being displaced outside frame.
	 */
	if (recalc_==1) {
	  for (v = nodes_; v != 0; v = v->next_) {
	    mag = sqrt(v->dx()*v->dx() + v->dy() * v->dy());
	    double posx = v->x(), posy = v->y();
	    if (mag != 0) {
	      minn = min(mag, temp_);
	      posx += v->dx() / mag * minn;
	      posy += v->dy() / mag * minn;
	    }
	    
	    v->place(posx, posy);
	    v->displace(0.,0.);
	  }
	}
	/*
	 * Calculate attractive forces between neighbors.
	 */
	for (v = nodes_; v != 0; v = v->next_) {
	  //if(v->mass()<=0) continue;
		for (e = v->links(); e != 0; e = e->next_) {
			u = e->neighbor();
			//if(u->mass()<=0) continue;
			dx = v->x() - u->x();
			dy = v->y() - u->y();
			mag = sqrt(dx * dx + dy * dy);
			// XXX we consider single direction edge as 
			// of single direction attraction. So we only
			// attract one node toward the other. If later 
			// we have the other edge, the other node will be 
			// attracted too.
			if (mag >= ((INIT_TEMP_-temp_)/INIT_TEMP_)*(e->length()+(v->size()+u->size())*0.75)) {
				f = ATT2(mag, optka_);
				v->displace(v->dx() - dx / mag * f,
					    v->dy() - dy / mag * f);
			}
		}
	}
	/* 
	 * Limit the maximum displacement to the temperature temp;
	 * and then prevent from being displaced outside frame.
	 */
	for (v = nodes_; v != 0; v = v->next_) {
	  //if(v->mass()<=0) continue;
		mag = sqrt(v->dx()*v->dx() + v->dy() * v->dy());
		double posx = v->x(), posy = v->y();
		if (mag != 0) {
			minn = min(mag, temp_);
			posx += v->dx() * minn / mag;
			posy += v->dy() * minn / mag;
		}

#if 0
		posx = min(MAXX_, max(MINX_, posx));
		posy = min(MAXY_, max(MINY_, posy));
#endif

		v->place(posx, posy);
#if 0
		printf("Position of node %s: (%f, %f)\n", 
		       v->name(), posx, posy);
#endif
	}

	/* Cool the temperature */
        if (temp_ > 0.001)
                temp_ *= 0.95;
        else
                temp_ = 0.001;

#if 0
	printf("------------------------------\n");
#endif
}


// packet handling stuff. use new packet
void AutoNetModel::handle(const TraceEvent& e, double now, int direction)
{
	switch (e.tt) {
	case 'a':
	{
		NetModel::handle(e, now, direction);
		// recalculate bounding box so that all agents will be within
		// view
		for (View *v = views_; v != 0; v = v->next_)
			v->redrawModel();
		break;
	}
	default:
		NetModel::handle(e, now, direction);
		break;
	}
}

void AutoNetModel::bifucate_graph()
{
    Node *n, *m;
    for (n = nodes_; n != 0; n = n->next_) {
      if (n->mass()<=0) continue;
      int remaining=0;
      for (m = nodes_; m != 0; m = m->next_) {
	if (m->mass()>0) remaining++;
	m->mark(0);
      }
      int depth=0;
      int result=2;
      while((result!=0)&&(result!=1))
      {
	depth++;
	for (m = nodes_; m != 0; m = m->next_) {
	  if (m->mass()>0) {
	    m->mark(0);
	    m->color("black");
	  }
	}
	result=mark_to_depth(n, depth);
	printf("depth: %d result: %d\n", depth, result);
      }
      if (result==1) {
	int ctr=0;
	for (m = nodes_; m != 0; m = m->next_) {
	  if (m->marked()==1) ctr++;
	}
	printf("remaining: %d ctr: %d\n", remaining, ctr);
	if ((remaining-ctr>1)&&(ctr>1)&&(ctr<remaining/2))
	{
	  printf("success!\n");
	  for (m = nodes_; m != 0; m = m->next_) {
	    if (m->marked()==1)
	    {
	      m->mass(-1);
	      m->color("blue");
	    }
	  }
	}
      } else {
	printf("failed at depth %d!\n", depth);
      }
    }
}

int AutoNetModel::mark_to_depth(Node *n, int depth)
{
  int sum=0;
  if (depth==0) {
    n->mark(2);
    return 1;
  }
  if (n->marked()==2) sum--;
  n->mark(1);


  for(Edge *e = n->links(); e != 0; e = e->next_)
  {
    Node *dst=e->neighbor();
    if ((dst->mass()>0)&&(dst->marked()==0))
      sum+=mark_to_depth(dst, depth-1);
  }
  return sum;
}

void AutoNetModel::weigh_subtrees()
{
  Node *n, *dst, *newdst;
  Edge* e;
  int nodes=0;
  for (n = nodes_; n != 0; n = n->next_) {
    n->mass(1);
  }
  for (n = nodes_; n != 0; n = n->next_) {
    int ctr=0;
    for (e = n->links(); e != 0; e = e->next_)
      ctr++;
    if (ctr==1)
    {
      dst=n->links()->neighbor();
      dst->mass(2);
      n->mass(0);
      n->color("green");
      nodes++;
    }
    while(ctr==1) {
      ctr=0;
      for (e = dst->links(); e != 0; e = e->next_)
	if (e->neighbor()->mass()==1) ctr++;
      if (ctr==1)
      {
	for (e = dst->links(); e != 0; e = e->next_)
	  if (e->neighbor()->mass()==1) {
	    newdst=e->neighbor();
	    newdst->mass(1+dst->mass());
	    dst->mass(0);
	    dst->color("red");
	    nodes++;
	    dst=newdst;
	    break;
	  }
      }
    }
  }
  printf("massless nodes: %d\n", nodes);
  nodes=0;
  for (n = nodes_; n != 0; n = n->next_) {
    if (n->mass()==0) {
      nodes++;
      n->color("grey");
    }
  }
  printf("massless nodes: %d\n", nodes);
}
    

void AutoNetModel::place_subtrees()
{
  Node *n, *dst;
  Edge* e;
  double angle;
  int nodes=0, did_something=1;
  double comx=0.0, comy=0.0, x, y;
  for (n = nodes_; n != 0; n = n->next_) {
    if (n->mass()!=0)
    {
      comx+=n->x();
      comy+=n->y();
      nodes++;
    }
  }
  comx/=nodes;
  comy/=nodes;
  printf("com at %.2f,%.2f\n", comx, comy);
  while (did_something) {
    did_something=0;
    for (n = nodes_; n != 0; n = n->next_) {
      if (n->mass()==0)
      {
	for (e = n->links(); e != 0; e = e->next_)
	  if (e->neighbor()->mass()>0)
	  {
	    dst=e->neighbor();
	    n->mass(1);
	    n->color("green");
	    x=dst->x();
	    y=dst->y();
	    if (y-comy!=0)
	      angle=atan((x-comx)/(y-comy));
	    else
	      angle=0;
	    if (y-comy>0)
	      angle+=M_PI;
	    n->place(x-2*n->size()*sin(angle), 
		     y-2*n->size()*cos(angle));
	    did_something=1;
	    break;
	  }
      }
    }
  }
}
