/* $Id$
 * 
 * This file is a part of Amp2p.  Amp2p is Copyright 2004 Joseph
 * Curtis.  jocurtis@gmail.com
 *
 *
 *  Amp2p is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  Amp2p is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Amp2p; if not, write to the Free Software Foundation,
 *  Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

/* Contains all of the XMMS/Linux specific code. There will definitly
 * be more of this.  Must contain the abstraction functions or a link
 * error will occur. Prototypes for the abstraction stuff are in
 * amp2p.h. */

#include <pthread.h>
#include <gtk/gtk.h>
#include "xmmsctrl.h"
#include <plugin.h>
#include "configfile.h"
#include <sys/socket.h>
#include <unistd.h>          
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/timeb.h>

/* for sha1 */
#include <openssl/sha.h>

/* for md5 */
#include <openssl/md5.h>

#include "amp2p.h"
#include "xmms-amp2p.h"
#include "hashtable.h"

/* Global Variables */

/* We need two sets of threads.  One to listen for connections and
 * forward search request, performs searches and whatnot.  The other
 * to interface with the user and perform searches.  In fact, probably
 * many threads, one for each download at least.  I should think about
 * this some more.
 *
 * Something about this is redundant, I can't remember what though :-(
 */
//pthread_t transfer[MAX_TRANSFER_THREADS]; 
//pthread_t forward[MAX_FORWARD_THREADS];

/* Thread which listens for requests and spawns other threads to
 * handle them.
 */
//pthread_t request_thread;

/* One of these threads is created whenever an incoming packet
 * arrives.
 */
//pthread_t request_handler[MAX_REQUEST_HANDLERS];

/* I think I will unify all threads into one array.  Whenever a thread
 * is created using create_thread, thindex is incremented.
*/
pthread_t threads[MAX_THREADS];
/* Thread index */
int thindex=0;

pthread_mutex_t thindex_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t sindex_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t list_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t resindex_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t read_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t write_mutex = PTHREAD_MUTEX_INITIALIZER;

extern int sindex;
extern int sockets[MAX_SOCKETS];

extern struct file * results;
extern int resindex;
extern int resmax;

extern int data_sock;

//int resindex=0;
//int resmax=0;

//extern unsigned long sdbm(unsigned char *);
//extern int equalkeys(void *, void *);


/* For GTK */
static GtkWidget *conf_dialog; 

static GtkWidget *about_win, *clist, *dlist;

static GtkWidget *search_win, *search_query;
//static GtkWidget *hellomessage_entry;
//static pthread_t tid;

extern struct hashtable *checksums;
extern struct hashtable *reverse; /* reverse lookup for checksums */

extern int query_replies;

/* Currently selected row/column in the list. */
int list_selected[2]={0};


/* Increment thindex and return the value before the increment. */
int inc_thindex(int inc) {
  int t;
  pthread_mutex_lock(&thindex_mutex);
  t=thindex;
  thindex += inc;
  pthread_mutex_unlock(&thindex_mutex);
  return t;
}

/* Increment sindex and return the value before the increment. */
int inc_sindex(int inc) {
  int s;
  pthread_mutex_lock(&sindex_mutex);
  s=sindex;
  sindex += inc;
  pthread_mutex_unlock(&sindex_mutex);
  return s;
}

int inc_resindex(int inc) {
  int r;
  pthread_mutex_lock(&resindex_mutex);
  r=resindex;
  resindex += inc;
  pthread_mutex_unlock(&resindex_mutex);
  return r;
}


int die(char * msg) {
  fprintf(stderr, "%s\n", msg);
  amp2p_cleanup();
  xmms_remote_quit(amp2p.xmms_session);
  return 1;
}


