#include "schpriv.h"

#ifdef MZ_USE_MZRT

/************************************************************************/
/************************************************************************/
/************************************************************************/
#define MZRT_INTERNAL
#include "mzrt.h"
#include "schgc.h"

#ifdef MZ_XFORM
START_XFORM_SUSPEND;
#endif

/* std C headers */
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

#include <../sconfig.h>

/* platform headers */
#ifdef WIN32
# include <windows.h>
#else
# include <pthread.h>
# include <signal.h>
# include <unistd.h>
# include <time.h>
# if defined(UNIX_LIMIT_STACK) || defined(UNIX_LIMIT_FDSET_SIZE)
#   include <signal.h>
#   include <sys/time.h>
#   include <sys/resource.h>
# endif
#endif

#if !defined(MZ_PRECISE_GC) && !defined(WIN32)
int GC_pthread_join(pthread_t thread, void **retval);
int GC_pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void * arg);
int GC_pthread_detach(pthread_t thread);
#endif

void mzrt_set_user_break_handler(void (*user_break_handler)(int))
{
#ifdef WIN32
#else
  signal(SIGINT, user_break_handler);
#endif
}

static void rungdb() {
#ifdef WIN32
#else
  pid_t pid = getpid();
  char outbuffer[100];
  char inbuffer[10];

  fprintf(stderr, "pid # %i resume(r)/gdb(d)/exit(e)?\n", pid);
  fflush(stderr);

  while(1) {
    while(read(fileno(stdin), inbuffer, 10) <= 0){
      if(errno != EINTR){
        fprintf(stderr, "Error detected %i\n", errno);
      }
    }
    switch(inbuffer[0]) {
      case 'r':
        return;
        break;
      case 'd':
        snprintf(outbuffer, 100, "xterm -e gdb ./racket3m %d &", pid);
        fprintf(stderr, "%s\n", outbuffer);
        if(system(outbuffer)) 
          fprintf(stderr, "system failed\n");
        break;
      case 'e':
      default:
        exit(1);
        break;
    }
  }
#endif
}

#ifndef WIN32
static void segfault_handler(int signal_num) {
  pid_t pid = getpid();
  fprintf(stderr, "sig# %i pid# %i\n", signal_num, pid);
  rungdb();
}
#endif

void mzrt_set_segfault_debug_handler()
{
#ifdef WIN32
#else
  signal(SIGSEGV, segfault_handler);
#endif
}

void mzrt_sleep(int seconds)
{
#ifdef WIN32
#else
  struct timespec set;
  struct timespec rem;
  set.tv_sec  = seconds;
  set.tv_nsec = 0;
  rem.tv_sec  = 0;
  rem.tv_nsec = 0;
  while ((-1 == nanosleep(&set, &rem))) {
    //fprintf(stderr, "%i %i INITIAL\n", set.tv_sec, set.tv_nsec);
    //fprintf(stderr, "%i %i LEFT\n", rem.tv_sec, rem.tv_nsec);
    set = rem;
    //fprintf(stderr, "%i %i NOW\n", set.tv_sec, set.tv_nsec);
  }
#endif
}

/***********************************************************************/
/*                Atomic Ops                                           */
/***********************************************************************/

MZ_INLINE uint32_t mzrt_atomic_add_32(volatile unsigned int *counter, unsigned int value) {
#ifdef WIN32
# if defined(__MINGW32__)
  return InterlockedExchangeAdd((long *)counter, value);
# else
  return InterlockedExchangeAdd(counter, value);
# endif

#elif defined (__GNUC__) && (defined(__i386__) || defined(__x86_64__))
  asm volatile ("lock; xaddl %0,%1"
      : "=r" (value), "=m" (*counter)
      : "0" (value), "m" (*counter)
      : "memory", "cc");
  return value;
#else
#error !!!Atomic ops not provided!!!
#endif
}

/* returns the pre-incremented value */
MZ_INLINE uint32_t mzrt_atomic_incr_32(volatile unsigned int *counter) {
  return mzrt_atomic_add_32(counter, 1);
}

