/*
 * Secure Network Transport Layer Library v2 implementation.
 * (sntllv2) it superseed all versions before due to the:
 * - memory consumption
 * - new features such as pulse emitting
 * - performance optimization
 *
 * This is a proprietary software. See COPYING for further details.
 *
 * (c) Askele Group 2013-2015 <http://askele.com>
 *
 */

#include <stdio.h>
#include <dirent.h>
#define __USE_GNU
#include <stdlib.h>
#include <stdarg.h>
#include <sys/types.h>
#include <limits.h>
#ifdef WIN32
#include <Winsock2.h>
#include <signal.h>
#include <ws2tcpip.h>
#else
#include <sys/select.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/resource.h>
#include <netdb.h>
#include <unistd.h>
#include <uuid/uuid.h>
#include <execinfo.h>
#include <netinet/in.h>
#endif

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

#include <pthread.h>
// #include <unistd.h>
#include <stdint.h>
#include <getopt.h>
#include <errno.h>

#define DEBUG
#define FREE(x) { if (x) { free(x); x = NULL; } }
#define MAX_STREAMS INT_MAX

typedef struct __datastream_type {
  int dsid;
  usrtc_node_t node;
  // readdir_r
  DIR *dp;
  struct dirent dent;
  struct dirent *dres;
} datastream_t;

usrtc_t *_rd_streams;
int _rd_last_id = 0;
pthread_rwlock_t _lock;

/* helper functions */
static long __cmp_int(const void *a, const void *b)
{
  return *(int *)a - *(int *)b;
}

inline void dump_dirent(struct dirent *d, char *buf)
{
  if (d == NULL) return;
  strcat(buf, "\"");
  strcat(buf, d->d_name);
  strcat(buf, "\" ");
  switch(d->d_type)
  {
  case DT_REG:
    strcat(buf, "\"regular\"");
    break;
  case DT_DIR:
    strcat(buf, "\"directory\"");
    break;
  case DT_BLK:
    strcat(buf, "\"block\"");
    break;
  case DT_CHR:
    strcat(buf, "\"char\"");
    break;
  case DT_FIFO:
    strcat(buf, "\"fifo\"");
    break;
  case DT_LNK:
    strcat(buf, "\"link\"");
    break;
  case DT_UNKNOWN:
    strcat(buf, "\"unknown\"");
    break;
  default:
    strcat(buf, "\"another\"");
    break;
  }
}

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 SNE_SUCCESS;
}

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

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

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

/* RPC functions implementation */
static int __dir_open(void *m, sexp_t *sx)
{
  sexp_t *lsx = NULL;
  sxmsg_t *msg = (sxmsg_t *)m;
  char *buf;
  size_t ln = 0;

#ifdef DEBUG
  char dbuf[2048];
  print_sexp(dbuf, sizeof(dbuf), sx);
  printf("%s: got sx '%s'\n", __FUNCTION__, dbuf);
#endif /* DEBUG */

  if(sexp_list_cdr(sx, &lsx) || !sx->list->next || !sx->list->next->val) {
    printf("Invalid protocol\n");
    return sxmsg_return(msg, SNE_BADPROTO);
  }

  char *dir_name = strdup(sx->list->next->val);

  if ( usrtc_isfull(_rd_streams) ) {
    return sxmsg_return(msg, SNE_TOOLONG);
  }

  datastream_t *item;
  if ( !(item = malloc(sizeof(datastream_t))) ) {
    return sxmsg_return(msg, SNE_ENOMEM);
  }

  /* find free id */
  pthread_rwlock_wrlock(&_lock);
  do {
    if (_rd_last_id == INT_MAX)
      _rd_last_id = 1;
    else
      ++_rd_last_id;
  } while (usrtc_lookup(_rd_streams, &_rd_last_id));

  /* create rd stream */
  DIR *dp = NULL;
  if ( !(dp = opendir(dir_name)) ) {
    pthread_rwlock_unlock(&_lock);
    FREE(item);
    FREE(dir_name);
    return sxmsg_return(msg, SNE_FAILED);
  }

  FREE(dir_name);

  /* init stream */
  item->dsid = _rd_last_id;
  item->dp = dp;
  item->dres = NULL;
  usrtc_node_init(&(item->node), item);
  usrtc_insert(_rd_streams, &(item->node), &item->dsid);

  buf = sxmsg_rapidbuf(msg);
  ln = snprintf(buf, MAX_RBBUF_LEN, "(dir-stream %d)", item->dsid);

  pthread_rwlock_unlock(&_lock);

#ifdef DEBUG
  printf("%s: stream %d has been opened\n", __FUNCTION__, item->dsid);
#endif /* DEBUG */

  return sxmsg_rreply(msg, ln + 1);
}