void amp2p_cleanup(void) {
  int i;

  /* Do the things we need to do before we die. */

  end();

  close(data_sock);

  /*  if(search_win)
      gtk_widget_destroy(search_win);*/

  /* close all sockets */
  pthread_mutex_lock(&sindex_mutex);
  for(i=0;i<sindex;i++)
    printf("closing.. %d\n", close(sockets[i]));
  sindex=0;
  pthread_mutex_unlock(&sindex_mutex);

  /* cancel all threads. */
  pthread_mutex_lock(&thindex_mutex);
  for(i=0;i<thindex;i++) 
    pthread_cancel(threads[i]);  
  thindex=0;
  pthread_mutex_unlock(&thindex_mutex);

  //  pthread_mutex_destroy(&sindex_mutex);
  //  pthread_mutex_destroy(&thindex_mutex);  

  //  pthread_cancel(request_thread);
  //  pthread_cancel(tid);
/* cancel the rest of the threads too! */
}

GeneralPlugin *get_gplugin_info(void) {
  return(&amp2p);
}

/* This will need to be changed to be more specific once a config file
   is needed. */
void read_config(GtkWidget *wid, gpointer data) {
  /*  gchar *configfile;
  ConfigFile *config;

  configfile = g_strconcat(g_get_home_dir(), CONFIGFILE, NULL);
  if((config = xmms_cfg_open_file (configfile)) != NULL) {
    xmms_cfg_read_string(config, "amp2p", "amp2pmessage", &amp2pmessage);
  }
  g_free(configfile); */
}


/* Initialisation of plugin */
void amp2p_init(void) {
  int i=0;

  /* We should close all sockets just in case. */
  for(i=0;i<MAX_SOCKETS;i++)
    close(sockets[i]);
  
  pthread_mutex_lock(&thindex_mutex);
  thindex=0;
  pthread_mutex_unlock(&thindex_mutex);

  pthread_mutex_lock(&sindex_mutex);
  sindex=0;
  pthread_mutex_unlock(&sindex_mutex);

  pthread_mutex_unlock(&list_mutex);
  resindex=0;
  pthread_mutex_unlock(&list_mutex);


  /* do the things that we must do before listening can commence */

  create_thread((void *)&listen_for_data, NULL);
  create_thread((void *)&listen_for_requests, NULL);
  
  /* create thread to md5 hash the playlist */
  create_thread((void *)&hash_playlist, NULL);

  ui();

  return;
}


/* Change this too! */

void amp2p_about(void) {
  GtkWidget *label, *bigbox;
  
  if(about_win)
    return;
  about_win = gtk_window_new(GTK_WINDOW_DIALOG);
  gtk_window_set_title(GTK_WINDOW(about_win), ("About"));
  gtk_window_set_policy(GTK_WINDOW(about_win), FALSE, FALSE, FALSE);
  gtk_window_set_position(GTK_WINDOW(about_win), GTK_WIN_POS_MOUSE);
  
  bigbox = gtk_vbox_new(FALSE, 5);
  gtk_container_add(GTK_CONTAINER(about_win), bigbox);
  
  label = gtk_label_new((gchar *)"\nGet a dog up ya!!\n");
  gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
  gtk_container_add(GTK_CONTAINER(bigbox), label);
  
  gtk_widget_show_all(about_win);
  
}

void amp2p_config(void) {
  
}
void xmms_search() {

  char * sq = gtk_entry_get_text(search_query);


  results = malloc(MAX_RESULTS * sizeof(struct file));
  resindex = 0;
  resmax = 10; // change to MAX_RESULTS

  query_replies = 0;

  query(sq);

  pthread_mutex_lock(&list_mutex);
  printf("Covington\n");
  gtk_clist_clear(clist);
  pthread_mutex_unlock(&list_mutex);


  return;
}

