#include <sys/types.h>
#include <ubixos/sched.h>
#include <ubixos/ubthread.h>
#include <ubixos/kpanic.h>
#include <lib/kprintf.h>
#include <lib/kmalloc.h>
#include <sys/sysproto_posix.h>
#include <sys/descrip.h>

#include "net/debug.h"
#include "net/sys.h"
#include "net/opt.h"
#include "net/stats.h"
#include <net/arch/sys_arch.h>

#include <ubixos/spinlock.h>
#include <ubixos/sem.h>

/* Get Definitions For These */
#define ERR_NOT_READY 0
#define ERR_TIMED_OUT 1
#define INFINITE_TIME 0

static struct timeval starttime;
static struct spinLock netThreadSpinlock = SPIN_LOCK_INITIALIZER;
static struct sys_thread *threads = 0x0;

static uint32_t cond_wait(ubthread_cond_t *cond, ubthread_mutex_t *mutex, uint32_t timeout);
static void sys_sem_free_internal(struct sys_sem *sem);

/* sys_arch layer initializer */
void sys_init() {
  struct timezone tz;
  gettimeofday(&starttime, &tz);
}

#ifdef _IGNORE
static struct sys_sem *sys_sem_new_internal(uint8_t count) {
  struct sys_sem *sem;

  sem = (struct sys_sem *) kmalloc(sizeof(struct sys_sem));
  if (sem != NULL) {
    sem->signaled = count;
    ubthread_cond_init(&(sem->cond), NULL);
    ubthread_mutex_init(&(sem->mutex), NULL);
  }
  return sem;
}
#endif

/* Create a new semaphore */
err_t sys_sem_new(sys_sem_t **sem, uint8_t count) {
  return (sem_init(sem, count));

#ifdef __IGNORE
  sys_sem_t *newSem = 0x0;

  if (*sem != 0) {
    kprintf("UH OH!");
  }

  newSem = kmalloc(sizeof(struct sys_sem));
  newSem->signaled = count;

  ubthread_cond_init(&(newSem->cond), NULL);
  ubthread_mutex_init(&(newSem->mutex), NULL);

  *sem = newSem;

  return (ERR_OK);
#endif
}

/* Deallocate semaphore */
void sys_sem_free(struct sys_sem **sem) {
  sem_destroy(sem);

#ifdef _IGNORE
  if ((sem != NULL) && (*sem != SYS_SEM_NULL)) {
    sys_sem_free_internal(*sem);
    *sem = 0x0;
  }
#endif


}

/* Signal semaphore */
void sys_sem_signal(struct sys_sem **s) {
  struct sys_sem *sem;
  LWIP_ASSERT("invalid sem", (s != NULL) && (*s != NULL));
  sem = *s;

  ubthread_mutex_lock(&(sem->mutex));
  sem->signaled++;

  if (sem->signaled > 1) {
    sem->signaled = 1;
  }

  ubthread_cond_broadcast(&(sem->cond));
  ubthread_mutex_unlock(&(sem->mutex));
}

uint32_t sys_arch_sem_wait(struct sys_sem **s, uint32_t timeout) {
  uint32_t time_needed = 0;
  struct sys_sem *sem;
  LWIP_ASSERT("invalid sem", (s != NULL) && (*s != NULL));
  sem = *s;

  ubthread_mutex_lock(&(sem->mutex));
  while (sem->signaled <= 0) {
    if (timeout > 0) {
      time_needed = cond_wait(&(sem->cond), &(sem->mutex), timeout);

      if (time_needed == SYS_ARCH_TIMEOUT) {
        ubthread_mutex_unlock(&(sem->mutex));
        return SYS_ARCH_TIMEOUT;
      }
      /*      ubthread_mutex_unlock(&(sem->mutex));
       return time_needed; */
    }
    else {
      cond_wait(&(sem->cond), &(sem->mutex), 0);
    }
  }
  sem->signaled--;
  ubthread_mutex_unlock(&(sem->mutex));
  return time_needed;
}

int sys_sem_valid(struct sys_sem **s) {
  struct sys_sem *sem = *s;
  if (sem == 0)
    return 0;
  else
    return 1;
}

void sys_sem_set_invalid(struct sys_sem **s) {
  *s = 0x0;
}