/***********************************************************************/
/*                Threads                                              */
/***********************************************************************/
typedef struct mzrt_thread_stub_data {
  mz_proc_thread_start start_proc;
  void *data;
  mz_proc_thread *thread;
} mzrt_thread_stub_data;

void *mzrt_thread_stub(void *data){
  mzrt_thread_stub_data *stub_data  = (mzrt_thread_stub_data*) data;
  mz_proc_thread_start start_proc     = stub_data->start_proc;
  void *start_proc_data               = stub_data->data;
  void* res;

  scheme_init_os_thread();

  proc_thread_self = stub_data->thread;

  free(data);

  res = start_proc(start_proc_data);

  if (proc_thread_self->refcount)
    free(proc_thread_self);

  scheme_done_os_thread();

  return res;
}

#ifdef WIN32
DWORD WINAPI mzrt_win_thread_stub(void *data)
{
  return (DWORD)mzrt_thread_stub(data);
}
#endif


mzrt_thread_id mz_proc_thread_self() {
#ifdef WIN32
  return GetCurrentThread();
#else
  return pthread_self();
#endif
}

mzrt_thread_id mz_proc_thread_id(mz_proc_thread* thread) {

  return thread->threadid;
}

mz_proc_thread* mzrt_proc_first_thread_init() {
  /* initialize mz_proc_thread struct for first thread myself that wasn't created with mz_proc_thread_create,
   * so it can communicate with other mz_proc_thread_created threads via pt_mboxes */
  mz_proc_thread *thread = (mz_proc_thread*)malloc(sizeof(mz_proc_thread));
  thread->mbox      = pt_mbox_create();
  thread->threadid  = mz_proc_thread_self();
  proc_thread_self  = thread;
  thread->refcount = 1;
  return thread;
}

mz_proc_thread* mz_proc_thread_create_w_stacksize(mz_proc_thread_start start_proc, void* data, long stacksize) {
  mz_proc_thread *thread = (mz_proc_thread*)malloc(sizeof(mz_proc_thread));
  mzrt_thread_stub_data *stub_data;

#   ifndef WIN32
  pthread_attr_t *attr;
  pthread_attr_t attr_storage;

  if (stacksize) {
    attr = &attr_storage;
    pthread_attr_init(attr);
    pthread_attr_setstacksize(attr, stacksize); /*8MB*/
  } else
    attr = NULL;
#   endif

  thread->refcount = 2;

  stub_data = (mzrt_thread_stub_data*)malloc(sizeof(mzrt_thread_stub_data));

  thread->mbox = pt_mbox_create();
  stub_data->start_proc = start_proc;
  stub_data->data       = data;
  stub_data->thread     = thread;
#   ifdef WIN32
  thread->threadid = CreateThread(NULL, stacksize, mzrt_win_thread_stub, stub_data, 0, NULL);
#   else
#    ifdef MZ_PRECISE_GC
  pthread_create(&thread->threadid, attr, mzrt_thread_stub, stub_data);
#    else
  GC_pthread_create(&thread->threadid, attr, mzrt_thread_stub, stub_data);
#    endif
#   endif

  return thread;
}

mz_proc_thread* mz_proc_thread_create(mz_proc_thread_start start_proc, void* data) {
  long stacksize;

#ifdef OS_X
  stacksize = 8*1024*1024;
#else
  stacksize = 0;
#endif

  return mz_proc_thread_create_w_stacksize(start_proc, data, stacksize);
}

void * mz_proc_thread_wait(mz_proc_thread *thread) {
  void *rc;
#ifdef WIN32
  DWORD rcw;
  WaitForSingleObject(thread->threadid,INFINITE);
  GetExitCodeThread(thread->threadid, &rcw);
  rc = (void *)rcw;
  CloseHandle(thread->threadid);
#else
#   ifndef MZ_PRECISE_GC
  GC_pthread_join(thread->threadid, &rc);
#   else
  pthread_join(thread->threadid, &rc);
#   endif
#endif

  if (!--thread->refcount)
    free(thread);
  
  return rc;
}