void xmms_download() {

  struct file target = results[list_selected[0]];

  /*
  target.name = malloc(results[list_selected[0]].name + 1);
  strcpy(target.name, results[list_selected[0]].name);
  target.title = malloc(results[list_selected[0]].title + 1);
  strcpy(target.title, results[list_selected[0]].title);
  target.length = results[list_selected[0]].length;
  target.offset = results[list_selected[0]].offset;
  memcpy(target.checksum, results[list_selected[0]].checksum, MD5_DIGEST_LENGTH);
  */

  //  printf("Selected: %d, Title: %s, Length: %d\n",
  //	 list_selected[0],
  //	 target.title,
  //	 target.length);
  
  download(target, resindex);

  return;
}

/**
 * Update the percentage scale on the downloads list.
 */
void update_download(int num, float percent) {

  char * s;
  pthread_mutex_lock(&list_mutex);    

  s = malloc(15); //TODO
  sprintf(s, "%f%%", percent);

  gtk_clist_set_text(dlist, num, 2, s);
  pthread_mutex_unlock(&list_mutex);    
}

ssize_t con_write(int fd, const void *buf, size_t count){
  //pthread_mutex_lock(&write_mutex);
  write(fd, buf, count);
  //pthread_mutex_unlock(&write_mutex);
}

ssize_t con_read(int fd, void *buf, size_t count){
  //pthread_mutex_lock(&read_mutex);
  read(fd, buf, count);
  //pthread_mutex_unlock(&read_mutex);
}

/**
 * Is the file in the list
 */
int is_in_list(struct file f, int list) {
  return 1;
}

/**
 * Append to the results list.
 */
int list_append(struct file f, int list){

  gchar * clist_row[4] = {"foo", "bar", "baz", "qux"};
  int res_count = 0;
  unsigned char * in;
  struct file * out;
  int length;
  int num;

  length = encode_file(f, &in);

  /*
  in->name=malloc(strlen(f.name)+1);
  strcpy(in->name, f.name);
  in->title=malloc(strlen(f.title)+1);
  strcpy(in->title, f.title);
  in->length=f.length;
  in->offset=f.offset;
  memcpy(in->checksum, f.checksum, MD5_DIGEST_LENGTH);
  */

  //  res_count = hashtable_count(search_results);  
  //  hashtable_insert(search_results, &res_count, in);

  if (f.name!=NULL&&f.title!=NULL&&f.checksum!=NULL&&f.length!=NULL){
    pthread_mutex_lock(&list_mutex);    
    clist_row[0] = malloc(strlen(f.title)+1);
    strcpy(clist_row[0], f.title);  
    clist_row[1] = malloc(30); //TODO: fix this 30
    sprintf(clist_row[1], "%dKB", f.length/1024);
    clist_row[2] = malloc(strlen(f.name)+1);
    strcpy(clist_row[2], f.name);
    clist_row[3] = malloc(34);
    strcpy(clist_row[3], checksum_str(f.checksum)); 
    
    switch(list) {
    case CLIST:
      num = gtk_clist_append(clist, clist_row);
      break;
    case DLIST:
      num = gtk_clist_append(dlist, clist_row);
      break;
    }

    //    usleep(10000);
    pthread_mutex_unlock(&list_mutex);
  }

  pthread_mutex_lock(&resindex_mutex);    
  if(resindex == resmax) {
    /* we need to expand the results array */
    resmax *= 2;
    results = realloc(results, resmax * sizeof(struct file)); 
  }

    results[resindex].name=malloc(strlen(f.name)+1);
    strcpy(results[resindex].name, f.name);
    results[resindex].title=malloc(strlen(f.title)+1);
    strcpy(results[resindex].title, f.title);
    results[resindex].offset=f.offset;
    memcpy(results[resindex].checksum, f.checksum, MD5_DIGEST_LENGTH);
    results[resindex].length=f.length;
    

    printf("resindex: %d, resmax: %d, length: %dKB, title: %s\n", resindex, resmax, results[resindex].length / 1024, results[resindex].title);

    resindex++;
    pthread_mutex_unlock(&resindex_mutex);    

    //    inc_resindex(1);
    
  //    printf("list_append says results = %p\n\n", search_results);

  return num;
}