err_t sys_mutex_new(sys_mutex_t *mutex) {
  ubthread_mutex_init(&(mutex->mutex), NULL);
  return ERR_OK;
}

void sys_mutex_free(sys_mutex_t *mutex) {
  ubthread_mutex_destroy(&(mutex->mutex));
}

void sys_mutex_lock(sys_mutex_t *mutex) {
  ubthread_mutex_lock(&(mutex->mutex));
}

void sys_mutex_unlock(sys_mutex_t *mutex) {
  ubthread_mutex_unlock(&(mutex->mutex));
}

err_t sys_mbox_new(struct sys_mbox **mb, int size) {
  struct sys_mbox *mbox = 0x0;
  LWIP_UNUSED_ARG(size);

  mbox = (struct sys_mbox *) kmalloc(sizeof(struct sys_mbox));

  if (mbox == NULL)
    return (ERR_MEM);

  mbox->head = 0;
  mbox->tail = 0;
  mbox->wait_send = 0;
  //mbox->size = size;

  //Pass By Reference It's a Pointer
  //ubthread_mutex_init(&mbox->lock, NULL);

  //Pass By Reference It's a Pointer
  sys_sem_new(&mbox->lock, 1);
  sys_sem_new(&mbox->empty, 0);
  sys_sem_new(&mbox->full, 0);

  //mbox->queue = kmalloc(sizeof(void *) * size);//calloc(size, sizeof(void *));

  //if (!mbox->queue) {
  //  return ERR_MEM;
  //}

  *mb = mbox;

  return (ERR_OK);
}

void sys_mbox_free(struct sys_mbox **mb) {
  if ((mb != NULL) && (*mb != SYS_MBOX_NULL)) {
    struct sys_mbox *mbox = *mb;
    sys_arch_sem_wait(&mbox->lock, 0);
/*
    sys_sem_free_internal(mbox->full);
    sys_sem_free_internal(mbox->empty);
    sys_sem_free_internal(mbox->lock);
*/
  sem_destroy(mbox->full);
  sem_destroy(mbox->empty);
  sem_destroy(mbox->lock);

    mbox->full = mbox->empty = mbox->lock = NULL;
    kfree(mbox);
    *mb = 0x0;
  }
  //kfree(mbox->queue);
  //mbox->queue = NULL;
}

void sys_mbox_post(struct sys_mbox **mb, void *msg) {
  uint8_t head;
  struct sys_mbox *mbox;
  LWIP_ASSERT("invalid mbox", (mb != NULL) && (*mb != NULL));
  mbox = *mb;

  sys_arch_sem_wait(&mbox->lock, 0);

  LWIP_DEBUGF(SYS_DEBUG, ("sys_mbox_post: mbox %p msg %p\n", (void *)mbox, (void *)msg));

  while ((mbox->tail + 1) >= (mbox->head + SYS_MBOX_SIZE)) {
    mbox->wait_send++;
    sys_sem_signal(&mbox->lock);
    sys_arch_sem_wait(&mbox->empty, 0);
    sys_arch_sem_wait(&mbox->lock, 0);
    mbox->wait_send--;
  }

  mbox->msgs[mbox->tail % SYS_MBOX_SIZE] = msg;

  if (mbox->tail == mbox->head) {
    head = 1;
  }
  else {
    head = 0;
  }

  mbox->tail++;

  if (head) {
    sys_sem_signal(&mbox->full);
  }

  sys_sem_signal(&mbox->lock);
}

err_t sys_mbox_trypost(struct sys_mbox **mb, void *msg) {
  uint8_t head;
  struct sys_mbox *mbox;
  LWIP_ASSERT("invalid mbox", (mb != NULL) && (*mb != NULL));
  mbox = *mb;

  sys_arch_sem_wait(&mbox->lock, 0);

  LWIP_DEBUGF(SYS_DEBUG, ("sys_mbox_trypost: mbox %p msg %p\n",
      (void *)mbox, (void *)msg));

  if ((mbox->tail + 1) >= (mbox->head + SYS_MBOX_SIZE)) {
    sys_sem_signal(&mbox->lock);
    return ERR_MEM;
  }

  mbox->msgs[mbox->tail % SYS_MBOX_SIZE] = msg;

  if (mbox->tail == mbox->head) {
    head = 1;
  }
  else {
    head = 0;
  }

  mbox->tail++;

  if (head) {
    sys_sem_signal(&mbox->full);
  }

  sys_sem_signal(&mbox->lock);

  return ERR_OK;
}