int mz_proc_thread_detach(mz_proc_thread *thread) {
  int rc;
#ifdef WIN32
  rc = CloseHandle(thread->threadid);
#else
#   ifndef MZ_PRECISE_GC
  rc = GC_pthread_detach(thread->threadid);
#   else
  rc = pthread_detach(thread->threadid);
#   endif
#endif

  if (!--thread->refcount)
    free(thread);

  return rc;
}

void mz_proc_thread_exit(void *rc) {
#ifdef WIN32
  ExitThread((DWORD)rc);
#else
#   ifndef MZ_PRECISE_GC
  pthread_exit(rc);
#   else
  pthread_exit(rc);
#   endif
#endif
}

/***********************************************************************/
/*                RW Lock                                              */
/***********************************************************************/

/* Unix **************************************************************/

#ifndef WIN32

# ifdef HAVE_PTHREAD_RWLOCK

struct mzrt_rwlock {
  pthread_rwlock_t lock;
};

int mzrt_rwlock_create(mzrt_rwlock **lock) {
  *lock = malloc(sizeof(mzrt_rwlock));
  return pthread_rwlock_init(&(*lock)->lock, NULL);
}

int mzrt_rwlock_rdlock(mzrt_rwlock *lock) {
  return pthread_rwlock_rdlock(&lock->lock);
}

int mzrt_rwlock_wrlock(mzrt_rwlock *lock) {
  return pthread_rwlock_wrlock(&lock->lock);
}

int mzrt_rwlock_tryrdlock(mzrt_rwlock *lock) {
  return pthread_rwlock_tryrdlock(&lock->lock);
}

int mzrt_rwlock_trywrlock(mzrt_rwlock *lock) {
  return pthread_rwlock_trywrlock(&lock->lock);
}
int mzrt_rwlock_unlock(mzrt_rwlock *lock) {
  return pthread_rwlock_unlock(&lock->lock);
}

int mzrt_rwlock_destroy(mzrt_rwlock *lock) {
  return pthread_rwlock_destroy(&lock->lock);
}

# else

struct mzrt_rwlock {
  pthread_mutex_t m;
  pthread_cond_t cr, cw;
  int readers, writers, write_waiting;
};

int mzrt_rwlock_create(mzrt_rwlock **lock) {
  int err;

  *lock = malloc(sizeof(mzrt_rwlock));
  err = pthread_mutex_init(&(*lock)->m, NULL);
  if (err) { free(*lock); return err; }
  err = pthread_cond_init(&(*lock)->cr, NULL);
  if (err) { free(*lock); return err; }
  err = pthread_cond_init(&(*lock)->cw, NULL);
  if (err) { free(*lock); return err; }
  return err;
}

static int rwlock_rdlock(mzrt_rwlock *lock, int just_try) {
  int err;

  err = pthread_mutex_lock(&lock->m);
  if (err) return err;
  while (lock->writers || lock->write_waiting) {
    if (just_try) {
      err = pthread_mutex_unlock(&lock->m);
      if (err) return err;
      return EBUSY;
    } else {
      err = pthread_cond_wait(&lock->cr, &lock->m);
      if (err)
        return err;
    }
  }
  lock->readers++;
  return pthread_mutex_unlock(&lock->m);
}

int mzrt_rwlock_rdlock(mzrt_rwlock *lock) {
  return rwlock_rdlock(lock, 0);
}

static int rwlock_wrlock(mzrt_rwlock *lock, int just_try) {
  int err;

  err = pthread_mutex_lock(&lock->m);
  if (err) return err;
  while (lock->writers || lock->readers) {
    if (just_try) {
      err = pthread_mutex_unlock(&lock->m);
      if (err) return err;
      return EBUSY;
    } else {
      lock->write_waiting++;
      err = pthread_cond_wait(&lock->cw, &lock->m);
      --lock->write_waiting;
      if (err)
        return err;
    }
  }
  lock->writers++;
  return pthread_mutex_unlock(&lock->m);
}

int mzrt_rwlock_wrlock(mzrt_rwlock *lock) {
  return rwlock_wrlock(lock, 0);
}

int mzrt_rwlock_tryrdlock(mzrt_rwlock *lock) {
  return rwlock_rdlock(lock, 1);
}

