/* $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
 */

/* This prorgam contains some of the code from the hello world xmms
 * plugin by iso:crash.  This is greatly appreciated because I had no
 * idea how to create an xmms plugin!  All in xmms-amp2p.[ch] now.
 */

//#include <sys/types.h>
#include <unistd.h>          
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <string.h>
#include <malloc.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/time.h> /* need? */
#include <sys/timeb.h>
#include <errno.h>
#include <math.h>
#include <regex.h>

#include <openssl/md5.h>

#include "amp2p.h"
#include "hashtable.h"
#include "hashtable_itr.h"


/* Global variables */

/* Nodes keep a list of nodes which they can connect to directly.
 * This is stored in this array of integers.  The integers in the
 * array are ip addresses, both having a length of 4 bytes.  Hopefully
 * this won't change in the future.  There is a maximum of
 * MAX_NODELIST nodes in the nodelist.
 */
struct node nodelist[MAX_NODELIST] = {0};

/* IP address of this node */
unsigned int local_address=0;

/* Must make sure all sockets are closed on cleanup of module!  Here
 * is a possible solution...  An array of global sockets and an index
 * to them.  Index prob needs mutex.
 */
int sockets[MAX_SOCKETS];
/* socket index */
int sindex=0;

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

/* Search results */
struct file * results;
int resindex=0;
int resmax=0;

/* Array containing the five most recent queries received */
struct msg recent_queries[MAX_RECENT] = {0};
struct msg recent_queries_sent[MAX_RECENT] = {0};

int query_replies = 0;

/* The server socket which listens for incoming data.  For closing purposes. */
int data_sock;

/* Array of chunks being currently downloaded.  Only uniquely
   identified by checksum. From this you should be able to identify
   which parts of a file are missing (from starts and offsets).  I
   sure pity the person who has to read/modify this code, it is
   horrible! */
struct file downloads[MAX_DOWNLOADS] = {0};

/* Index to the above array.  Points to the next free spot in the
   array. */
int dindex = 0;

void load_nodelist(char * file) {

  FILE * in;
  char * addr;
  int j;

  printf("Loading nodelist: %s\n", file);

  in = fopen(file, "r");
  
  if(in == NULL) {
    fprintf(stderr, "Unable to load nodelist");
    return;
  }

  j=0;
  addr = malloc(20); //TODO: Why 20?  Because I said so!
  while(j < MAX_NODELIST && fgets(addr, 25, in) != NULL){
    addr[strlen(addr)-1] = '\0';
    //    printf("addr: '%s'\n", addr);
    nodelist[j].addr = inet_addr(addr);
    j++;
    
  }

  fclose(in);

  return;
}


/* Prints errors to stderr then exits with a return value of -1 */
void error(char * error_string) {
  fprintf(stdout, "ERROR -- %s\n", error_string);
  exit(-1);
}
#ifdef DEBUG
/* Prints debug statements to stderr */
void debug(char * debug_string) {
  fprintf(stderr, "%s\n", debug_string);
}
#endif

/* Prints a message to stdout. */
void print_msg(struct msg m) {
  char type[15];
  char * data;
  
  switch(m.type){
  case QUERY:
    strcpy(type, "QUERY");
    break;
  case REQUEST:
    strcpy(type, "REQUEST");
    break;
  case DATA:
    strcpy(type, "DATA");
    break;
  case PING:
    strcpy(type, "PING");
    break;
  case PING_REPLY:
    strcpy(type, "PING_REPLY");
    break;
  case QUERY_REPLY:
    strcpy(type, "QUERY_REPLY");
    break;
  default:
    strcpy(type, "INVALID_TYPE");
    break;
  }

  data = malloc(m.length+1);
  strncpy(data, m.data, m.length);
  data[m.length]='\0';

  printf("Origin: %s, From: %s, To: %s, Type: %s, Length: %d, Data: %s\n", i2d(ntohl(m.origin)), i2d(ntohl(m.from)), i2d(ntohl(m.to)), type, m.length, data);

  return;
}


/* Takes a message in the format described above and converts it into
 * a message structure.
 */
struct msg decode_msg(unsigned char * buf) {
  struct msg m;
  int i=0;
#ifdef DEBUG
  //  debug("decode_msg: begin");
#endif  

  /* Insert error checking here! */

  /* There is most probably a much better and more elegant way to do
     this.  I'll figure it out later. */

  m.origin = (buf[0]&0xff)<<24|(buf[1]&0xff)<<16|(buf[2]&0xff)<<8|(buf[3]);
  m.from = (buf[4]&0xff)<<24|(buf[5]&0xff)<<16|(buf[6]&0xff)<<8|(buf[7]);
  m.to = (buf[8]&0xff)<<24|(buf[9]&0xff)<<16|(buf[10]&0xff)<<8|(buf[11]);
  m.type = (buf[12]&0xff)<<8|(buf[13]);
  m.length = (buf[14]&0xff)<<24|(buf[15]&0xff)<<16|(buf[16]&0xff)<<8|(buf[17]);
  
  m.data = malloc(m.length);
  
  for(i=0;i<m.length;i++)
      m.data[i]=buf[i+18];

#ifdef DEBUG
  //  debug("decode_msg: end");
#endif
  return(m);
}

/* Does the opposite of decode_msg.  Takes a message structure and
 * spits out a message in byte array format.
 */