/**
 * Launch the GUI.
 */
void ui(void) {
  
  GtkWidget *label, *bigbox, *search_button, *hbox, *scrolled_window;
  GtkWidget *download_scrolled_window;
  GtkWidget *download_button;

  gchar *titles[4] = { "Title", "Size" , "File", "Checksum"};

  search_win = gtk_window_new(GTK_WINDOW_DIALOG);

  /*  gtk_signal_connect (GTK_OBJECT (search_win), "destroy",
      GTK_SIGNAL_FUNC (amp2p_cleanup), NULL); */

  gtk_window_set_title(GTK_WINDOW(search_win), ("Amp2p Search"));
  //  gtk_window_set_policy(GTK_WINDOW(search_win), FALSE, FALSE, FALSE);
  //  gtk_window_set_position(GTK_WINDOW(search_win), GTK_WIN_POS_MOUSE);
  gtk_widget_set_usize(GTK_WIDGET(search_win), 600, 550);

  gtk_container_set_border_width(GTK_CONTAINER (search_win), 10);

  bigbox = gtk_vbox_new(FALSE, 5);
  hbox = gtk_hbox_new(FALSE, 5);
  search_query = gtk_entry_new();

  //  gtk_container_set_border_width(GTK_CONTAINER (bigbox), 5);
  gtk_container_set_border_width(GTK_CONTAINER (hbox), 10);

  gtk_container_add(GTK_CONTAINER(search_win), bigbox);
  
  label = gtk_label_new((gchar *)"Search for a song:");
  gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);

  download_button = gtk_button_new_with_label("Aquire");
  gtk_signal_connect (GTK_OBJECT (download_button), "clicked",
		      GTK_SIGNAL_FUNC (xmms_download), NULL);

  search_button = gtk_button_new_with_label("Search");
  gtk_signal_connect (GTK_OBJECT (search_button), "clicked",
		      GTK_SIGNAL_FUNC (xmms_search), NULL);

  /* list stuff */
  
  scrolled_window = gtk_scrolled_window_new (NULL, NULL);
  download_scrolled_window = gtk_scrolled_window_new (NULL, NULL);

  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (download_scrolled_window),
				  GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);

  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
				  GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);

  gtk_widget_set_usize(GTK_WIDGET(scrolled_window), 480, 200);
  gtk_widget_set_usize(GTK_WIDGET(download_scrolled_window), 480, 200);
  
  gtk_box_pack_end(GTK_BOX(bigbox), download_button, TRUE, TRUE, 0);
  gtk_widget_show(download_button);

  gtk_box_pack_end(GTK_BOX(bigbox), download_scrolled_window, TRUE, TRUE, 0);
  gtk_widget_show (download_scrolled_window);

  gtk_box_pack_end(GTK_BOX(bigbox), scrolled_window, TRUE, TRUE, 0);
  gtk_widget_show (scrolled_window);


  clist = gtk_clist_new_with_titles(4, titles);
  dlist = gtk_clist_new_with_titles(4, titles);
  // hlist = gtk_clist_new(5); 
 
  gtk_clist_set_column_width (GTK_CLIST(clist), 0, 300);
  gtk_clist_set_shadow_type (GTK_CLIST(clist), GTK_SHADOW_OUT);
  gtk_clist_set_column_width (GTK_CLIST(clist), 1, 80);
  gtk_clist_set_column_width (GTK_CLIST(clist), 2, 250);
  gtk_clist_set_column_width (GTK_CLIST(clist), 3, 230);
  gtk_clist_set_selection_mode(GTK_CLIST(clist), GTK_SELECTION_SINGLE);
  gtk_signal_connect(GTK_OBJECT(clist), "select_row",
		     GTK_SIGNAL_FUNC(xmms_list_select),
		     NULL);

  gtk_clist_set_column_width (GTK_CLIST(dlist), 0, 300);
  gtk_clist_set_shadow_type (GTK_CLIST(dlist), GTK_SHADOW_OUT);
  gtk_clist_set_column_width (GTK_CLIST(dlist), 1, 80);
  gtk_clist_set_column_width (GTK_CLIST(dlist), 2, 250);
  gtk_clist_set_column_width (GTK_CLIST(dlist), 3, 230);
  gtk_clist_set_selection_mode(GTK_CLIST(dlist), GTK_SELECTION_SINGLE);


  gtk_container_add(GTK_CONTAINER(scrolled_window), clist);
  gtk_widget_show(clist);

  gtk_container_add(GTK_CONTAINER(download_scrolled_window), dlist);
  gtk_widget_show(dlist);

  gtk_container_add(GTK_CONTAINER(bigbox), label);
  gtk_container_add(GTK_CONTAINER(bigbox), hbox);
  gtk_container_add(GTK_CONTAINER(hbox), search_button);
  gtk_container_add(GTK_CONTAINER(hbox), search_query);
  //  gtk_container_add(GTK_CONTAINER(bigbox), clist);

  
  gtk_widget_show_all(search_win);
}

