/* Josh Pieper, (c) 2000 */

/* This file is distributed under the GPL, see file COPYING for details */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#include "gnut_lib.h"
#include "Gnut_Hash.h"
#include "Gnut_Queue.h"
#include "route.h"

/* this mutex protects all finds, adds, and removes to the
 * routing table */
#ifndef PTHREADS_DRAFT4
pthread_mutex_t route_mutex=PTHREAD_MUTEX_INITIALIZER;
#else
pthread_mutex_t route_mutex;
#endif

Gnut_Hash *route_hash=0;
Gnut_Queue *route_queue=0;
lru_hash qreplies;

/* The routing table uses a hash and a list, both store 16-byte GUID's.
 * The list is used to keep track of which GUID's are the oldest, and the
 * hash is used to quickly locate a GUID (to see if it's in the table
 * or not) */

/* The compare func compares all 16 bytes of GUID data */
int route_compare_func(void *a, void *b)
{
  gnut_mprobe(a);
  gnut_mprobe(b);
  if (memcmp(a, b, 16) == 0)
	return 0;
  return -1;
}

/* GUID is hashed by adding its 16 bytes together */
uchar route_hash_func(void *a)
{
  route_entry *re;
  uchar b;
  int i;

  /* gd_s(1,"entering a=%p\n",a); */

  re=a;
  for (b=0, i=0; i<16; i++) {
	/* printf("%02x", re->guid[i]); */
    b += re->guid[i];
  }
  
  /* printf("\n"); */
  return b;
}

int route_init()
{
  route_hash=gnut_hash_new(route_hash_func, route_compare_func);
  route_queue=gnut_queue_new();
  lru_init(&qreplies, 1000);
  return 0;
}

/* int route_guid_add(uchar guid[16], gcs *gc)
 *
 * adds the guid (which came over connection gc) into the routing table
 *
 * When there are more than ROUTE_MAX entries in the table, the oldest
 * is deleted */
int route_guid_add(uchar guid[16], gcs *gc)
{
  route_entry *re;
  
  gd_s(3, "route_guid_add entering gc=");
  gd_p(3, gc);
  gd_s(3, "\n");

  /* allocate and copy the data */
  re=(route_entry *) xmalloc(sizeof(route_entry));
  memcpy(re->guid, guid, 16);
  re->p = gc;

  /* Store in both queue and hash. Neither copies the data block, they
   * just point to it */
  pthread_mutex_lock(&route_mutex);

  /* printf("route_guid_add: "); */
  /* for (i=0;i<16;i++) printf("%02X",re->guid[i]); */
  /* printf("\n\n");   */
  gnut_hash_insert(route_hash, re);
  gnut_queue_insert(route_queue, re);
  
  if (route_queue->size > ROUTE_MAX) {
    gd_s(5,"hit max routing table size\n");
    re=gnut_queue_remove(route_queue);
    gnut_hash_remove(route_hash, re);
    xfree(re);
  }

  pthread_mutex_unlock(&route_mutex);
  
  gd_s(3, "route_guid_add returning success\n");
  return 0;
}

/* gcs * route_guid_find(uchar guid[16])
 *
 * looks through the routing table, and if an entry is found,
 * the corresponding connection is returned, otherwise
 * NULL is returned */
gcs *route_guid_find(uchar guid[16], route_entry **the_re)
{
  route_entry *re,*re2;
  
  gd_s(3,"route_guid_find entering\n");
  
  re=(route_entry *) xmalloc(sizeof(route_entry));
  gnut_mprobe(re);

  memcpy(re->guid, guid, 16);

  pthread_mutex_lock(&route_mutex);
  gd_s(3, "route_guid_find locked mutex\n");
  
  re2=gnut_hash_find(route_hash,re);
    
  pthread_mutex_unlock(&route_mutex);
  
  gnut_mprobe(re);
  
  xfree(re);
  if (re2) {
    *the_re = re2;
    return re2->p;
  }
  gd_s(3, "route_guid_find not found!\n");
  *the_re = 0;
  return 0;
}
      
/* int route_guid_clear(gcs *gc)
 *
 * turns all occurances of gc in routing table into 0
 * so that they won't be matched again */
int route_guid_clear(gcs *gc)
{
  Gnut_List *tmp;
  route_entry *re;
  gd_s(4, "route_guid_clear entering gc=");
  gd_p(4, gc);
  gd_s(4, "\n");
  
  pthread_mutex_lock(&route_mutex);
  gd_s(4, "route_guid_clear locked mutex\n");
  
  /* since the hash contains pointers to the
   * same data, I can do this here, and save a lot
   * of time... */
  for (tmp=gnut_queue_list(route_queue); tmp; tmp=tmp->next) {
    gnut_mprobe(tmp);
    re=tmp->data;
    if (re->p==gc)
      re->p=0;
  }

  pthread_mutex_unlock(&route_mutex);

  gd_s(4,"route_guid_clear returning success\n");
  return 0;
}
