/* Josh Pieper, (c) 2000 */

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

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

#include "query.h"
#include "conf.h"
#include "gnut.h"
#include "host.h"
#include "Gnut_List.h"
#include "Gnut_Queue.h"

Gnut_Queue *push_list;

Gnut_List *query_list;

#ifndef PTHREADS_DRAFT4
pthread_mutex_t query_mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t push_mutex=PTHREAD_MUTEX_INITIALIZER;
#else
pthread_mutex_t query_mutex;
pthread_mutex_t push_mutex;
#endif

void free_qrl(query_response ***x, int bugnum)
{
  yfree((void **) x, bugnum);
}

void free_qr(query_response **x, int bugnum)
{
  yfree((void **) x, bugnum);
}

int query_init()
{
  push_list = gnut_queue_new();
  query_list = 0;
  return 0; 
}

int query_push_add(query_response *qr)
{
  gd_s(1, "query_push_add entering qr=");
  gd_p(1, qr);
  gd_s(1, "\n");
  pthread_mutex_lock(&push_mutex);
  gnut_queue_insert(push_list, qr);

  /* Punt oldest one if necessary */
  if (push_list->size > MAX_OUTSTANDING_PUSHES)
    gnut_queue_remove(push_list);

  pthread_mutex_unlock(&push_mutex);
  gd_s(1, "query_push_add exiting push_list->size=");
  gd_i(1, push_list->size);
  gd_s(1, "\n");
  return 0;
}

query_response * query_push_find(uchar guid[16], uint32 ref)
{
  Gnut_List *gltmp;
  query_response *qr;
  
  pthread_mutex_lock(&push_mutex);
  
  for(gltmp=push_list->head; gltmp; gltmp=gltmp->next) {
    qr=gltmp->data;
    if (memcmp(qr->guid,guid,4)==0 && qr->ref==ref) {
      /* we have a match!!! */
      pthread_mutex_unlock(&push_mutex);
      return qr;      
    }
  }
  
  pthread_mutex_unlock(&push_mutex);
  return 0;
}

void query_push_remove(query_response *qr)
{
  pthread_mutex_lock(&query_mutex);
  gnut_queue_delete(push_list,qr);
  pthread_mutex_unlock(&query_mutex);
}

/* clears out dups in the query list, this can take a long time
 * so it shouldn't be called very often... */
int query_unique()
{
  Gnut_List *tmp;
  Gnut_List *cur, *prev;
  query_response *qr1,*qr2;
  int i;

  for (i=0,tmp=query_list; tmp; tmp=tmp->next,i++) {
    /* now for each element, we need to go through the rest
     * of the list and unlink any that are copies of this... */
	/*    printf("%i\n",i); */
    /* this is to keep abormally large lists from taking forever */
    if (i>500)
	  return 0;
    qr1=(query_response *) tmp->data;
    for (prev=tmp,cur=tmp->next; cur; cur=cur->next) {
      qr2=cur->data;
      if (memcmp(qr1->guid,qr2->guid,16)==0 && qr1->ref==qr2->ref) {
        /* we need to unlink this one from the list and continue */
        prev->next=cur->next;
        free_str(&(qr2->name), 168);
        free_qr(&qr2, 169);
        free_gl(&cur, 170);
        cur=prev;
      }
      
      prev=cur;
    }
  }
  return 0;
}

uint32 query_num()
{
  uint32 ret;
  pthread_mutex_lock(&query_mutex);
  
  ret=gnut_list_size(query_list);
  
  pthread_mutex_unlock(&query_mutex);
  return ret;
}

int query_lock()
{
  pthread_mutex_lock(&query_mutex);
  return 0;
}

int query_unlock()
{
  pthread_mutex_unlock(&query_mutex);
  return 0;
}

Gnut_List *query_retrieve()
{
  return query_list;
}

int query_add(uchar ip[4], uchar port[2], int ref, int speed,
  int size, char *name, uchar guid[16])
{
  query_response *qr;
  int i;
  char *t;
  uint32 qcrc;
  int dup;

  /* If we're in a VPN, we ignore query results from other VPN's of
   * differing class, because they can't be downloaded even with a
   * push request. */
  if (host_ok_for_neither(ip)) {
    return 0;
  }

  /* If we already have max responses, forget this one */
  if (query_num() >= conf_get_int("max_responses")) {
    return 0;
  }

  if (0) {
    printf("add ");
    for(i=0; i<16; i++) {
      printf("%02x", guid[i]);
    }
    printf("\n");
  }
  
  qr=(query_response *) xmalloc(sizeof(query_response));
  memcpy(qr->qr_ip, ip, 4);
  memcpy(qr->port,port,2);
  qr->ref=ref;
  qr->speed=speed;
  qr->size=size;
  qr->name=strdup(name);
  qr->dest_cache = 0;
  qr->qr_flag = ' ';
  memcpy(qr->guid,guid,16);

  /* Calculate CRC that identifies whether this result is new */
  crc32_start(&qcrc);
  for(i=0; i<4; i++) {
    crc32_add8(&qcrc, ip[i]);
  }
  for(i=0; i<2; i++) {
    crc32_add8(&qcrc, port[i]);
  }
  t = name;
  for(i=0; (i<100) && (*t); i++) {
    crc32_add8(&qcrc, *t);
    t++;
  }
  qr->crc = qcrc;
  
  /* Search list to see if it's there (no mutex needed, because
   * prepend is sufficiently atomic) */
  dup = 0;
  {
    Gnut_List *l;
    query_response *qr2;
	
    l = query_list;
    while(l) {
      qr2 = l->data;
      if (qr2->crc == qcrc) {
	dup = 1;
      }
      l = l->next;
    }
  }
  
  if (dup == 0) {
    /* The result is new -- we can add it now. */
    pthread_mutex_lock(&query_mutex);
    query_list=gnut_list_prepend(query_list,qr);
    pthread_mutex_unlock(&query_mutex);
  } else {
    /* We already have this one. Free up the stuff we allocated. */
    free_str(&(qr->name), 237);
    free_qr(&qr, 238);
  }
  
  return 0;
}