static int __dir_read(void *m, sexp_t *sx)
{
  sexp_t *lsx = NULL;
  sxmsg_t *msg = (sxmsg_t *)m;
  char *buf;
  size_t ln = 0;
  int stid = -1;

#ifdef DEBUG
  char dbuf[2048];
  print_sexp(dbuf, sizeof(dbuf), sx);
  printf("%s: got sx '%s'\n", __FUNCTION__, dbuf);
#endif /* DEBUG */

  if (sexp_list_cdr(sx, &lsx) || !sx->list->next || !sx->list->next->val
      || (0 >= (stid = atoi(sx->list->next->val) )) )
  {
    printf("Invalid protocol\n");
    return sxmsg_return(msg, SNE_BADPROTO);
  }

  /* get stream item */
  datastream_t *item;
  usrtc_node_t *node;
  pthread_rwlock_rdlock(&_lock);
  if ( !(node = usrtc_lookup(_rd_streams, &stid)) ) {
    pthread_rwlock_unlock(&_lock);
    return sxmsg_return(msg, SNE_INVALINDEX);
  }
  pthread_rwlock_unlock(&_lock);

  pthread_rwlock_wrlock(&_lock);
  item = usrtc_node_getdata(node);
  if (readdir_r(item->dp, &(item->dent), &(item->dres))) {
#ifdef DEBUG
    printf("%s: readdir_r() is failed (%d)\n", __FUNCTION__, errno);
#endif /* DEBUG */
    pthread_rwlock_unlock(&_lock);
    return sxmsg_return(msg, SNE_FAILED);
  }

  if (!item->dres) {
    buf = sxmsg_rapidbuf(msg);
    ln = snprintf(buf, MAX_RBBUF_LEN, "(dir-end %d)", item->dsid);
    goto __finish;
  }

  char dump[2048];
  sprintf(dump, "(dir-entry (");
  dump_dirent(item->dres, dump);
  strcat(dump, "))");

#ifdef DEBUG
  printf("%s: dump = '%s'\n", __FUNCTION__, dump);
#endif /* DEBUG */

  buf = sxmsg_rapidbuf(msg);
  ln = snprintf(buf, MAX_RBBUF_LEN, "%s", dump);

__finish:
  pthread_rwlock_unlock(&_lock);
  return sxmsg_rreply(msg, ln + 1);
}

static int __dir_close(void *m, sexp_t *sx)
{
  sexp_t *lsx = NULL;
  sxmsg_t *msg = (sxmsg_t *)m;
  int stid = -1;

#ifdef DEBUG
  char dbuf[2048];
  print_sexp(dbuf, sizeof(dbuf), sx);
  printf("%s: got sx '%s'\n", __FUNCTION__, dbuf);
#endif /* DEBUG */

  if (sexp_list_cdr(sx, &lsx) || !sx->list->next || !sx->list->next->val
      || (0 >= (stid = atoi(sx->list->next->val) )) )
  {
    printf("Invalid protocol\n");
    return sxmsg_return(msg, SNE_BADPROTO);
  }

  /* get stream item */
  datastream_t *item;
  usrtc_node_t *node;
  pthread_rwlock_rdlock(&_lock);
  if ( !(node = usrtc_lookup(_rd_streams, &stid)) ) {
    pthread_rwlock_unlock(&_lock);
    return sxmsg_return(msg, ENOENT);
  }
  pthread_rwlock_unlock(&_lock);

  pthread_rwlock_wrlock(&_lock);
  item = usrtc_node_getdata(node);
  closedir(item->dp);
  usrtc_delete(_rd_streams, node);
  FREE(item);
  pthread_rwlock_unlock(&_lock);

#ifdef DEBUG
  printf("%s: stream %d has been closed\n", __FUNCTION__, stid);
#endif /* DEBUG */

  return sxmsg_return(msg, SNE_SUCCESS);
}

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

static void sigpipe_handler(int a)
{
  return;
}

struct startbundle {
  int client;
  conn_sys_t *ssys;
};

static void *__starter_thread(void *stb);

int main(int argc, char **argv)
{
  signal(SIGPIPE, SIG_IGN);

  char *rootca = NULL, *cert = NULL;
  conn_sys_t *ssys = connections_create();
  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;
  }

  sntl_init();

  /* all is fine let's init connection subsystem */
  if(!ssys) {
    fprintf(stderr, "Subsystem init failed: %d\n", opt);
    return 2;
  }
  /* set wroking certificates */
  opt = connections_setsslserts(ssys, 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_set_authcheck(ssys, __secure_check);
  connections_set_sslvalidate(ssys, __validate_sslpem);
  /* set a callback, it's optional and doesn't required in server side apps */
  connections_set_channelcall(ssys, __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;
  }
  opt = sntl_rpclist_add(fulist, 1, "Public", NULL);
  if(opt) {
    fprintf(stderr, "Failed to add typed RPC channel\n Failure.\n");
    return opt;
  }

  /* ok, let's add stream functions */
  opt = sntl_rpclist_add_function(fulist, 12, "dir-open", __dir_open);
  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, "dir-read", __dir_read);
  if(opt) goto __fail;
  opt = sntl_rpclist_add_function(fulist, 12, "dir-close", __dir_close);
  if(opt) goto __fail;

  /* ok, setup it */
  connections_set_rpcvalidator(ssys, __rettlist);

  /* create stream tree */
  if(!(_rd_streams = malloc(sizeof(usrtc_t)) ))
    return ENOMEM;
  usrtc_init(_rd_streams, USRTC_REDBLACK, MAX_STREAMS, __cmp_int);

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

    bundle.ssys = ssys;

    int client = accept(srv, (struct sockaddr*)&addr, &len); /* accept connection as usual */
    blub(".");
    co = connection_master_link(ssys, client, NULL);

    if(!co) {
      fprintf(stderr, "Cannot create connetion (%d)\n", errno);
    }
#if 0
    bundle.client = client;
    pthread_create(&starter, NULL, __starter_thread, &bundle);
    pthread_detach(starter);
    //    pthread_join(starter, &co);
    //__starter_thread(&bundle);
    usleep(2000);
#endif
  }

  connections_destroy(ssys);

  sntl_finalize();

  return 0;
}

static void *__starter_thread(void *stb)
{
  struct startbundle *s = (struct startbundle *)stb;
  int client = s->client;
  conn_sys_t *ssys = s->ssys;
  conn_t *co = connection_master_link(ssys, client, NULL);

  if(!co) {
    fprintf(stderr, "Cannot create connetion (%d)\n", errno);
  }

  return co;
}