void xmms_list_select(GtkWidget *clist, gint row, gint column,
		      GdkEventButton *event,
		      gpointer data) {

  gchar * text;
  int i;

  gtk_clist_get_text(GTK_CLIST(clist), row, column, &text);

  list_selected[0] = row;
  list_selected[1] = column;

  //  for(i=0;i<resindex;i++)
  //    printf("i: %d, title: %s\n", i, results[i].title);

  //  g_print("Row: %d, Column: %d, text: %s\n\n", row, column, text);
  //  g_print("Row: %d, Column: %d, text: %s\n\n", row, column,
  //	  results[list_selected[0]].title);

}

/* Create a thread that runs func. */
int create_thread(void * func, void * arg) {
  pthread_attr_t attr;

  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
  pthread_attr_setschedpolicy(&attr, SCHED_OTHER);
  pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

  printf("create_thread: thindex = %d\n", thindex+1);

  return(pthread_create(&threads[inc_thindex(1)], &attr, func, arg));
}

void exit_thread(void) {
  printf("exit_thread: thindex = %d\n", thindex);
  inc_thindex(-1);
  pthread_exit(NULL);
}

/**
 * Fills the hashtable with the md5sums of the entire playlist.  For
 * easy access!
 */
void hash_playlist() {
  
  int length = xmms_remote_get_playlist_length(amp2p.xmms_session);
  int i;
  char * title;
  char * file;
  char * r;
  unsigned char * s;

  for(i=0;i<length;i++){
    title = malloc(strlen(xmms_remote_get_playlist_title(amp2p.xmms_session, i))+1);
    strcpy(title, xmms_remote_get_playlist_title(amp2p.xmms_session, i));
    file = malloc(strlen(xmms_remote_get_playlist_file(amp2p.xmms_session, i))+1);
    strcpy(file,xmms_remote_get_playlist_file(amp2p.xmms_session, i));

    r = malloc(strlen(file)+strlen(title)+HEADER_SIZE);
    strcpy(r, file);
    strcat(r, ";"); /* TODO: #DEFINE this delimiter */
    strcat(r, title);

    if(hashtable_search(checksums, (void *)r) != NULL){
      /* Checksum was found in hashtable, yay! */

    }else{
      printf("Adding to hashtable: %s\n", title);
      
      s = malloc(MD5_DIGEST_LENGTH);
      memcpy(s, md5_wait(file, HASH_WAIT), MD5_DIGEST_LENGTH);
      if(s != -1) {
	hashtable_insert(checksums, r, s);
	hashtable_insert(reverse, s, r);
      }
      //      print_checksum(s);

      pthread_yield(); //TODO: abstract this.
    }
  }
  exit_thread();
  return;
}