int query_add_cache(uchar ip[4], uchar port[2], int ref, int speed,
  int size, char *name, uchar guid[16])
{
  query_response *qr;
  int min_cache_size;

  min_cache_size = conf_get_int("min_cache_size");
  if (min_cache_size == 0) {
    min_cache_size = 15;
  }

  /* only cache files 1/4 the size of the cache or smaller
   * (limits thrashing, I hope - Ray) */
  if ( (size < min_cache_size*1024)
       || (size > conf_get_int("cache_size") * 1024 / 4) ) {
    return 0;
  }

  printf("\nAdding to cache: %s.\n", name);

  qr=(query_response *) xmalloc(sizeof(query_response));

  qr->dest_cache = 1;
  memcpy(qr->qr_ip,ip,4);
  memcpy(qr->port,port,2);
  qr->ref=ref;
  qr->speed=speed;
  qr->size=size;
  qr->name=strdup(name);
  qr->qr_flag = ' ';
  qr->qr_retr_enable = 0;
  memcpy(qr->guid,guid,16);

  gnut_transfer_start(qr);

  return 0;
}

int query_clear()
{
  Gnut_List *gltmp;
  query_response *qr;
  
  pthread_mutex_lock(&query_mutex);
  
  for (gltmp = query_list; gltmp; gltmp = gltmp->next) {
    qr = gltmp->data;
    free_str(&(qr->name), 239);
  }
  
  query_list = gnut_list_fre(query_list);
  
  pthread_mutex_unlock(&query_mutex);
  return 0;
}

query_response *query_find(uchar guid[16], uint32 ref)
{
  /* find the matching entry, then return a copy of it... */
  Gnut_List *gltmp;
  query_response *qr, *qr2;
  
  pthread_mutex_lock(&query_mutex);
  
  for(gltmp=query_list;gltmp;gltmp=gltmp->next) {
    qr=gltmp->data;
    if (memcmp(qr->guid,guid,4)==0 && qr->ref==ref) {
      /* we have a match!!! */
      qr2=(query_response *) xmalloc(sizeof(query_response));
      memcpy(qr2,qr,sizeof(query_response));
      qr2->name=strdup(qr->name);
      pthread_mutex_unlock(&query_mutex);
      return qr2;
    }
  }
  
  pthread_mutex_unlock(&query_mutex);
  return 0;
}

query_response *query_index(int index, char flag)
{
  /* we need to actually make a copy, because as soon as the mutex
   * is unlocked, we aren't guaranteed of the original's existance */
  Gnut_List *gltmp;
  query_response *qr, *qr2;

  pthread_mutex_lock(&query_mutex);

  for(gltmp = query_list; gltmp; gltmp = gltmp->next) {
    qr=gltmp->data;
    if (qr->index == index) {
      /* we have a match!!! */
      if (flag) {
	qr->qr_flag = flag;
      }
      qr2 = (query_response *) xmalloc(sizeof(query_response));
      memcpy(qr2, qr, sizeof(query_response));
      qr2->name = strdup(qr->name);
      pthread_mutex_unlock(&query_mutex);
      return qr2;
    }
  }

  pthread_mutex_unlock(&query_mutex);
  return 0;
}

query_response *query_copy(query_response *qr)
{
  query_response *qrnew;

  qrnew = (query_response *)xmalloc(sizeof(query_response));
	
  memcpy(qrnew->qr_ip, qr->qr_ip, 4);
  memcpy(qrnew->port,qr->port,2);
  qrnew->ref=qr->ref;
  qrnew->speed=qr->speed;
  qrnew->size=qr->size;
  qrnew->name=strdup(qr->name);
  qrnew->dest_cache = qr->dest_cache;
  memcpy(qrnew->guid,qr->guid,16);
  qrnew->qr_flag = qr->qr_flag;
  
  return qrnew;
}
