/* This example code was written by Juliusz Chroboczek.
   You are free to cut'n'paste from it to your heart's content. */

/* For crypt */
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/signal.h>

#include "ks.h"
#include "histedit.h"
#include "sodium.h"

#define MAX_BOOTSTRAP_NODES 20
static ks_sockaddr_t bootstrap_nodes[MAX_BOOTSTRAP_NODES];
static ks_sockaddr_t bind_nodes[MAX_BOOTSTRAP_NODES];
static int num_bootstrap_nodes = 0;
static int num_bind_nodes = 0;

/* The call-back function is called by the DHT whenever something
   interesting happens.  Right now, it only happens when we get a new value or
   when a search completes, but this may be extended in future versions. */
static void callback(void *closure, ks_dht_event_t event, const unsigned char *info_hash, const void *data, size_t data_len)
{
  if(event == KS_DHT_EVENT_SEARCH_DONE) {
    printf("Search done.\n");
  } else if(event == KS_DHT_EVENT_VALUES) {
    const uint8_t *bits_8 = data;
    const uint16_t *bits_16 = data;
    
    printf("Received %d values.\n", (int)(data_len / 6));
    printf("Recieved %u.%u.%u.%u:%u\n", bits_8[0], bits_8[1], bits_8[2], bits_8[3], ntohs(bits_16[2]));
  } else {
    printf("Unhandled event %d\n", event);
  }
}

void json_cb(struct dht_handle_s *h, const cJSON *msg, void *arg)
{
  char *pretty = cJSON_Print((cJSON *)msg);
  
  printf("Received json msg: %s\n", pretty);

  free(pretty);
}

static char * prompt(EditLine *e) {
  return "dht> ";
}

static dht_handle_t *h;


typedef struct dht_globals_s {
  int s;
  int s6;
  int port;
  int exiting;
} dht_globals_t;

void *dht_event_thread(ks_thread_t *thread, void *data)
{
  dht_globals_t *globals = data;
  
  while(!globals->exiting) {
	  ks_dht_one_loop(h, 0);
	  ks_sleep(1000000);
  }

  return NULL;
}