int mzrt_rwlock_trywrlock(mzrt_rwlock *lock) {
  return rwlock_wrlock(lock, 1);
}

int mzrt_rwlock_unlock(mzrt_rwlock *lock) {
  int err;

  err = pthread_mutex_lock(&lock->m);
  if (err) return err;

  if (lock->readers)
    --lock->readers; /* must have been a read lock */
  else
    --lock->writers;

  if (lock->write_waiting)
    err = pthread_cond_signal(&lock->cw);
  else
    err = pthread_cond_broadcast(&lock->cr);
  if (err) return err;
  
  return pthread_mutex_unlock(&lock->m);
}

int mzrt_rwlock_destroy(mzrt_rwlock *lock) {
  pthread_mutex_destroy(&lock->m);
  pthread_cond_destroy(&lock->cr);
  pthread_cond_destroy(&lock->cw);
  free(lock);

  return 0;
}

# endif

struct mzrt_mutex {
  pthread_mutex_t mutex;
};

int mzrt_mutex_create(mzrt_mutex **mutex) {
  *mutex = malloc(sizeof(struct mzrt_mutex));
  return pthread_mutex_init(&(*mutex)->mutex, NULL);
}

int mzrt_mutex_lock(mzrt_mutex *mutex) {
  return pthread_mutex_lock(&mutex->mutex);
}

int mzrt_mutex_trylock(mzrt_mutex *mutex) {
  return pthread_mutex_trylock(&mutex->mutex);
}

int mzrt_mutex_unlock(mzrt_mutex *mutex) {
  return pthread_mutex_unlock(&mutex->mutex);
}

int mzrt_mutex_destroy(mzrt_mutex *mutex) {
  return pthread_mutex_destroy(&mutex->mutex);
}

struct mzrt_cond {
  pthread_cond_t cond;
};

int mzrt_cond_create(mzrt_cond **cond) {
  *cond = malloc(sizeof(struct mzrt_cond));
  return pthread_cond_init(&(*cond)->cond, NULL);
}

int mzrt_cond_wait(mzrt_cond *cond, mzrt_mutex *mutex) {
  return pthread_cond_wait(&cond->cond, &mutex->mutex);
}

int mzrt_cond_timedwait(mzrt_cond *cond, mzrt_mutex *mutex, long seconds, long nanoseconds) {
  struct timespec timeout;
  timeout.tv_sec  = seconds;
  timeout.tv_nsec = nanoseconds;
  return pthread_cond_timedwait(&cond->cond, &mutex->mutex, &timeout);
}

int mzrt_cond_signal(mzrt_cond *cond) {
  return pthread_cond_signal(&cond->cond);
}

int mzrt_cond_broadcast(mzrt_cond *cond) {
  return pthread_cond_broadcast(&cond->cond);
}

int mzrt_cond_destroy(mzrt_cond *cond) {
  return pthread_cond_destroy(&cond->cond);
}

struct mzrt_sema {
  int ready;
  pthread_mutex_t m;
  pthread_cond_t c;
};

int mzrt_sema_create(mzrt_sema **_s, int v)
{
  mzrt_sema *s;
  int err;

  s = (mzrt_sema *)malloc(sizeof(mzrt_sema));
  err = pthread_mutex_init(&s->m, NULL);
  if (err) { 
    free(s); 
    return err; 
  }
  err = pthread_cond_init(&s->c, NULL);
  if (err) { 
    pthread_mutex_destroy(&s->m);
    free(s); 
    return err; 
  }
  s->ready = v;
  *_s = s;

  return 0;
}

int mzrt_sema_wait(mzrt_sema *s)
{
  pthread_mutex_lock(&s->m);
  while (!s->ready) {
    pthread_cond_wait(&s->c, &s->m);
  }
  --s->ready;
  pthread_mutex_unlock(&s->m);

  return 0;
}

int mzrt_sema_post(mzrt_sema *s)
{
  pthread_mutex_lock(&s->m);
  s->ready++;
  pthread_cond_signal(&s->c);
  pthread_mutex_unlock(&s->m);

  return 0;
}

int mzrt_sema_destroy(mzrt_sema *s)
{
  pthread_mutex_destroy(&s->m);
  pthread_cond_destroy(&s->c);
  free(s);

  return 0;
}

