#include <stdio.h>
#define __USE_GNU
#include <stdlib.h>
#include <stdarg.h>
#include <sys/types.h>

#include <pthread.h>
#include <stdint.h>
#include <getopt.h>
#include <errno.h>
#include <assert.h>
#include <time.h>

#ifdef WIN32
#include <Winsock2.h>
#include <signal.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/sntllv2.h>
#include <sntl/limits.h>

/* define a little bit */
#define DEFAULT_PORT  13133
#define CHANNEL_COUNT 200
#define CLIENT_COUNT 256
#define MESSAGES_PER_SESSION 10000
#define ITERATION_COUNT 1000

#define FAILS_ONLY

struct testdata {
  int uc;
  pthread_mutex_t ulock;
  conn_t *co;
};

static int __init_testdata(struct testdata *t, conn_t *co)
{
  t->uc = 0;
  pthread_mutex_init(&t->ulock, NULL);
  t->co = co;
  return 0;
}

static void __wait_completion(struct testdata *t)
{
  pthread_mutex_lock(&t->ulock);
  if(t->uc) {
    pthread_mutex_lock(&t->ulock);
  }
  return;
}

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

static void *__addsthrd(void *a)
{
  struct testdata *t = a;
  conn_t *co = t->co;
  chnl_t *mch;
  sxmsg_t *msg;
  char mmbuf[1024];
  size_t ln;
  int mr, i;

  pthread_mutex_lock(&t->ulock);
  t->uc++;
  pthread_mutex_unlock(&t->ulock);

  /* here we go */
  mch = sxchannel_open(co, 12);

  if(!mch) {
    fprintf(stderr, "Failed to openchannel with %d\n", errno);
    goto __fini;
  }

  for(i = 0; i < MESSAGES_PER_SESSION; i++) {
    ln = snprintf(mmbuf, 1024, "(ar-add (10 10))");
    mr = sxmsg_send(mch, mmbuf, ln, &msg);
    switch(mr) {
    case SNE_RAPIDMSG:
      //fprintf(stdout, "Rapidly replied: %s\n", (char *)sxmsg_payload(msg));
      sxmsg_clean(msg);
      break;
    case SNE_REPLYREQ:
      if(sxmsg_datalen(msg)) fprintf(stdout, "Replied (confirmation required): %s\n",
                                     (char *)sxmsg_payload(msg));
      mr = sxmsg_return(msg, SNE_SUCCESS);
      fprintf(stderr, "mr = %d\n", mr);
      break;
    case SNE_SUCCESS:
      fprintf(stdout, "Success.\n");
      break;
    default:
      fprintf(stderr, "ERROR: %d\n", mr);
      break;
    }
  }

  sxchannel_close(mch);

 __fini:
  t->uc--;
  if(t->uc <= 1) pthread_mutex_unlock(&t->ulock);

  return NULL;
}

int main(int argc, char **argv)
{
  char *rootca = NULL, *cert = NULL;
  int port = DEFAULT_PORT;
  char *addr = NULL, *login = NULL, *password = NULL;
  int opt;
  conn_sys_t *ssys;
  conn_t *co;

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

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

  if(!addr) {
    fprintf(stderr, "Server address not pointed.\n Failure.\n");
    return EINVAL;
  }

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

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

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

  sntl_init();
  /* all is fine let's init connection subsystem */
  ssys = connections_create();
  if(!ssys) {
    fprintf(stderr, "Subsystem init failed: %d\n", errno);
    return errno;
  }
  /* set working 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;
  }

  /* Tests */
  struct timeval beg, end;
  /* try to open connection */
  connections_set_channelcall(ssys, __set_typed_list_callback);

  gettimeofday(&beg, NULL);
  co = connection_link(ssys, addr, port, cert, login, password);

  if(!co) {
    fprintf(stderr, "Failed to connection with %d\n", errno);
    return errno;
  }
  gettimeofday(&end, NULL);

  if((end.tv_sec - beg.tv_sec) > 0) {
    printf("Seconds: %ld ", end.tv_sec - beg.tv_sec);
    printf("µS: %ld\n", end.tv_usec + (1000000 - beg.tv_usec));
  } else printf("µS: %ld\n", end.tv_usec - beg.tv_usec);

  /* ok now we should open a channel */
  chnl_t *testchannel = sxchannel_open(co, 12);

  if(!testchannel) {
    fprintf(stderr, "Failed to openchannel with %d\n", errno);
    return errno;
  }
  gettimeofday(&end, NULL);

  if((end.tv_sec - beg.tv_sec) > 0) {
    printf("Seconds: %ld ", end.tv_sec - beg.tv_sec);
    printf("µS: %ld\n", end.tv_usec + (1000000 - beg.tv_usec));
  } else printf("µS: %ld\n", end.tv_usec - beg.tv_usec);

  /* ok, send a message */
  char mmbuf[1024];
  sxmsg_t *msg;
  size_t ln;
  ln = snprintf(mmbuf, 1024, "(ar-add (10 10))");
  int mr = sxmsg_send(testchannel, mmbuf, ln, &msg);
  switch(mr) {
  case SNE_RAPIDMSG:
    fprintf(stdout, "Rapidly replied: %s\n", (char *)sxmsg_payload(msg));
    sxmsg_clean(msg);
    break;
  case SNE_REPLYREQ:
    if(sxmsg_datalen(msg)) fprintf(stdout, "Replied (confirmation required): %s\n",
                                   (char *)sxmsg_payload(msg));
    mr = sxmsg_return(msg, SNE_SUCCESS);
    fprintf(stderr, "mr = %d\n", mr);
    break;
  case SNE_SUCCESS:
    fprintf(stdout, "Success.\n");
    break;
  default:
    fprintf(stderr, "ERROR: %d\n", mr);
    break;
  }

  int ee = sxchannel_close(testchannel);
  printf("ee = %d\n", ee);
  gettimeofday(&end, NULL);

  if((end.tv_sec - beg.tv_sec) > 0) {
    printf("Seconds: %ld ", end.tv_sec - beg.tv_sec);
    printf("µS: %ld\n", end.tv_usec + (1000000 - beg.tv_usec));
  } else printf("µS: %ld\n", end.tv_usec - beg.tv_usec);
  sleep(10);
  /* ok, now we need to create many threads */
  struct testdata trd;
  pthread_t thrd;
  int i;
  __init_testdata(&trd, co);

  for(i = 0; i < 256; i++)    pthread_create(&thrd, NULL, __addsthrd, &trd);

  __wait_completion(&trd);

  connection_close(co);

  return 0;
}