uint32_t sys_arch_mbox_fetch(struct sys_mbox **mb, void **msg, uint32_t timeout) {
  uint32_t time_needed = 0x0;
  struct sys_mbox *mbox = 0x0;

  LWIP_ASSERT("invalid mbox", (mb != NULL) && (*mb != NULL));
  mbox = *mb;

  /* The mutex lock is quick so we don't bother with the timeout
   stuff here. */
  sys_arch_sem_wait(&mbox->lock, 0);

  while (mbox->head == mbox->tail) {
    sys_sem_signal(&mbox->lock);

    /* We block while waiting for a mail to arrive in the mailbox. We
     must be prepared to timeout. */
    if (timeout != 0) {
      time_needed = sys_arch_sem_wait(&mbox->full, timeout);

      if (time_needed == SYS_ARCH_TIMEOUT) {
        return SYS_ARCH_TIMEOUT;
      }
    }
    else {
      sys_arch_sem_wait(&mbox->full, 0);
    }

    sys_arch_sem_wait(&mbox->lock, 0);
  }

  if (msg != NULL) {
    LWIP_DEBUGF(SYS_DEBUG, ("sys_mbox_fetch: mbox %p msg %p\n", (void *)mbox, *msg));
    *msg = mbox->msgs[mbox->head % SYS_MBOX_SIZE];
  }
  else {
    LWIP_DEBUGF(SYS_DEBUG, ("sys_mbox_fetch: mbox %p, null msg\n", (void *)mbox));
  }

  mbox->head++;

  if (mbox->wait_send) {
    sys_sem_signal(&mbox->empty);
  }

  sys_sem_signal(&mbox->lock);

  return time_needed;
}

uint32_t sys_arch_mbox_tryfetch(struct sys_mbox **mb, void **msg) {
  struct sys_mbox *mbox;
  LWIP_ASSERT("invalid mbox", (mb != NULL) && (*mb != NULL));
  mbox = *mb;

  sys_arch_sem_wait(&mbox->lock, 0);

  if (mbox->head == mbox->tail) {
    sys_sem_signal(&mbox->lock);
    return SYS_MBOX_EMPTY;
  }

  if (msg != NULL) {
    LWIP_DEBUGF(SYS_DEBUG, ("sys_mbox_tryfetch: mbox %p msg %p\n", (void *)mbox, *msg));
    *msg = mbox->msgs[mbox->head % SYS_MBOX_SIZE];
  }
  else {
    LWIP_DEBUGF(SYS_DEBUG, ("sys_mbox_tryfetch: mbox %p, null msg\n", (void *)mbox));
  }

  mbox->head++;

  if (mbox->wait_send) {
    sys_sem_signal(&mbox->empty);
  }

  sys_sem_signal(&mbox->lock);

  return 0;
}

int sys_mbox_valid(struct sys_mbox **mb) {
  struct sys_mbox *mbox = *mb;
  if (mbox == NULL)
    return(0);
  else
    return(1);
}

void sys_mbox_set_invalid(struct sys_mbox **mb) {
  *mb = 0x0;
}

sys_thread_t sys_thread_new(const char *name, void (*thread)(void *arg), void *arg, int stacksize, int prio) {
  //void sys_thread_new(void (*function)(void), void *arg) {
  struct sys_thread *new_thread = 0x0;
  //struct thread_start_param *thread_param;
  prio = 1;
  LWIP_ASSERT("Non-positive prio", prio > 0);
  LWIP_ASSERT("Prio is too big", prio < 20);

  new_thread = kmalloc(sizeof(struct sys_thread));
  memset(new_thread, 0x0, sizeof(struct sys_thread));

  spinLock(&netThreadSpinlock);
  new_thread->next = threads;
  new_thread->timeouts.next = NULL;
  new_thread->ubthread = 0x0;
  threads = new_thread;
  spinUnlock(&netThreadSpinlock);

  /*
   thread_param = kmalloc(sizeof(struct thread_start_param));

   thread_param->function = function;
   thread_param->arg = arg;
   thread_param->thread = thread;
   */
  if (ubthread_create(&new_thread->ubthread, 0x0, (void *) (thread), arg) != 0x0) {
    kpanic("sys_thread_new: ubthread_create");
  }
  return (new_thread);
}

