#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include <unistd.h>
#include <stdint.h>
#include <getopt.h>
#include <errno.h>

#include <tdata/usrtc.h>
#include <sexpr/sexp.h>
#include <sntl/connection.h>

/* helper functions */
int __openlistener(int port)
{
  int sd;
  struct sockaddr_in addr;

  sd = socket(PF_INET, SOCK_STREAM, 0);
  bzero(&addr, sizeof(addr));
  addr.sin_family = AF_INET;
  addr.sin_port = htons(port);
  addr.sin_addr.s_addr = INADDR_ANY;
  if ( bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0 ) {
    perror("can't bind port");
    abort();
  }
  if ( listen(sd, 10) != 0 ) {
    perror("Can't configure listening port");
    abort();
  }

  return sd;
}

/*
 * Validation of the SSL certificate
 * this function must be exist.
 */
static int __validate_sslpem(conn_t *co)
{
  return 0;
}

/*
 * validate - authorize user with password
 */
static int __secure_check(conn_t *co)
{
  return 0;
}

/*
 * typed list callback
 */
static int __set_typed_list_callback(conn_t *co, int ch, char *desc)
{
  printf("allowed channel %d (%s)\n", ch, desc);
  return 0;
}

/* list of rpc calls functions */
usrtc_t *fulist;

/* our fake */
usrtc_t *__rettlist(conn_t *c)
{
  return fulist;
}

/* RPC functions implementation */
static int __ar_add(void *data, sexp_t *sx)
{
  sexp_t *lsx = NULL;
  int idx;
  sexp_t *sx_iter;
  if(sexp_list_cdr(sx, &lsx)) {
    printf("Invalid protocol\n");
    return EINVAL;
  }
  
  long int sum = 0;
  SEXP_ITERATE_LIST(lsx, sx_iter, idx) {
    if(!SEXP_IS_TYPE(sx_iter, SEXP_BASIC)) {
      printf("Invalid value type\n");
      return EINVAL;
    }
    sum += atoi(sx->val);
  }
  printf("Result = %ld\n", sum);
  
  return 0;
}

static int __ar_multiply(void *data, sexp_t *sx)
{
  sexp_t *lsx = NULL;
  int idx;
  sexp_t *sx_iter;
  if(sexp_list_cdr(sx, &lsx)) {
    printf("Invalid protocol\n");
    return EINVAL;
  }
  
  long int mult = 0;
  SEXP_ITERATE_LIST(lsx, sx_iter, idx) {
    if(!SEXP_IS_TYPE(sx_iter, SEXP_BASIC)) {
      printf("Invalid value type\n");
      return EINVAL;
    }
    mult *= atoi(sx->val);
  }
  printf("Result = %ld\n", mult);
  return 0;
}

/* define a little bit */
#define DEFAULT_PORT  13133

int main(int argc, char **argv)
{
  char *rootca = NULL, *cert = NULL;
  int port = DEFAULT_PORT;
  int opt;

  while((opt = getopt(argc, argv, "p:r:u:")) != -1) {
    switch(opt) {
    case 'p':
      port = atoi(optarg);
      break;
    case 'r':
      rootca = strdup(optarg);
      break;
    case 'u':
      cert = strdup(optarg);
      break;
    default:
      fprintf(stderr, "usage: %s [-p <PORTNUM>] -r <PATH to Root CA> -u <PATH"
              " to SSL certificate>\n", argv[0]);
      return EINVAL;
    }
  }

  if(!rootca) {
    fprintf(stderr, "Root CA not pointed.\n Failure.\n");
    return EINVAL;
  }

  if(!cert) {
    fprintf(stderr, "User certificate not pointed.\n Failure.\n");
    return EINVAL;
  }

  /* all is fine let's init connection subsystem */
  opt = connections_subsystem_init();
  if(opt) {
    fprintf(stderr, "Subsystem init failed: %d\n", opt);
    return opt;
  }
  /* set wroking certificates */
  opt = connections_subsystem_setsslserts(rootca, cert, cert);
  if(opt) {
    fprintf(stderr, "Subsystem init failed (set SSL x.509 pems): %d\n", opt);
    return opt;
  }

  /* clean up */
  free(rootca);
  free(cert);

  /* set important callbacks to do the security checking */
  connections_subsystem_set_securecheck(conn_sys, __secure_check);
  connections_subsystem_set_sslvalidator(conn_sys, __validate_sslpem);
  /* set a callback, it's optional and doesn't required in server side apps */
  connections_subsystem_set_rpctlist_call(conn_sys, __set_typed_list_callback);

  /* ok, now we need to construct RPC lists (channels) */
  if(!(fulist = malloc(sizeof(usrtc_t)))) {
    fprintf(stderr, "Cannot allocate memory for RPC lists\n Failure.\n");
    return ENOMEM;
  }
  opt = sntl_rpclist_init(fulist);
  if(opt) {
    fprintf(stderr, "Failed to init rpc list\n Failure.\n");
    return opt;
  }
  /* we will add one channel with type id 12 "Demo rpc list" */
  opt = sntl_rpclist_add(fulist, 12, "Demo RPC list", NULL);
  if(opt) {
    fprintf(stderr, "Failed to add typed RPC channel\n Failure.\n");
    return opt;
  }
  /* ok, let's add two functions */
  opt = sntl_rpclist_add_function(fulist, 12, "ar-add", __ar_add);
  if(opt) {
  __fail:
    fprintf(stderr, "Failed to add functions to typed RPC channel\n Failure.\n");
    return opt;
  }
  opt = sntl_rpclist_add_function(fulist, 12, "ar-multiply", __ar_multiply);
  if(opt) goto __fail;
  /* ok, setup it */
  connections_subsystem_setrpclist_function(__rettlist);

  /* now we're ready to run the listen process */
  int srv = __openlistener(port);
  while(1) {
    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);
    conn_t *co = malloc(sizeof(conn_t));

    int client = accept(srv, (struct sockaddr*)&addr, &len); /* accept connection as usual */
    opt = connection_create(co, client); /* create connection, that's all */
    if(opt) return opt;
  }

  return 0;
}