unsigned char * encode_msg(struct msg m) {
  unsigned char * buf;
  int i=0;
#ifdef DEBUG
  debug("encode_msg: begin");
#endif


  buf = malloc(18 + m.length); /* The header is 18 bytes long */

  for(i=0;i<4;i++)
    buf[i]=(m.origin>>((3-i)*8))&0xff;
  for(i=0;i<4;i++)
    buf[i+4]=(m.from>>((3-i)*8))&0xff;
  for(i=0;i<4;i++)
    buf[i+8]=(m.to>>((3-i)*8))&0xff;
  for(i=0;i<2;i++)
    buf[i+12]=(m.type>>((1-i)*8))&0xff;
  for(i=0;i<4;i++)
    buf[i+14]=(m.length>>((3-i)*8))&0xff;
  for(i=0;i<m.length;i++)
    buf[i+18]=m.data[i];

  /* 01111011 00101101 01000011 01011001 */
  /* 123      45       67       89       */   

#ifdef DEBUG
  debug("encode_msg: end");
#endif
  return(buf);
}

/* The following four functions are the main grunt of the work that
 * gets done when any message is received.  They handle the four main
 * message types.  Guess what they are!!
 */


/**
 * Handle a query.  Searches the playlist for songs matching the query
 * string and then sends replies back to the origin.
 *
 * TODO: Ignore a query if we are already handling that exact query!!
 * At this very moment, there is a thread toiling away on that query!
 * Won't somebody think of the cycles!!!
 * 
 */
void handle_query(struct msg m) {

  char * query_string;
  int i, j=0;
  int num_results;
  

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

  /* For queries the data section of the message is a query string.*/

  print_msg(m);

  query_string = (char *)malloc(m.length+1);
  for(i=0;i<m.length;i++)
    query_string[i]=m.data[i];
  query_string[m.length]='\0';

  fprintf(stderr, "\nquery string: %s\n", query_string);

  /* Send to the rest of your nodelist */
    while(nodelist[j].addr && j < MAX_NODELIST){
      if ( /*!is_recent_query_sent(m) && !is_recent_query(m)
	     && */ (nodelist[j].addr != m.origin)
		   && (m.from != nodelist[j].addr))  
	_send_msg(m.origin, local_addr(), nodelist[j].addr,
		  m.type, m.length, m.data);
      j++;
    }


  /* Search playlist for song with a substring match of the query
     string. */

  num_results = search_playlist((char *)query_string, m);

  if(num_results > 0){

    //    for(i=0;i<num_results;i++)
    //      printf("%d: %s\n", i, result[i]);
    
    /* Send each result back as a single message */
   
 
    //      for(i=0;i<num_results;i++){
	/* send a query reply. Origin remains same, from is us, to is back to origin, type is QUERY_REPLY, length = strlen(result[i])*/
    //	_send_msg(m.origin, local_addr(), m.origin, QUERY_REPL[6~Y,
    //	    strlen(result[i]), result[i]);
  //      }
    
  }else{
    /* No results!  Forward this query to nodelist. Forward even if
       results? 
    Don't forward if:
        - you just got a query like this.
	- you are the origin.
	- routing loops?
	- TTL?
    */
  }



  
    //add_recent_query(m);


#ifdef DEBUG
  debug("handle_query: end");
#endif
  return;
}

add_recent_query(struct msg m){

  int j;

  /* Add the just received query to the recent queries */
  for(j=MAX_RECENT-1;j>0;j--){
    recent_queries[j].origin = recent_queries[j-1].origin;
    recent_queries[j].from = recent_queries[j-1].from;
    recent_queries[j].to = recent_queries[j-1].to;
    recent_queries[j].type = recent_queries[j-1].type;
    recent_queries[j].length = recent_queries[j-1].length;
    if(recent_queries[j-1].data){
      recent_queries[j].data = malloc(strlen(recent_queries[j-1].data));
      strcpy(recent_queries[j].data, recent_queries[j-1].data);
    }
  }

  recent_queries[0].origin = m.origin;
  recent_queries[0].from = m.from;
  recent_queries[0].to = m.to;
  recent_queries[0].type = m.type;
  recent_queries[0].length = m.length;
  recent_queries[0].data = malloc(strlen(m.data));
  strcpy(recent_queries[0].data, m.data);
  /* nicer way to do this? */

  return;
}

add_recent_query_sent(struct msg m){

  int j;

  /* Add the just received query to the recent queries */
  for(j=MAX_RECENT-1;j>0;j--){
    recent_queries_sent[j].origin = recent_queries_sent[j-1].origin;
    recent_queries_sent[j].from = recent_queries_sent[j-1].from;
    recent_queries_sent[j].to = recent_queries_sent[j-1].to;
    recent_queries_sent[j].type = recent_queries_sent[j-1].type;
    recent_queries_sent[j].length = recent_queries_sent[j-1].length;
    if(recent_queries_sent[j-1].data){
      recent_queries_sent[j].data = malloc(strlen(recent_queries_sent[j-1].data));
      strcpy(recent_queries_sent[j].data, recent_queries_sent[j-1].data);
    }
  }

  recent_queries_sent[0].origin = m.origin;
  recent_queries_sent[0].from = m.from;
  recent_queries_sent[0].to = m.to;
  recent_queries_sent[0].type = m.type;
  recent_queries_sent[0].length = m.length;
  recent_queries_sent[0].data = malloc(strlen(m.data));
  strcpy(recent_queries_sent[0].data, m.data);
  /* nicer way to do this? */

  return;
}