/**
 * Search the current playlist for files matching the file req.
 * Probably matching just the checksum.
 *
 * On second thoughts, I don't think this is needed.  We just need to
 * look up the hashtable.  Going through and generating hashes for
 * everything would take too long and is going on anyway.  Maybe in a
 * later version...?
 */
struct file checksum_search_playlist(struct file req) {

  //  int length = xmms_remote_get_playlist_length(amp2p.xmms_session);

}

/* TODO: Fix comment below. */
/**
 * Searches current xmms playlist.  Puts the filename of playlist
 * items which contain a substring matching query_string into the
 * string array r.  Returns number of matching playlist items.
 */
int search_playlist(char * query_string, struct msg m) {

  //TODO: clean up this function.

#ifdef DEBUG
  debug("search_playlist: begin");
#endif

  int length = xmms_remote_get_playlist_length(amp2p.xmms_session);
  int i, j=0, k=0, n=0;
  char * title;
  char * file;
  unsigned char *s;
  char checksum_string[33];
  char *cs;
  unsigned int file_length = 0;
  FILE * f;
  char * r;
  struct file we_have;
  
  
  for(i=0;i<length;i++){
    
    title = malloc(strlen(xmms_remote_get_playlist_title(amp2p.xmms_session, i))+1);
    strcpy(title, xmms_remote_get_playlist_title(amp2p.xmms_session, i));
    file = malloc(strlen(xmms_remote_get_playlist_file(amp2p.xmms_session, i))+1);
    strcpy(file, xmms_remote_get_playlist_file(amp2p.xmms_session, i));
    
    /* TODO: Change to case insensitive */
    if(strstr(lower(title), lower(query_string))
       || strstr(lower(file), lower(query_string))){
      if(j < MAX_RESULTS) {
	

	r = malloc(strlen(file) + strlen(title) + 2);
	
	strcpy(r, file);
	strcat(r, ";"); /* TODO: #DEFINE this delimiter */
	strcat(r, title);	  
	
	if(hashtable_search(checksums, (void *)r) != NULL){
	  /* Checksum was found in hashtable, yay! */
	  printf("Found in hashtable:'%s' => '%p'\n", r, hashtable_search(checksums, r));
	  s = malloc(MD5_DIGEST_LENGTH);
	  memcpy(s, hashtable_search(checksums, r), MD5_DIGEST_LENGTH);
	  
	}else{
	  printf("Adding to hashtable.\n");
	  
	  s = malloc(MD5_DIGEST_LENGTH);
	  memcpy(s, md5(file), MD5_DIGEST_LENGTH);
	  if(s != -1){
	    hashtable_insert(checksums, r, s);
	    hashtable_insert(reverse, s, r);
	  }
	  
	  // pthread_yield(); // need?
	}
	
	f = fopen(file, "r");
	
	if(f == NULL){
	  die("File has gone somewhere.  Someone has stoeld j00r m3gabyt3z!!");
	  return;
	}

	fseek(f, 0, SEEK_END);
	file_length = ftell(f);
	fclose(f);

	printf("\n%s, has length %d\n\n\n", file, file_length);
	
	we_have.name = malloc(strlen(file)+1);
	strcpy(we_have.name, file);
	we_have.title = malloc(strlen(title)+1);
	strcpy(we_have.title, title);
	we_have.length = file_length;
	memcpy(we_have.checksum, s, MD5_DIGEST_LENGTH);
	
	send_query_reply(m, we_have);

	j++;
      }
    }
  }
  
#ifdef DEBUG
  debug("search_playlist: end");
#endif

  return(j);
}


/**
 * Returns the sha1 message digest of the file.
 */