/* OLD */

struct thread_start_param {
  struct sys_thread *thread;
  void (*function)(void *);
  void *arg;
};

static uint32_t cond_wait(ubthread_cond_t *cond, ubthread_mutex_t *mutex, uint32_t timeout) {
  unsigned int tdiff;
  unsigned long sec, usec;
  struct timeval rtime1, rtime2;
  struct timespec ts;
  struct timezone tz;
  int retval;

  if (timeout > 0) {
    /* Get a timestamp and add the timeout value. */
    gettimeofday(&rtime1, &tz);
    sec = rtime1.tv_sec;
    usec = rtime1.tv_usec;
    usec += timeout % 1000 * 1000;
    sec += (int) (timeout / 1000) + (int) (usec / 1000000);
    usec = usec % 1000000;
    ts.tv_nsec = usec * 1000;
    ts.tv_sec = sec;

    retval = ubthread_cond_timedwait(cond, mutex, &ts);
    if (retval == ETIMEDOUT) {
      return 0;
    }
    else {
      /* Calculate for how long we waited for the cond. */
      gettimeofday(&rtime2, &tz);
      tdiff = (rtime2.tv_sec - rtime1.tv_sec) * 1000 + (rtime2.tv_usec - rtime1.tv_usec) / 1000;
      if (tdiff == 0) {
        return 1;
      }
      return tdiff;
    }
  }
  else {
    ubthread_cond_wait(cond, mutex);
    return 0;
  }
}

static struct sys_thread *current_thread(void) {
  struct sys_thread *st;
  kTask_t *pt;
  pt = ubthread_self();
  spinLock(&netThreadSpinlock);
  for (st = threads; st != NULL; st = st->next) {
    if (st->ubthread == pt) {
      spinUnlock(&netThreadSpinlock);
      return st;
    }
  }
  spinUnlock(&netThreadSpinlock);
  kprintf("sys: current_thread: could not find current thread!\n");
  kprintf("This is due to a race condition in the LinuxThreads\n");
  kprintf("ubthreads implementation. Start the program again.\n");

  kpanic("ABORT");
  return (0x0);
}

struct sys_timeouts *sys_arch_timeouts(void) {
  struct sys_thread *thread;
  thread = current_thread();
  return (&thread->timeouts);
}

unsigned long sys_unix_now() {
  struct timeval tv;
  struct timezone tz;
  long sec, usec;
  unsigned long msec;

  gettimeofday(&tv, &tz);

  sec = tv.tv_sec - starttime.tv_sec;
  usec = tv.tv_usec - starttime.tv_usec;
  msec = sec * 1000 + usec / 1000;
  return msec;
}

uint32_t sys_now() {
  return (sys_unix_now());
}


int sys_socket(struct thread *td, struct sys_socket_args *args) {
  int error = 0x0;
  int fd = 0x0;
  struct file *nfp = 0x0;

  error = falloc(td, &nfp, &fd);

  if (error)
    return (error);

  nfp->socket = lwip_socket(args->domain, args->type, args->protocol);
  nfp->fd_type = 2;
  kprintf("socket(%i:%i): 0x%X:0x%X:0x%X", nfp->socket, fd, args->domain, args->type, args->protocol);

  if (nfp->fd == 0x0  && nfp->socket) {
    fdestroy(td, nfp, fd);

    td->td_retval[0] = -1;
    error = -1;
  }
  else {
    td->td_retval[0] = fd;//nfp->fd; //MrOlsen 2018index;
  }

  return (error);
}

int sys_setsockopt(struct thread *td, struct sys_setsockopt_args *args) {
  struct file *fd = 0x0;
  getfd(td, &fd, args->s);

  td->td_retval[0] = lwip_setsockopt(fd->socket, args->level, args->name, args->val, args->valsize);
  kprintf("SSO: %i:%i:%i", args->s, fd->socket, td->td_retval[0]);
  td->td_retval[0] = 0;

  return(0);
}

int sys_sendto(struct thread *td, struct sys_sendto_args *args) {
  struct file *fd = 0x0;
  getfd(td, &fd, args->s);

  lwip_sendto(fd->socket, args->buf, args->len, args->flags, args->to, args->tolen);
  td->td_retval[0] = 0x0;
  
  return (0);
}