/* is the query represented by m in the recent queries list? */
int is_recent_query(struct msg m){
  int i;

  printf("\n\nRECENT QUERY: %d, %s\n\n", m.length, m.data);

  for(i=0;i<MAX_RECENT;i++)
    if(recent_queries[i].origin == m.origin
       && recent_queries[i].from == m.from
       && recent_queries[i].to == m.to
       && recent_queries[i].type == m.type
       && recent_queries[i].length == m.length
       && !strncmp(recent_queries[i].data, m.data, m.length)){
      printf("\n\nSPAM ALERT!!!\n\n");
      return 1;
    }
  
  return 0;

}

/* is the query represented by m in the recent queries sent list? */
int is_recent_query_sent(struct msg m){
  int i;
  
  printf("\n\nRECENT QUERY SENT: %d, %s\n\n", m.length, m.data);

  for(i=0;i<MAX_RECENT;i++)
    if(recent_queries_sent[i].origin == m.origin 
       && recent_queries_sent[i].from == m.from
       && recent_queries_sent[i].to == m.to
       && recent_queries_sent[i].type == m.type
       && recent_queries_sent[i].length == m.length
       && !strncmp(recent_queries_sent[i].data, m.data, m.length)){
      printf("\n\nSPAM ALERT!!!\n\n");
      return 1;
    }
  
  return 0;

}


/**
 * Handle a query reply.  Yay, there was an answer to our search!
 */
void handle_query_reply(struct msg m){

  //testies!
  struct hashtable_itr * i;
  int *key;

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

  print_msg(m);

  // only if it is a unique ip address
  //query_replies++;

  list_append(decode_file(m.data), CLIST);

  /* Added in code for testing ! */
  //  printf("handle_query_reply says results = %p\n\n", search_results);
  //  printf("res: '%s'\n", hashtable_search(search_results, &num));

  /*
  i = hashtable_iterator(search_results);
  do {
    key = (int *)hashtable_iterator_key(i);
    printf("key: %d, val: %s\n", *key,
	   hashtable_iterator_value(i));

  }while(hashtable_iterator_advance(i));
  */
  /* end add in */



#ifdef DEBUG
  debug("handle_query_reply: end");
#endif
  return;
}

/**
 * Decode a byte stream into a file structure.
 */
struct file decode_file(unsigned char * s) {

  struct file f;
  //  char * first;
  char * name;
  char * title;
  unsigned char * checksum;
  int piv;

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


  //  for(i=0;i<;i++)
  //    printf("%p ", s);
  //  printf("\n\n");

  //  first = malloc(strlen(s));
  //  strcpy(first, s);

  //  printf("s: %s\n", s);

  //  query = malloc(m.length+1);
  //  for(i=0;i<m.length;i++)
  //    query[i] = m.data[i];

  //  query[m.length] = '\0';

  piv = strlen(s)+1;

  f.name = malloc(piv-1);
  f.title = malloc(piv-1);
  //  f.checksum = malloc(MD5_DIGEST_LENGTH);

  f.offset = (s[piv]&0xff)<<24|(s[piv+1]&0xff)<<16|(s[piv+2]&0xff)<<8|(s[piv+3]);
  f.length = (s[piv+4]&0xff)<<24|(s[piv+5]&0xff)<<16|(s[piv+6]&0xff)<<8|(s[piv+7]);

  sscanf(s, "%[^;];%[^;];%[^;]", f.name, f.title, f.checksum);

  //  printf("\n%s, %s\n", f.name, f.title);
  //  print_checksum(f.checksum);
  //  printf("%d, %d\n\n", f.offset, f.length);

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


  return f;
}

/**
 * Handle and incoming file request.  Firstly we have to decode the
 * request.  Then we search the playlist for this exact file (name,
 * title and checksum matching).  Then we send to the origin the
 * section of the file requested.  The sending of the file is a bit
 * complex.  Here's what happens:
 *
 *    - The region of the file is that defined by offset and length.
 *    - We send chunks of the file with a maximum size of DATA_SIZE.
 *    - Send chunks sequentially from the region until we have sent
 *      the entire region.
 *    _ Then we quit.
 */ 