unsigned char * sha1(char * file) {

  SHA_CTX c;
  unsigned char md[SHA_DIGEST_LENGTH];
  unsigned char chunk[CHUNK_SIZE];
  unsigned char *temp;
  int in;
  int n;
  int diff;
  struct timeb * start, * end;

  start = malloc(sizeof(struct timeb));
  end = malloc(sizeof(struct timeb));

  ftime(start);
  printf("START...");
  fflush(stdout);

  in = open(file, O_RDONLY);
  SHA1_Init(&c);
  
  while(n = read(in, chunk, CHUNK_SIZE)){
    n = (n < CHUNK_SIZE) ? n : CHUNK_SIZE;
    SHA1_Update(&c, chunk, n);
  }

  SHA1_Final(md, &c);
  close(in);

  printf("END\n");
  fflush(stdout);
  ftime(end);
  
  diff = ((end->time-start->time)*1000) + (end->millitm-start->millitm);

  printf("SHA1: %dms\t", diff);

  return md;
}


/**
 * Returns the md5 message digest of the file.
 */
unsigned char * md5(char * file) {

#ifdef DEBUG
  debug("md5: begin");
#endif


  MD5_CTX c;
  unsigned char md[MD5_DIGEST_LENGTH];
  unsigned char chunk[CHUNK_SIZE];
  unsigned char *temp;
  int in;
  int n;
  int diff;
  struct timeb * start, * end;

  start = malloc(sizeof(struct timeb));
  end = malloc(sizeof(struct timeb));

  ftime(start);
  printf("START...");
  fflush(stdout);

  in = open(file, O_RDONLY);
  if(in == -1)
    return -1;

  MD5_Init(&c);
  
  while(n = read(in, chunk, CHUNK_SIZE)){
    n = (n < CHUNK_SIZE) ? n : CHUNK_SIZE;
    MD5_Update(&c, chunk, n);
  }

  MD5_Final(md, &c);
  close(in);

  printf("END - ");
  fflush(stdout);
  ftime(end);
  
  diff = ((end->time-start->time)*1000) + (end->millitm-start->millitm);

  printf("MD5: %dms\t", diff);
  print_checksum(md);

#ifdef DEBUG
  debug("md5: end");
#endif

  return(md);
}

/* Urgh, redundant! */

unsigned char * md5_wait(char * file, int wait) {

  MD5_CTX c;
  unsigned char md[MD5_DIGEST_LENGTH];
  unsigned char chunk[CHUNK_SIZE];
  unsigned char *temp;
  int in;
  int n;
  int diff;
  struct timeb * start, * end;

  start = malloc(sizeof(struct timeb));
  end = malloc(sizeof(struct timeb));

  ftime(start);
  printf("START...");
  fflush(stdout);

  in = open(file, O_RDONLY);
  if(in == -1)
    return -1;

  MD5_Init(&c);
  
  while(n = read(in, chunk, CHUNK_SIZE)){
    n = (n < CHUNK_SIZE) ? n : CHUNK_SIZE;
    MD5_Update(&c, chunk, n);

    // sleep a small amount so as not to chunk the CPU.

    usleep(wait);
  }

  MD5_Final(md, &c);
  close(in);

  printf("END - ");
  fflush(stdout);
  ftime(end);
  
  diff = ((end->time-start->time)*1000) + (end->millitm-start->millitm);

  printf("MD5: %dms\t", diff);
  print_checksum(md);

  return(md);
}


/* REDUNDANT KLUDGE!!! */

/* Taken from http://www.cl.cam.ac.uk/users/cwc22/hashtable/
 *
 * Many thanks to Christopher Clark
 * (<firstname>.<lastname>@cl.cam.ac.uk) whose hashtable code this is
 * (see hashtable.c, hashtable.h and hashtable_private.h).
 */

static unsigned long sdbm(unsigned char *str)
{
  unsigned long hash = 0;
  int c;
  
  while (c = *str++)
    hash = c + (hash << 6) + (hash << 16) - hash;
  
  return hash;
}


static int equalkeys(void *k1, void *k2)
{
    return (0 == memcmp(k1,k2,strlen(k1)));
}