#endif

/* Windows **************************************************************/

#ifdef WIN32

typedef struct mzrt_rwlock {
  HANDLE readEvent;
  HANDLE writeMutex;
  unsigned long readers;
} mzrt_rwlock;

int mzrt_rwlock_create(mzrt_rwlock **lock) {
  *lock = malloc(sizeof(mzrt_rwlock));
  (*lock)->readers = 0;
  /* CreateEvent(LPSECURITY_ATTRIBUTES, manualReset, initiallySignaled, LPCSTR name) */
  if (! ((*lock)->readEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
    return 0;
  if (! ((*lock)->writeMutex = CreateMutex(NULL, FALSE, NULL)))
    return 0;

  return 1;
}

static int get_win32_os_error() {
  return 0;
}

static int mzrt_rwlock_rdlock_worker(mzrt_rwlock *lock, DWORD millis) {
  DWORD rc = WaitForSingleObject(lock->writeMutex, millis);
  if (rc == WAIT_FAILED || rc == WAIT_TIMEOUT );
    return 0;

  InterlockedIncrement(&lock->readers);

  if (! ResetEvent(lock->readEvent))
    return 0;

  if (!ReleaseMutex(lock->writeMutex))
    return 0;

  return 1;
}

static int mzrt_rwlock_wrlock_worker(mzrt_rwlock *lock, DWORD millis) {
  DWORD rc = WaitForSingleObject(lock->writeMutex, millis);
  if (rc == WAIT_FAILED || rc == WAIT_TIMEOUT );
    return 0;

  if (lock->readers) {
    if (millis) {
      rc = WaitForSingleObject(lock->readEvent, millis);
    }
    else {
      rc = WAIT_TIMEOUT;
    }

    if (rc == WAIT_FAILED || rc == WAIT_TIMEOUT );
      return 0;
  }

  return 1;
}

int mzrt_rwlock_rdlock(mzrt_rwlock *lock) {
  return mzrt_rwlock_rdlock_worker(lock, INFINITE);
}

int mzrt_rwlock_wrlock(mzrt_rwlock *lock) {
  return mzrt_rwlock_wrlock_worker(lock, INFINITE);
}

int mzrt_rwlock_tryrdlock(mzrt_rwlock *lock) {
  return mzrt_rwlock_rdlock_worker(lock, 0);
}

int mzrt_rwlock_trywrlock(mzrt_rwlock *lock) {
  return mzrt_rwlock_wrlock_worker(lock, 0);
}

int mzrt_rwlock_unlock(mzrt_rwlock *lock) {
  DWORD rc = 0;
  if (!ReleaseMutex(lock->writeMutex)) {
    rc = get_win32_os_error();
  }

  if (rc == ERROR_NOT_OWNER) {
    if (lock->readers && !InterlockedDecrement(&lock->readers) && !SetEvent(lock->readEvent)) {
      rc = get_win32_os_error();
    }
    else {
      rc = 0;
    }
  }

  return !rc;
}

int mzrt_rwlock_destroy(mzrt_rwlock *lock) {
  int rc = 1;
  rc &= CloseHandle(lock->readEvent);
  rc &= CloseHandle(lock->writeMutex);
  return rc; 
}

struct mzrt_mutex {
  CRITICAL_SECTION critical_section;
};

int mzrt_mutex_create(mzrt_mutex **mutex) {
  *mutex = malloc(sizeof(mzrt_mutex));
  InitializeCriticalSection(&(*mutex)->critical_section);
  return 0;
}

int mzrt_mutex_lock(mzrt_mutex *mutex) {
  EnterCriticalSection(&mutex->critical_section);
  return 0;
}

int mzrt_mutex_trylock(mzrt_mutex *mutex) {
  /* FIXME: TryEnterCriticalSection() requires NT:
     if (!TryEnterCriticalSection(&mutex->critical_section))
       return 1; */
  return 0;
}

int mzrt_mutex_unlock(mzrt_mutex *mutex) {
  LeaveCriticalSection(&mutex->critical_section);
  return 0;
}

int mzrt_mutex_destroy(mzrt_mutex *mutex) {
  DeleteCriticalSection(&mutex->critical_section);
  return 0;
}

struct mzrt_cond {
  int nothing;
};

int mzrt_cond_create(mzrt_cond **cond) {
  return 0;
}

int mzrt_cond_wait(mzrt_cond *cond, mzrt_mutex *mutex) {
  return 0;
}

int mzrt_cond_timedwait(mzrt_cond *cond, mzrt_mutex *mutex, long secs, long nsecs) {
  return 0;
}

int mzrt_cond_signal(mzrt_cond *cond) {
  return 0;
}

int mzrt_cond_broadcast(mzrt_cond *cond) {
  return 0;
}

int mzrt_cond_destroy(mzrt_cond *cond) {
  return 0;
}

struct mzrt_sema {
  HANDLE ws;
};

int mzrt_sema_create(mzrt_sema **_s, int v)
{
  mzrt_sema *s;
  HANDLE ws;

  s = (mzrt_sema *)malloc(sizeof(mzrt_sema));
  ws = CreateSemaphore(NULL, v, 32000, NULL);
  s->ws = ws;
  *_s = s;

  return 0;
}

int mzrt_sema_wait(mzrt_sema *s)
{
  WaitForSingleObject(s->ws, INFINITE);
  return 0;
}

int mzrt_sema_post(mzrt_sema *s)
{
  ReleaseSemaphore(s->ws, 1, NULL);  
  return 0;
}

int mzrt_sema_destroy(mzrt_sema *s)
{
  CloseHandle(s->ws);
  free(s);

  return 0;
}

#endif

/****************** PROCESS THREAD MAIL BOX *******************************/

pt_mbox *pt_mbox_create() {
  pt_mbox *mbox = (pt_mbox *)malloc(sizeof(pt_mbox));
  mbox->count = 0;
  mbox->in    = 0;
  mbox->out   = 0;
  mzrt_mutex_create(&mbox->mutex);
  mzrt_cond_create(&mbox->nonempty);
  mzrt_cond_create(&mbox->nonfull);
  return mbox;
}

void pt_mbox_send(pt_mbox *mbox, int type, void *payload, pt_mbox *origin) {
  mzrt_mutex_lock(mbox->mutex);
  while ( mbox->count == 5 ) {
    mzrt_cond_wait(mbox->nonfull, mbox->mutex);
  }
  mbox->queue[mbox->in].type = type;
  mbox->queue[mbox->in].payload = payload;
  mbox->queue[mbox->in].origin = origin;
  mbox->in = (mbox->in + 1) % 5;
  mbox->count++;
  mzrt_cond_signal(mbox->nonempty);
  mzrt_mutex_unlock(mbox->mutex);
}

void pt_mbox_recv(pt_mbox *mbox, int *type, void **payload, pt_mbox **origin){
  mzrt_mutex_lock(mbox->mutex);
  while ( mbox->count == 0 ) {
    mzrt_cond_wait(mbox->nonempty, mbox->mutex);
  }
  *type    = mbox->queue[mbox->out].type;
  *payload = mbox->queue[mbox->out].payload;
  *origin  = mbox->queue[mbox->out].origin;
  mbox->out = (mbox->out + 1) % 5;
  mbox->count--;
  mzrt_cond_signal(mbox->nonfull);
  mzrt_mutex_unlock(mbox->mutex);
}

void pt_mbox_send_recv(pt_mbox *mbox, int type, void *payload, pt_mbox *origin, int *return_type, void **return_payload) {
  pt_mbox *return_origin;
  pt_mbox_send(mbox, type, payload, origin);
  pt_mbox_recv(origin, return_type, return_payload, &return_origin);
}

void pt_mbox_destroy(pt_mbox *mbox) {
  mzrt_mutex_destroy(mbox->mutex);
  mzrt_cond_destroy(mbox->nonempty);
  mzrt_cond_destroy(mbox->nonfull);
  free(mbox);
}

/************************************************************************/
/************************************************************************/
/************************************************************************/

#ifdef MZ_XFORM
END_XFORM_SUSPEND;
#endif

#endif
