#include <atmcore/atmcore.h>
#include <sroute/sroute.h>
#include <elib/string.h>


void print_routing_table(char *name);
void print_route(route r, int dummy);

int key_from_address();
static table switch_mapping;
static int switch_count=1;
static int switch_comparator(a,b) route_switch a; virtual_switch b;
{return (a->s==b);}
static int route_hash_comparator (a,b)   route a;   address b;
{ return(address_compare (a->to,b));}

void write_address( pbuffer b, int o, address a)
{
  buffer_write(b,o,&a->family,4);
  buffer_write(b,o+4,&a->length,4);
  buffer_write(b,o+8,address_contents(a),a->length);
}

address read_address(pbuffer b,int o)
{
  int f;
  int l;
  char *contents;
  address result;

  buffer_read(b,o,&f,4);
  buffer_read(b,o+4,&l,4);
  contents=(char *)allocate_memory(l);
  buffer_read(b,o+8,contents,l);
  result=create_address(f,l,contents);
  deallocate_memory(contents);
  return(result);
}

void print_routing_table(char *name)
{
  virtual_switch s = (virtual_switch) switch_from_name(name);
  route_switch rs = (route_switch) table_find(switch_mapping,s);
  if (rs) 
     iterate_table_entries(rs->t,print_route,0);
  else 
     printf("No route switch found\n");
}

void print_route(route r, int dummy)
{
  printf("To: %s\n", sprint_address(r->to));
  printf("From: %s\n", sprint_address(r->from));
  printf("Hops: %d \t Last seen: %d\n", r->hop_count, r->last_seen);
  printf("\n");
}	

static void send_route(route r,route_port rp)
{
  int fl=address_length(r->from);
  int tl=address_length(r->to);
  pbuffer b;
  int type=2|(r->local*4);
  int new=r->hop_count+1;

  if (rp->connected){
    b=allocate_downwards_buffer(rp->l,fl+tl+24);
    buffer_write(b,0,&type,4);
    buffer_write(b,4,&new,4);
    write_address(b,8,rp->rs->a);
    write_address(b,16+fl,r->to);
    write_down(rp->l,b,0);
    dereference_buffer(b);
  }
}

static void send_route_request(route r, route_port rp)
{
  int fl=address_length(r->from);
  int tl=address_length(r->to);
  pbuffer b;
  int type=1;

  if (rp->connected){
    b=allocate_downwards_buffer(rp->l,fl+tl+20);
    buffer_write(b,0,&type,4);
    write_address(b,4,rp->rs->a);
    write_address(b,fl+12,r->to);
    write_down(rp->l,b,0);
    dereference_buffer(b);
  }
}

static void route_response( pbuffer b, route_port rp, int type)
{
  route new,old;
  char *temp;
  address from,to;
  int hop_count;
  list i;

  buffer_read(b,4,&hop_count,4);
  from=read_address(b,8);
  to=read_address(b,16+address_length(from));
  if (type & 4) {
    add_translation(from,to);
    add_translation(to,from);
  }
  if (old=(route)table_find(rp->rs->t,to)){
    if (hop_count < old->hop_count){
      printf("Switch: %s\n", rp->rs->s->name);
      printf ("sroute got route to %s from %s hop_count %d\n",
	      temp=sprint_address (to),rp->p->name,hop_count);
      deallocate_memory(temp);
      old->from=from;
      old->hop_count=hop_count;
      old->last_seen=now();
    }
  } else {
    printf("Switch: %s\n", rp->rs->s->name);
    printf ("sroute got route to %s from\n       %s hop_count %d\n",
	    temp=sprint_address (to),rp->p->name,hop_count);
    deallocate_memory(temp);
    add_translation(from,rp->p->a);
    add_translation(rp->p->a,from);
    new=(route)allocate_memory(sizeof(struct route));
    new->from=from;
    new->to=to;
    new->last_seen=now();
    new->hop_count=hop_count;
    table_insert(rp->rs->t,new,to);
    dolist(i,rp->rs->ports) send_route(new,(route_port)(i->first));
  }
}