void handle_file_request(struct msg m) {
#ifdef DEBUG
  debug("handle_file_request: begin");
#endif

  struct file req = decode_file(m.data);
  struct hashtable_itr * i;
  char * s;
  char * filename;
  unsigned char buf[BUFLEN];
  FILE * in;
  int numchunks=0;
  int j;
  int len;

  unsigned int file_len;
  char * file;

  int sock;
  struct sockaddr_in serv_addr;
  //  struct hostent * server;


/* Send to the rest of your nodelist */
  j=0;
   while(nodelist[j].addr && j < MAX_NODELIST){
      if ( (nodelist[j].addr != m.origin)
		   && (m.from != nodelist[j].addr))  
	_send_msg(m.origin, local_addr(), nodelist[j].addr,
		  m.type, m.length, m.data);
      j++;
    }



  i = hashtable_iterator(reverse);

  do {
    if (hashtable_iterator_key(i) != NULL) {

      if(memcmp(hashtable_iterator_key(i),
		req.checksum, MD5_DIGEST_LENGTH) == 0) {
	printf("Found a match!  %s\n", hashtable_iterator_value(i));
	/* Now we schend some data! */

	s = malloc(strlen(hashtable_iterator_value(i)));
	strcpy(s, hashtable_iterator_value(i));
	
	strcpy(s, strtok(s, ";"));

	printf("Filename: %s\n", s);
	

	/* Open a socket to the origin of this request */
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if(sock < 0)
	  error("Opening socket");
	bzero((char *)&serv_addr, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = m.origin;
	serv_addr.sin_port = htons(LISTEN_PORT);
	if(connect(sock, &serv_addr, sizeof(serv_addr)) < 0)
	  error("Connecting");

	file_len = m.length;
	
	for(j=0;j<4;j++) {
	  buf[j] = (file_len>>((3-j)*8))&0xff;
	  //printf("buf[%d] = %d ... %d\n", j, buf[j], file_len);
	}

	write(sock, buf, 4); /* write the length */
	write(sock, m.data, file_len); /* write the metadata */

	in = fopen(s, "r");
	/* open from offset to offset + length - 1 */
	
	fseek(in, req.offset, SEEK_SET);

	//	printf("Sending chunks from %d to %d\n", req.offset,
	//	req.offset + req.length - 1);
	printf("Sending to sock: %d", sock);

	while (ftell(in) < req.length + req.offset) {
	  //	  printf(".", ftell(in), ftell(in) + DATA_SIZE);
	  numchunks++;
	  len = fread(buf, 1, BUFLEN, in);
	  
	  con_write(sock, buf, len);
	  /*	  printf("Writing %d bytes to socket %d.  File location is %dKB\n", len, sock,
		  ftell(in)/1024); */
	}

	//printf("%d chunks of %d bytes = %dKB\n", numchunks,
	//     DATA_SIZE, (numchunks * DATA_SIZE) / 1024);

	fclose(in);
	close(sock);

      }
    }

  } while (hashtable_iterator_advance(i));

  //TODO: Decode this.

  //  printf("Request for %s, %dKB, %s\n", req.title,
  //	 req.length/1024, checksum_str(req.checksum));

  //  printf("cs: '%s', hs: %s\n", checksum_str(req.checksum),
  // hashtable_search(reverse, req.checksum));

  if(hashtable_search(reverse, req.checksum) != NULL)
    printf("Found a match!\n");


#ifdef DEBUG
  debug("handle_file_request: end");
#endif
  return;
}

void handle_data(struct data * d) {
  
  unsigned char buf[BUFLEN] = {0};
  char * file;
  struct file f;
  int c = 0;
  int b;
  int len;
  FILE * out;
  char * filename;
  char * path;
  int i;
  int listnum;
  int percent=0;
  int last_percent=0;

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


  // TODO: Fix double download file fuxing

  /* Read in the first four bytes which correspond to an integer.
     This integer represents the length of the file metadata in the
     stream. */
  b = con_read(d->sock, buf, 4);
  if(b != 4) {
    printf("Bad data\n");
    return; /* bad data */
  }

  //  for(i=0;i<4;i++)
  //    printf("buf[%d] = %d\n", i, buf[i]);
  //  printf("\n");

  len = (buf[0]&0xff)<<24|(buf[1]&0xff)<<16|(buf[2]&0xff)<<8|(buf[3]);
  file = malloc(len);

  if(len > BUFLEN) {
    printf("Buffer too small!\n");
    return;
  }

  //printf("Len: %d\n", len);

  read(d->sock, file, len);

  f = decode_file(file);

  printf("Handling data from sock: %d (offset: %d, length: %d)\n", d->sock,
	 f.offset, f.length);

  /* Add the chunk to the downloads array */
  if (dindex < MAX_DOWNLOADS) {
    downloads[dindex].
  } else {
    return; /* Whoops, max downloads.  There is probably a better way
	       to handle this! */
  }

  // TODO: This is dodgy as, seperation of concerns man!
  
  if (!is_in_list(f, DLIST)) {
    listnum = list_append(f, DLIST);
  } else {
    // File is already downloading.... what do we do??  Check if we
    // already have this chunk... if not then add it to the file,
    // although the file will probably have a lock on it, well I guess
    // we wait.  If we already have this chunk then we do nothing I
    // guess.
    /* How am I going to keep track of which parts of a file have been
       downloaded???  Really need a proper list of current downloads,
       not this clist crapulence! */
  }

  path = malloc(100);
  strcpy(path, "/tmp/");

  filename = malloc(strlen(f.name)+1);
  strcpy(filename, f.name);
  reverse_str(filename);
  strcpy(filename, strtok(filename, "/"));
  reverse_str(filename);
  path = realloc(path, strlen(path) + strlen(filename) + 2);
  //printf("Foo\n");
  strcat(path, filename);

  //printf("Filename: '%s'", path);

  //  return;

  out = fopen(path, "r+");
  if (out == NULL) {
    out = fopen(path, "w");
  }
  fseek(out, f.offset, SEEK_SET);

  printf("Retrieving socket %d\n", d->sock);

  while(b = read(d->sock, buf, BUFLEN)) {
    fwrite(buf, 1, b, out);
    c+=b;
    //printf(".");

    percent = (100 * c) / f.length;

    if(percent != last_percent){
      update_download(listnum, last_percent);
      //      fprintf(stderr, "%s: %d%%, %d%%\n", path, percent, last_percent);
    }

    last_percent = percent;
  }

  printf("\nTransfer of %s complete, %dKB  total\n", f.title, c/1024);
  
  fclose(out);

  /* Calculate the file checksum, if it matches the checksum then yay,
     the file is done.  If not then we need to figure out which chunks
     we need and aquire them pronto.  Aquiring will be done via some
     file requests.  Checking which parts of the file we need is
     another matter, some thought is required. */


#ifdef DEBUG
  debug("handle_data: end");
#endif
  return;
}

void handle_ping(struct msg m) {

  struct msg reply;

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

  reply.origin = m.origin;
  reply.from = local_addr();
  reply.to = m.from;
  reply.type = PING_REPLY;
  reply.length = m.length;
  reply.data = m.data;

  send_msg(reply);

#ifdef DEBUG
  debug("handle_ping: end");
#endif
  return;  
}

void handle_ping_reply(struct msg m) {
  int diff; /* Round trip time in milliseconds. */
  struct timeb * tp;
  int secs, millisecs;

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

  /* w00t, we have a ping reply.  Now, check the timestamps.  Get the
     round trip time by subtracting timestamp in data from current
     timestamp.  If ping is smaller than largest ping in nodelist then
     kick that node off! */

  tp = malloc(sizeof(struct timeb));
  ftime(tp); /* get current time */
  sscanf(m.data, "%d:%d", &secs, &millisecs); /* get timestamp from ping. */
  diff = ((tp->time-secs)*1000) + (tp->millitm-millisecs);

#ifdef DEBUG
  fprintf(stderr, "Ping reply from '%s', %d milliseconds.\n", i2d(ntohl(m.origin)), diff);
  debug("handle_ping_reply: end");
#endif
  return;
}

/* Send a ping message to the node with ip address to. */
void ping(unsigned int to) {
  struct msg m;
  struct timeb * tp;
  char t[20];
  
  tp = malloc(sizeof(struct timeb));
  ftime(tp);
  sprintf(t, "%d:%d", (int)tp->time, tp->millitm);

  m.origin=m.from=local_addr();
  //printf("%s\n", t);
  m.to=to;
  m.type=PING;
  m.data = malloc(strlen(t));
  strcpy(m.data, t); /* Storing this as a string probably is not as
			efficient as I'd like */
  m.length=(strlen(t));

  send_msg(m);
}

/* return the ip address identifying the local host. */
unsigned int local_addr() {
  char hostname[100]; /* is this number large enough? */
  struct hostent * h;
  struct in_addr addr;

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

  /* This function does not work if there is no entry in /etc/hosts.
     This is bad!!  This is a definite must fix! */

  /* This whole business of setting a variable to the local address
     and using that is probably not so good for the situation where
     the address changes during execution. Will this ever be a
     problem?? */

  if(!local_address){
    gethostname(hostname, sizeof(hostname));
    h = gethostbyname(hostname);
    memcpy(&addr, h->h_addr, sizeof(struct in_addr));
    printf("local host name: '%s', addr: %s\n", hostname, inet_ntoa(addr));
    local_address=addr.s_addr;
#ifdef DEBUG
  debug("local_addr: end");
#endif
    return(addr.s_addr);
  }else {
#ifdef DEBUG
  debug("local_addr: end");
#endif
  return(local_address);
  }
}


/* Data was originally going to be sent using UDP as well however on
 * closer inspection I realised that I would pretty much have to do
 * something TCP like to get it to work.  SO I am just going to go
 * with TCP for data transfer.  The TCP connection is going to be
 * between the origin of the file request and the node which is
 * sending the file.
 *
 * Whenever a file chunk is being received a new TCP connection will
 * be active.  The data received will be written to the file by the
 * method write_file which makes sure only one thread is writing to
 * the file at once.
 */
void listen_for_data() {
  
  int sock, s;
  struct sockaddr_in serv_addr, cli_addr;
  int len;
  struct data d;

  sock = data_sock = socket(AF_INET, SOCK_STREAM, 0);
  if(sock < 0)
    error("Opening socket");

  bzero((char *)&serv_addr, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = INADDR_ANY;
  serv_addr.sin_port = htons(LISTEN_PORT);

  if(bind(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
    error("Binding socket");

  listen(sock, MAX_DOWNLOADS);

  while(1) {

    len = sizeof(cli_addr);
    s = accept(sock, (struct sockaddr *) &cli_addr, &len);
    
    if(s < 0)
      error("Accepting");
    
    d.sock = s;
    bzero((char *)&d.addr, sizeof(d.addr));
    memcpy(&d.addr, &cli_addr, sizeof(cli_addr));
    
    create_thread((void *)&handle_data, (void *)&d);
    
  }

}

/* This is what the child process does all the time.  Listen on the
 * correct port and do whatever is required of you.  Service
 * search/file requests.
 */
void listen_for_requests() {
  struct msg m;
  char * testmsg;

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

  /* Initialise things before listening */

  initial();

  while (1) {
    
     /* Spawn a thread to handle the incoming data when it accepts or
     * whatever.  That thread will then receive the data and maybe spawn
     * a different thread to handle that specific request.  Or is that
     * redundant...?
     */
    
    /* How am I going to handle the re-creation of threads after they
     * have finished handling a request.  How am I going to decide
     * what i is? -- Hehe, you is stoopid...
     */

    m = rcv_msg();
    //  print_msg(m);
    testmsg = encode_msg(m);
    //pthread_create(&request_handler[i++], NULL, (void *)&handle_request, &m); 
    

    create_thread((void *)&handle_request, (void *)testmsg);
    usleep(1000);
    //handle_request(testmsg);

    
  }

  
#ifdef DEBUG
  debug("listen_for_requests: end");
#endif
  
  exit_thread();
}


/* Handle a request coming in over sock.  Decodes the message into a
 * message structure, checks the type and invokes the appropriate
 * handler function.
 *
 * There have to be some definite routing rules in here!!!  There are
 * generic cases when requests should be ignored.  I think it is
 * correct to have the routing rules in here because they don't really
 * belong in with the individual request handlers.
 */
void handle_request(char * buf) {
  struct msg m;

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

    m = decode_msg(buf);

    //    print_msg(m);

  switch(m.type){
  case QUERY:
    handle_query(m);
    break;
  case REQUEST:
    handle_file_request(m);
    break;
  case DATA:
    //    handle_data(m);
    break;
  case PING:
    handle_ping(m);
    break;
  case PING_REPLY:
    handle_ping_reply(m);
    break;
  case QUERY_REPLY:
    handle_query_reply(m);
    break;
  default:
    /* It really doesn't seem to like this!! */
    //    error("handle_request: unknown message type!");
    break;
  }

#ifdef DEBUG
  debug("handle_request: end");
#endif
  
  exit_thread();
  return;
}

/* Returns a string representation of the IP address in dotted quad
 * format. Whoops, use inet_ntoa instead.
 */
char * i2d(unsigned int address) {
  unsigned char quads[4]={0};
  unsigned char * buf;
  int i=0;
  for(i=0;i<4;i++)
      quads[i]=(address>>((3-i)*8))&0xff;
  buf = malloc(16);
  sprintf(buf, "%u.%u.%u.%u", quads[0], quads[1], quads[2], quads[3]);
  return(buf);
}

/**
 * Send a message with all info specified in attributes, not a
 * structure.
 */
void _send_msg(unsigned int origin, unsigned int from, unsigned int to,
	       short int type, int length, char * data) {
  struct msg m = {origin, from, to, type, length, 0};

#ifdef DEBUG
  debug("_send_msg: start");
#endif


  m.data = malloc(length);
  memcpy(m.data, data, length);

  send_msg(m);

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

  return;
}

/**
 * Send out a file request for target.  This gets tricky too in a
 * minute.  We have to try and figure out how many people have replied
 * to us with a search result.  This is done by counting the unique
 * number (say n) search results for this particular file.  This data
 * is stored elsewhere (TODO: figure out where).  We then divide the
 * file up into n regions and send out n file requests.  Sounds good
 * eh?
 */
void download(struct file target, int count){
  int i;
  int j = 0;
  int c;
  int offset=0;
  int length=0;

  c = 10;

  // TODO: Proper chunking

  /* Suppose we send out a file request for each chunk out to a
     different node.  This should be propagated accross rhe network.
     When we are handling the incoming data then we can send out the
     rest of the file requests...  Or we could just sent out one file
     request for the first chunk and take it from there.  The
     possibilities are endless.  */

  length = target.length / c;

  printf("\nDividing into %d chunks of %d bytes...total: %d\n", c, length, target.length);
  
  for(i=0;i<c;i++) {
    offset = i * length;
    if(i==c-1)
      length = target.length - offset;
    
    //printf("From %d to %d\n", offset, length+offset-1);
    
    j=0;
    while(nodelist[j].addr && j < MAX_NODELIST)
      send_file_request(nodelist[j++].addr, local_addr(), target, offset, length);

  }
  


  return;
}

/**
 * Send a file request for target to a node.  Offset and length
 * together request sections of the file.  If length == 0 then the
 * section of the file from offset to the end of the file will be
 * sent.  It follows that if both the offset and length are zero then
 * the entire file will be requested.
 *
 * The structure of the data in a file request message is as follows:
 *
 *     target.name;target.title;target.checksum\0offsetlength
 *
 * The \0 indicates the end of the string containing the name, title
 * and checksum of the file.  The next four bytes represent the offset
 * and the final four bytes the length.  There is no indication of the
 * end of the data apart from the obvious "length of data" field in
 * the message header.
 */
void send_file_request(unsigned int to, unsigned int origin, struct file target,
		       int offset, int length) {

  char * data;
  int data_length;

  data_length = encode_file(target, &data);

  _send_msg(origin, local_addr(), to,
	    REQUEST, data_length, data);
  
  printf("Title: %s, offset: %d, length: %d\n", target.title, offset, length);
  
  return;
  
}

/**
 * Send a query to all nodes in nodelist.
 */
void query(char * query_string){

  int i=0;

  while(nodelist[i].addr && i < MAX_NODELIST)
    send_query(nodelist[i++].addr, query_string);

  return;
}

/**
 * Send a query to a node (to) represented (the query) by
 * query_string.
 */
void send_query(unsigned int to, char * query_string) {
  
  struct msg m = {local_addr(), local_addr(), to, QUERY,
		  strlen(query_string), query_string};

  printf("query says: %s, to: %s\n", query_string, i2d(ntohl(to)));

  //  if(!is_recent_query_sent(m)){
    send_msg(m);
    add_recent_query_sent(m);
    //  }
  return;
}

/**
 * Send a query reply.
 */
void send_query_reply(struct msg m, struct file f) {
  
  unsigned char * data;
  int length;
  int i=0;

  f.offset=0;

  //  printf("ABOUT TO ENCODE!\n\n");

  length = encode_file(f, &data);

  _send_msg(m.origin, local_addr(), m.origin, QUERY_REPLY,
  	    length, data); 

}


/**
 * Encode a file struct to a byte stream.  Bytestream is stored in
 * data and length is returned.
 */
int encode_file(struct file f, unsigned char ** d) {

  //  unsigned char ** data = &s;
  unsigned char * data;
  unsigned char * checksum;
  int i, j;
  int data_length;

#ifdef DEBUG
  debug("encode_file: begin");
#endif
 
  
  /* Sundries are for the two semicolons and the null character. */
  data_length = strlen(f.name) + strlen(f.title) 
    + MD5_DIGEST_LENGTH + 2*sizeof(int) + 3;

  data = malloc(data_length); 

  strcpy(data, f.name);
  strcat(data, ";");
  strcat(data, f.title);
  strcat(data, ";");

  checksum = malloc(MD5_DIGEST_LENGTH + 1);
  memcpy(checksum, f.checksum, MD5_DIGEST_LENGTH);
  checksum[MD5_DIGEST_LENGTH] = '\0';

  strcat(data, checksum);

  j = strlen(data) + 1;

  for(i=0;i<4;i++)
    data[i+j] = (f.offset>>((3-i)*8))&0xff;
  for(i=0;i<4;i++)
    data[i+4+j] = (f.length>>((3-i)*8))&0xff;

  *d = malloc(data_length);
  memcpy(*d, data, data_length);

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

  return(data_length);

}


/* Send a message, specifying all details of that message, including
 *  origin and from.  Low-level, should not be used directly.
 */
void send_msg(struct msg m) {
  
  int sd, rc;
  struct sockaddr_in cliAddr, remoteServAddr;
  struct hostent *h;
  unsigned char * buf;
  
  #ifdef DEBUG
  debug("send_msg: start");
  #endif

  buf = malloc(m.length + HEADER_SIZE);
  memcpy(buf, encode_msg(m), m.length + HEADER_SIZE);

  h = gethostbyname("localhost");

  /*  printf("Sending data to '%s' (IP : %s) \n", h->h_name,
      inet_ntoa(*(struct in_addr *)h->h_addr_list[0])); */

  remoteServAddr.sin_family = h->h_addrtype;
  /*  memcpy((char *) &remoteServAddr.sin_addr.s_addr, 
      h->h_addr_list[0], h->h_length); */
  remoteServAddr.sin_addr.s_addr = m.to;
  remoteServAddr.sin_port = htons(LISTEN_PORT);

  /* socket creation */
  sd = socket(AF_INET,SOCK_DGRAM,0);
  if(sd<0) {
    printf("cannot open socket \n");
    exit(1);
  }
  
  /* bind any port */
  cliAddr.sin_family = AF_INET;
  cliAddr.sin_addr.s_addr = htonl(INADDR_ANY);
  cliAddr.sin_port = htons(0);
  
  rc = bind(sd, (struct sockaddr *) &cliAddr, sizeof(cliAddr));
  if(rc<0) {
    printf("cannot bind port\n");
    exit(1);
  }

    rc = sendto(sd, buf ,m.length+18, 0, 
		(struct sockaddr *) &remoteServAddr, 
		sizeof(remoteServAddr));

    if(rc<0) {
      printf("cannot send data\n");
      close(sd);
      //      exit(1);
    }  

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

  return;
}



/* Create a UDP socket on LISTEN_PORT.  Listen for a message, receive
 *  it and return it.
 */
struct msg rcv_msg() {
  int sock_index=inc_sindex(1); /* get the index of the next socket. */
  size_t len;
  struct sockaddr_in serv_name;
  char buf[PACK_SIZE];
  struct msg m;

#ifdef DEBUG
  debug("rcv_msg: start");
#endif

  bzero(&serv_name, sizeof(serv_name));
  serv_name.sin_family = AF_INET;
  serv_name.sin_port = htons(LISTEN_PORT);
  
  if((sockets[sock_index] = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    error("rcv_msg: socket opening");

  //printf("%d, %p, %d\n", sockets[sock_index], serv_name, sizeof(serv_name));
  
  if(bind(sockets[sock_index], (struct sockaddr *)&serv_name, sizeof(serv_name)) < 0)
    error("rcv_msg: binding socket");
  
  len = sizeof(serv_name);

  recvfrom(sockets[sock_index], &buf, PACK_SIZE, 0, (struct sockaddr *)&serv_name, &len);

  close(sockets[sock_index]); 
  inc_sindex(-1); /* release the socket index */

  m = decode_msg(buf);

  //  print_msg(m);

#ifdef DEBUG
  debug("rcv_msg: end");
#endif
  return(m);
}

/* Convert a string ip address in dotted quad format to a 32 bit
 * integer. You had better make sure it is well formed cos there aint
 * no error checking!
 */
int dotted_to_int(char * dotted) {
  int address=0;
  return(address);
}

/* This is a stub because it is meant to be run as a plugin for winamp
 *  and xmms.  The abstraction for the interface to the plugins will
 *  be done later, after the p2p stuff works properly.
 */

void amp2p_thread(void *args) {

  /* Have to do several things.  First spawn a thread to listen for
   * requests.  Then handle user interface stuff and user search/file
   * requests.
   */

  char * to;
  int quit = 0;
    
#ifdef DEBUG
  debug("amp2p_thread: begin");
#endif

  local_addr(); /* get the local address of this node */

  /* do something now, like send a message!!*/

  while(!quit){
    
    //    scanf("%s", to);
    //    ping(inet_addr(to));

    //  printf("origin: %u\n", ntohl(inet_addr("192.168.0.1")));
    //  printf("origin: %u\n", ntohl(inet_addr("192.168.0.255")));
    
  }
  
    
#ifdef DEBUG
  debug("amp2p_thread: end");
#endif

  /* or return some other value depending on things */
  return;
}

void initial(void) {

  int i;
  unsigned char s[MD5_DIGEST_LENGTH];

  /* Initialise the hashtable of checksums */

  checksums = create_hashtable(16, sdbm, equalkeys);
  reverse = create_hashtable(16, sdbm, equalkeys);  
  //  search_results = create_hashtable(16, sdbm, equalkeys); 

  read_checksums(CHECKSUMS_FILE);

  load_nodelist(NODELIST);


}

void end(void) {

  write_checksums(CHECKSUMS_FILE);

  hashtable_destroy(checksums, 0);
}

/**
 * Read the checksums from a file into a hashtable.
 */
void read_checksums(char * file) {
  
  FILE * in;
  char * key;
  char * k;
  unsigned char val[MD5_DIGEST_LENGTH];
  unsigned char * v;
  unsigned char all[216];
  int end;
  char t;

  printf("Reading checksums: %s\n", file);

  in = fopen(file, "r");
  
  if(in == NULL) {
    fprintf(stderr, "Unable to load checksums\n");
    return;
  }
  
  fseek(in, 0, SEEK_END);
  end = ftell(in);
  rewind(in);

  printf("Generating reverse lookup.");

  while(ftell(in) < end){

    key = malloc(1024);
    fgets(key, 1024, in);
    key[strlen(key)-1]='\0';
    fread(val, MD5_DIGEST_LENGTH, 1, in);
    v = malloc(MD5_DIGEST_LENGTH);
    memcpy(v, val, MD5_DIGEST_LENGTH);
    hashtable_insert(checksums, key, v);
    hashtable_insert(reverse, v, key);

    putchar('.');

  }
  fclose(in);

  printf(".done\n");

  return;
  
}

/**
 * Write the checksums to a file.
 */
void write_checksums(char * file) {
 
  struct hashtable_itr * i;
  FILE * o;
  int j;
  unsigned char *temp;
  char * key;
  int length;

  /* Write the hashtable to disk. Is this wise?  Yes! */
  
  i = hashtable_iterator(checksums);
  
  if(hashtable_count(checksums)>0){
    o = fopen(file, "w");

    if(o == NULL) {
      fprintf(stderr, "Unable to write checksums\n");
      return;
    }

     do {
       key = malloc(length = strlen(hashtable_iterator_key(i)));
       memcpy(key, hashtable_iterator_key(i), length);
       fwrite(key, length, 1, o);
       fwrite("\n", 1, 1, o);
       temp = malloc(MD5_DIGEST_LENGTH);
       memcpy(temp, hashtable_iterator_value(i), MD5_DIGEST_LENGTH);
       fwrite(temp, MD5_DIGEST_LENGTH, 1, o);
     }while(hashtable_iterator_advance(i));
     fclose(o);
  }
}

/**
 * Print to stdout the md5 checksum represented by c.
 */
void print_checksum(unsigned char * c) {
  int j;
  unsigned char s[MD5_DIGEST_LENGTH];

  memcpy(s, c, MD5_DIGEST_LENGTH);

  for(j=0;j<MD5_DIGEST_LENGTH;j++)
    printf("%x ", s[j]);
  printf("\n");

  return;
}

/**
 * Print to FILE the md5 checksum represented by c.
 */
void fprint_checksum(FILE * where, unsigned char * c) {
  int j;
  unsigned char s[MD5_DIGEST_LENGTH];

  memcpy(s, c, MD5_DIGEST_LENGTH);

  for(j=0;j<MD5_DIGEST_LENGTH;j++)
    fprintf(where, "%x ", s[j]);
  fprintf(where, "\n");

  return;
}

/**
 * Convert checksum into a string representation.
 */
char * checksum_str(unsigned char * c) {
  int j;
  unsigned char s[MD5_DIGEST_LENGTH];
  char out[33] = {0};
  char temp[3] = {0};

  memcpy(s, c, MD5_DIGEST_LENGTH);
  
  for(j=0;j<MD5_DIGEST_LENGTH;j++){
    sprintf(temp, "%.2x", s[j]);
    strcat(out, temp);
  }
  
  //  printf("to string: '%s'\n", out);

  return out;
}

/**
 * Converts s to lowercase.
 */
char * lower(char *s) {
  int i;
  char * l;

  l = malloc(strlen(s)+1);

  for(i=0;i<strlen(s);i++)
    l[i] = (char)tolower(s[i]);

  l[strlen(s)] = '\0';

  //  printf("LOWERISED:: '%s'\n\n", l);

  return l;

}


/**
 * Convert string representation of a checksum into a checksum. String
 * *should* be 32 characters long.  Broken, obselete, don't use!
 */
unsigned char * str_checksum(char * s) {
  unsigned char * c;
  unsigned char bytes[2][16];
  int i;

  if(strlen(s)!=32){
    printf("%d != 32!\n", strlen(s));
    return -1;
  }

  for(i=0;i<16;i++){
    bytes[i%2][i]=s[i*2];
    bytes[(i%2)+1][i]=s[(i*2)+1];
    printf("%c%c ", bytes[i%2][i], bytes[(i%2)+1][i]);
    
  }

  c = malloc(MD5_DIGEST_LENGTH);
  
}

/* 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)));
}


/* Thx to K&R */
void reverse_str(char s[])
{
  int c, i, j;
   
  for (i=0, j=strlen(s)-1; i < j;i++, j--)
  {
    c = s[i];
    s[i] = s [j];
    s[j] = c;
  }
}