int
main(int argc, char **argv)
{
	dht_globals_t globals = {0};
    int i;
    //int have_id = 0;
    //char *id_file = "dht-example.id";
	int ipv4 = 0, ipv6 = 0;
	int autobind = 0;
    int opt;
    EditLine *el;
    History *myhistory;
    int count;
    const char *line;
    HistEvent ev;
    ks_status_t status;
    static ks_thread_t *threads[1]; /* Main dht event thread */
    ks_pool_t *pool;
	int err = 0;
    unsigned char alice_publickey[crypto_sign_PUBLICKEYBYTES] = {0};
    unsigned char alice_secretkey[crypto_sign_SECRETKEYBYTES] = {0};

    ks_init();

    el = el_init("test", stdin, stdout, stderr);
    el_set(el, EL_PROMPT, &prompt);
    el_set(el, EL_EDITOR, "emacs");
    myhistory = history_init();
    history(myhistory, &ev, H_SETSIZE, 800);
    el_set(el, EL_HIST, history, myhistory);
    globals.port = 5309;


    ks_global_set_default_logger(7);

    while(1) {
        opt = getopt(argc, argv, "46ap:b:B:");
        if(opt < 0)
            break;

        switch(opt) {
		case '4':
			ipv4 = 1;
			break;
		case '6':
			ipv6 = 1;
			break;
		case 'a':
			autobind = 1;
			break;
		case 'p':
			globals.port = atoi(optarg);
			break;
		case 'b':
		case 'B': {
			char ip[80];
			int port = globals.port;
			char *p;
			ks_set_string(ip, optarg);

			if ((p = strchr(ip, '+'))) {
				*p++ = '\0';
				port = atoi(p);
			}
			if (opt == 'B') {
				printf("Adding bootstrap node %s:%d\n", ip, port);
				ks_addr_set(&bootstrap_nodes[num_bootstrap_nodes++], ip, port, 0);
			} else {
				printf("Adding binding %s:%d\n", ip, port);
				ks_addr_set(&bind_nodes[num_bind_nodes++], ip, port, 0);
			}
		}
			break;
        default:
            goto usage;
        }
    }
	
    if(argc < 2)
        goto usage;

    i = optind;

    if(globals.port <= 0 || globals.port >= 0x10000)
        goto usage;


	ks_dht_af_flag_t af_flags = 0;

	if (ipv4) {
		af_flags |= KS_DHT_AF_INET4;
	}

	if (ipv6) {
		af_flags |= KS_DHT_AF_INET6;
	}

    /* Init the dht. */
    status = ks_dht_init(&h, af_flags, NULL, globals.port);

    if(status != KS_STATUS_SUCCESS) {
        perror("dht_init");
        exit(1);
    }

    for(i = 0; i < num_bind_nodes; i++) {
		ks_dht_add_ip(h, bind_nodes[i].host, bind_nodes[i].port);
	}

	if (autobind) {
		ks_dht_set_param(h, DHT_PARAM_AUTOROUTE, KS_TRUE);
	}

	ks_dht_start(h);

	ks_dht_set_callback(h, callback, NULL);

    ks_pool_open(&pool);
    status = ks_thread_create_ex(&threads[0], dht_event_thread, &globals, KS_THREAD_FLAG_DETATCHED, KS_THREAD_DEFAULT_STACK, KS_PRI_NORMAL, pool);

    if ( status != KS_STATUS_SUCCESS) {
		printf("Failed to start DHT event thread\n");
		exit(1);
    }

    /* For bootstrapping, we need an initial list of nodes.  This could be
       hard-wired, but can also be obtained from the nodes key of a torrent
       file, or from the PORT bittorrent message.

       Dht_ping_node is the brutal way of bootstrapping -- it actually
       sends a message to the peer.  If you're going to bootstrap from
       a massive number of nodes (for example because you're restoring from
       a dump) and you already know their ids, it's better to use
       dht_insert_node.  If the ids are incorrect, the DHT will recover. */
    for(i = 0; i < num_bootstrap_nodes; i++) {
        dht_ping_node(h, &bootstrap_nodes[i]);
        usleep(random() % 100000);
    }

    printf("TESTING!!!\n");
    err = crypto_sign_keypair(alice_publickey, alice_secretkey);
    printf("Result of generating keypair %d\n", err);

    ks_dht_store_entry_json_cb_set(h, json_cb, NULL);
	
    while ( !globals.exiting ) {
		line = el_gets(el, &count);

		if (count > 1) {
			int line_len = (int)strlen(line) - 1;
			char *cmd_dup = strdup(line);
			char *argv[8] = { 0 };
			int argc = 0;

			history(myhistory, &ev, H_ENTER, line);

			if ( cmd_dup[line_len] == '\n' ) {
				cmd_dup[line_len] = '\0';
			}
			argc = ks_separate_string(cmd_dup, " ", argv, (sizeof(argv) / sizeof(argv[0])));

			if (!strncmp(line, "quit", 4)) {
				globals.exiting = 1;
			} else if (!strncmp(line, "show_bind", 9)) {
				const ks_sockaddr_t **bindings;
				ks_size_t len = 0;
				int i;

				ks_dht_get_bind_addrs(h, &bindings, &len);

				for (i = 0; i < len; i++) {
					printf("Bind addr %s:%d\n", bindings[i]->host, bindings[i]->port);
				}

			} else if (!strncmp(line, "ping ", 5)) {
				const char *ip = line + 5;
				ks_sockaddr_t tmp;
				char *p;

				while ((p = strchr(ip, '\r')) || (p = strchr(ip, '\n'))) {
					*p = '\0';
				}

				ks_addr_set(&tmp, ip, globals.port, 0);
				dht_ping_node(h, &tmp);
			} else if (!strncmp(line, "find_node ", 9)) {
				/* usage: find_node ipv[4|6] [40 character node id] [40 character target id] */
				ks_bool_t ipv6 = strncmp(argv[1], "ipv4", 4);
				(void) argc; /* Check to see if it's the right length, else print usage */
				ks_dht_api_find_node(h, argv[2], argv[3], ipv6);
			} else if (!strncmp(line, "loglevel", 8)) {
				ks_global_set_default_logger(atoi(line + 9));
			} else if (!strncmp(line, "peer_dump", 9)) {
				dht_dump_tables(h, stdout);
			} else if (!strncmp(line, "generate_identity", 17)) {
				/* usage: generate_identity [identity key: first_id] */
				/* requires an arg, checks identity hash for arg value. 
				   
				   if found, return already exists.
				   if not found, generate sodium public and private keys, and insert into identities hash.
				 */
			} else if (!strncmp(line, "print_identity_key", 18)) {
				/* usage: print_identity_key [identity key] */
			} else if (!strncmp(line, "message_mutable", 15)) {
			  char *input = strdup(line);
			  char *identity_key = input + 16;
			  char *identities[2] = { identity_key, NULL };
			  char *message_id = NULL;
			  char *message = NULL;
			  cJSON *output = NULL;
			  int idx = 17; /* this should be the start of the message_id */
			  for ( idx = 17; idx < 100 && input[idx] != '\0'; idx++ ) {
			    if ( input[idx] == ' ' ) {
			      input[idx] = '\0';
			      message_id = input + 1 + idx;
			      break;
			    }
			  }

			  for ( idx++; idx < 100 && input[idx] != '\0'; idx++ ) {
			    if ( input[idx] == ' ' ) {
			      input[idx] = '\0';
			      message = input + 1 + idx;
			      break;
			    }
			  }

			  /* Hack for my testing, so that it chomps the new line. Makes debugging print nicer. */
			  for ( idx++; input[idx] != '\0'; idx++) {
			    if ( input[idx] == '\n' ) {
			      input[idx] = '\0';
			    }
			  }
				/* usage: message_mutable [identity key] [message id: asdf] [your message: Hello from DHT example]*/
				/*
				  takes an identity, a message id(salt) and a message, then sends out the announcement.
				 */
			  output = cJSON_CreateString(message);
			  
			  ks_dht_send_message_mutable_cjson(h, alice_secretkey, alice_publickey,
												identities, message_id, 1, output, 600);
			  free(input);
			  cJSON_Delete(output);
			} else if (!strncmp(line, "message_immutable", 15)) {
			  /* usage: message_immutable [identity key] */
				/*
				  takes an identity, and a message, then sends out the announcement.
				 */
			} else if (!strncmp(line, "message_get", 11)) {
				/* usage: message_get [40 character sha1 digest b64 encoded]*/

				/* MUST RETURN BENCODE OBJECT */
			} else if (!strncmp(line, "message_get_mine", 16)) {
				/* usage: message_get [identity key] [message id: asdf]*/
				/* This looks up the message token from identity key and the message id(aka message salt) */
				
				/* MUST RETURN BENCODE OBJECT */
			} else if (!strncmp(line, "add_buddy", 9)) {
				/* usage: add_buddy [buddy key] [buddy public key] */

			} else if (!strncmp(line, "get_buddy_message", 17)) {
				/* usage: get_buddy_message [buddy key] [buddy message_id] */

				
			} else if (!strncmp(line, "search", 6)) {
				if ( line_len > 7 ) {
					unsigned char hash[20];
					memcpy(hash, line + 7, 20);

					if(globals.s >= 0) {
						dht_search(h, hash, 0, AF_INET, callback, NULL);
					}
				} else {
					printf("Your search string isn't a valid 20 character hash. You entered [%.*s] of length %d\n", line_len - 7, line + 7, line_len - 7);
				}
			} else if (!strncmp(line, "announce", 8)) {
				if ( line_len == 29 ) {
					unsigned char hash[20];
					memcpy(hash, line + 9, 20);

					if(globals.s >= 0) {
						dht_search(h, hash, globals.port, AF_INET, callback, NULL);
					}
				} else {
					printf("Your search string isn't a valid 20 character hash. You entered [%.*s]\n", line_len - 7, line + 7);
				}
			} else {
				printf("Unknown command entered[%.*s]\n", line_len, line);
			}

			free(cmd_dup);
		}
    }

    {
        struct sockaddr_in sin[500];
        struct sockaddr_in6 sin6[500];
        int num = 500, num6 = 500;
        int i;
        i = dht_get_nodes(h, sin, &num, sin6, &num6);
        printf("Found %d (%d + %d) good nodes.\n", i, num, num6);
    }


    history_end(myhistory);
    el_end(el);
    dht_uninit(&h);
    ks_shutdown();
    return 0;
    
 usage:
    printf("Usage: dht-example [-a] [-4] [-6] [-p <port>] [-b <ip>[+<port>]]...\n"
           "                   [-B <ip>[+<port>]]...\n");
    exit(0);
}


/* For Emacs:
 * Local Variables:
 * mode:c
 * indent-tabs-mode:t
 * tab-width:4
 * c-basic-offset:4
 * End:
 * For VIM:
 * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
 */