static route_request(pbuffer b,route_port rp)
{
}

static list find_route (call c,list d, qos q)
{
  address a=(address)d->first;
  route result=0;
  route_switch rs=(route_switch)table_find(switch_mapping,c->s);
  
  if (result=table_find (rs->t,d->first)){
    if (!address_compare(result->from,rs->a))
      return(insert(result->from,d));
  }
  return(0);
}


static void route_top (layer l, pbuffer b, int f)
{
  route_port rp=(route_port)l->state;
  int type;
  if (b){
    buffer_read(b,0,&type,4);
    if (type&1) route_request(b,rp);
    if (type&2) route_response(b,rp,type);
  }
  if (f & PROTOCOL_CONNECTED){
    rp->connected=1;
    iterate_table_entries(rp->rs->t,send_route,rp,0);
    printf ("sroute %s connected\n",rp->p->name);
  }
  if (f & PROTOCOL_DISCONNECTED){
    rp->connected=0;
    printf ("sroute %s disconnected\n",rp->p->name);
  }
}

static int sroute_new_switch_handler(virtual_switch s)
{
  int a=(unique_identifier<<16)|(switch_count++);
  route_switch new=(route_switch)allocate_memory(sizeof(struct route_switch));
  new->t=create_table(route_hash_comparator,key_from_address);
  new->s=s;
  new->ports=0;
  new->a=create_address(VINCE_SWITCH_FAMILY,4,&a);
  table_insert(switch_mapping,new,s);
  return(1);
}

void add_sroute_port (port p)
{
  connection_destination d;
  route_switch rs=(route_switch)table_find(switch_mapping,p->s);
  route_port rp=(route_port)allocate_memory(sizeof(struct route_port));
  rp->p=p;
  rp->connected=0;
  rp->l=create_layer(rp,0,route_top,0);
  rp->rs=rs;
  rs->ports=insert(rp,rs->ports);
  (*p->h->assemble_stack)(p,0,SROUTE_PVCI,STACK_SSCOP|STACK_AAL_5,rp->l,0,0);
}

static void new_translation(address from, address to, void *arg)
{
  port p;
  virtual_switch s;
  route new;
  route_switch rs;
  route_port rp;
  list i;
  
  if (((to->family==PORT_NAME_FAMILY) ||
       (to->family==SWITCH_NAME_FAMILY)) &&
      (from->family != VINCE_SWITCH_FAMILY)){
    if ((to->family==PORT_NAME_FAMILY))
      rs=(route_switch)table_find(switch_mapping,port_from_address(to)->s);
    else 
      rs=(route_switch)table_find(switch_mapping,switch_from_address(to));
    if (rs){
      if (!table_find(rs->t,to)){
	new=(route)allocate_memory(sizeof(struct route));
	new->to=from;
	new->from=rs->a;
	new->hop_count=0;
	new->q=0;
	if (to->family==SWITCH_NAME_FAMILY) new->local=1;
	else new->local=0;
	new->last_seen=now();
	table_insert(rs->t,new,from);
	dolist(i,rs->ports) send_route(new,(route_port)(i->first));
      }
    }
  }
}


static struct router sroute={
  "sroute",
  find_route,
};

static char *sprint_vince_switch(address a)
{
  return(string_format("%08x",*(int *)address_contents(a)));
}

static struct address_family vince_switch_family={
  0,
  sprint_vince_switch,
  "vince_switch_family",
  0
};
 
/* new port handler ?*/
void sroute_init()
{
  add_router(&sroute);
  switch_mapping=create_table(switch_comparator,key_from_int);
  add_switch_creation_handler(sroute_new_switch_handler);
  add_translation_handler(new_translation,0);
  all_translations(new_translation,0);
  register_family(VINCE_SWITCH_FAMILY,&vince_switch_family);
#ifdef SKIP
  skip_function (add_sroute_port,"add_sroute_port","","p");
  skip_function (print_routing_table,"print_routing_table","","s");
#endif
}
