/* Pseudo-threads.
 * Jim Plank
 * October, 1995
 * CS460
 */

#include <stdio.h>
#include "dlist.h"
#include "rb.h"
#include <setjmp.h>

#define RUNNING 0
#define READY 1
#define BLOCKED 2
#define SLEEPING 3
#define ZOMBIE 4
#define JOINING 5

static Dlist Readyq = NULL;
static Rb_node Sleepq = NULL;

static int first_time = 1;
static jmp_buf thebuf;
static int debug = 0;

typedef struct thread {
  void (*function)();
  void *arg;
  int state;
  struct thread *joiner;
} Thread;

typedef struct gsem {
  int val;
  Dlist queue;
} *Gsem;

int gsem_getval(g)
Gsem g;
{
  return g->val;
}

Thread *pt_self = NULL;
static Thread *Joinall = NULL;

pt_set_debug(i)
int i;
{
  debug = i;
}

static pt_initialize()
{
  if (Readyq != NULL) {
    fprintf(stderr, "PT: Called pt_initialize twice\n");
    exit(1);
  }
  Readyq = make_dl();
  Sleepq = make_rb();
  pt_self = (Thread *) malloc(sizeof(Thread));
  pt_self->state = RUNNING;
  pt_self->joiner = NULL;
}

void *pt_fork(function, arg)
void (*function)();
void *arg;
{
  Thread *p;

  if (Readyq == NULL) pt_initialize(); 

  p = (Thread *) malloc(sizeof(Thread));
  p->state = READY;
  p->function = function;
  p->arg = arg;
  p->joiner = NULL;

  dl_insert_b(Readyq, p);
  if (debug) fprintf(stderr, "0x%x: Calling pt_fork(0x%x, 0x%x): 0x%x\n",
              pt_self, function, arg, p); 
  return (void *) p;
}

static block_myself()
{
  Dlist d;
  Thread *p;
  void (*function)();
  void *arg;
  long t;

  if (Readyq == NULL) pt_initialize();

  if (debug) printf("0x%x: Block_myself %d\n", pt_self, first_time);
  /* Always longjmp down to pop all thread frames off the stack */

  if (first_time) {
    first_time = 0;
    setjmp(thebuf);
  } else {
    if (debug) printf("Doing longjmp\n");
    longjmp(thebuf, 1);
  }

  /* If the sleep queue is not empty, check to see if any sleepq
     elements should come off of the queue */

  if (!rb_empty(Sleepq)) {
    t = time(0);
    while(!rb_empty(Sleepq) && rb_first(Sleepq)->k.ikey <= t) {
      p = (Thread *) (rb_first(Sleepq)->v.val);
      p->state = READY;
      dl_insert_b(Readyq, p);
      rb_delete_node(rb_first(Sleepq));
    }
  }

  /* Call the first thread on the ready queue */

  if (!dl_empty(Readyq)) {
    d = Readyq->flink;
    p = (Thread *) d->val;
    function = p->function;
    arg = p->arg;
    dl_delete_node(d);
    pt_self = p;
    p->state = RUNNING;
    if (dl_empty(Readyq) && Joinall == NULL) {
      if (debug) printf("0x%x: setting first_time to 1\n", pt_self);
      first_time = 1;
    }
    (*function)(arg);

    /* If the function returns, the thread should exit */

    pt_exit();

  }
 
  /* Otherwise, if there are sleepers, sleep until one of them is ready */
  else if (!rb_empty(Sleepq)) {
    t = rb_first(Sleepq)->k.ikey-t;
    sleep(t);
    block_myself();
  }
  
  /* Otherwise, there are no more threads to run.  If there is 
     a joinall continuation, call it.  Otherwise, exit */

  if (Joinall != NULL) {
    p = Joinall;
    p->state = READY;
    dl_insert_b(Readyq, p);
    Joinall = NULL;
    block_myself();
  }

  fprintf(stderr, "No more threads to run\n");
  exit(0);
}

pt_join(thread, function, arg)
Thread *thread;
void (*function)();
void *arg;
{
  int fnd;
  Rb_node r;

  if (Readyq == NULL) pt_initialize();

  if (thread->joiner != NULL) {
    fprintf(stderr, "Called pt_join on a thread twice\n");
    exit(1);
  }

  /* If the thread is a zombie -- free it and go directly to the 
     continuation */

  pt_self->function = function;
  pt_self->arg = arg;

  if (thread->state == ZOMBIE) {
    free(thread);
    pt_self->state = READY; /* Unnecessary -- see P() */
    dl_insert_a(Readyq, pt_self);
  
  /* Otherwise, block the thread as joining */

  } else {
    thread->joiner = pt_self;
    pt_self->state = JOINING;
  }

  block_myself();
}

pt_joinall(function, arg)
void (*function)();
void *arg;
{
  if (Readyq == NULL) {
    pt_initialize();
  }

  pt_self->function = function;
  pt_self->arg = arg;
  pt_self->state = JOINING;
  Joinall = pt_self;
  block_myself();
}

void *make_gsem(initval)
int initval;
{
  Gsem g;

  if (initval < 0) {
    fprintf(stderr, "make_gsem: initval < 0 (%d)\n", initval);
    exit(1);
  }
  g = (Gsem) malloc(sizeof(struct gsem));
  g->val = initval;
  g->queue = make_dl();
  return g;
}
  
kill_gsem(g)
Gsem g;
{
  Dlist d;

  if (!dl_empty(g->queue)) {
    fprintf(stderr, "Killing a semahpore with threads waiting\n");
    exit(1);
  }
  dl_delete_list(g->queue);
  free(g);
}
  
gsem_P(g, function, arg)
Gsem g;
void (*function)();
void *arg;
{
  Thread *p;

  if (Readyq == NULL) pt_initialize();

  g->val--;

  p = pt_self;
  p->function = function;
  p->arg = arg;

  /* If blocking, put the continuation on the semaphore's queue, otherwise
     put the continuation on the front of the ready_queue, and call
     block_myself().  The reason for this is to pop off all the stack 
     frames and start anew */

  if (g->val < 0) {
    dl_insert_b(g->queue, p);
    p->state = BLOCKED;
    if (debug) fprintf(stderr, "0x%x: blocking on semaphore 0x%x\n",
                       pt_self, g);
  } else {
    dl_insert_a(Readyq, p);
    p->state = READY; /* This is not really necessary, since it's going 
                         on the head of the queue */
    if (debug) fprintf(stderr, "0x%x: P called but no blocking on 0x%x\n",
                       pt_self, g);
  }
  block_myself();
}

gsem_V(g)
Gsem g;
{
  Thread *p;
  Dlist d;

  if (Readyq == NULL) pt_initialize();

  g->val++;

  /* If g->val <= 0, unblock a thread */

  d = g->queue;
  if (g->val <= 0) {
    d = d->flink;
    p = (Thread *) d->val;
    dl_delete_node(d);
    dl_insert_b(Readyq, p);
    p->state = READY;
    if (debug) fprintf(stderr, "0x%x: V called on  0x%x -- waking up 0x%x\n",
                       pt_self, g, p);
  } else {
    if (debug) fprintf(stderr, "0x%x: V called on  0x%x no one to wake\n",
                       pt_self, g);
  }
}

/* pt_sleep -- if sec <= 0, put the thread at the end of the ready queue.
   If sec > 0, put the thread with the proper waking time into the 
   sleep queue */

pt_sleep(sec, function, arg)
int sec;
void (*function)();
void *arg;
{
  long t;
  Thread *p;
  
  if (Readyq == NULL) pt_initialize();

  p = pt_self;
  p->function = function;
  p->arg = arg;

  if (sec <= 0) {
    dl_insert_b(Readyq, p);
    p->state = READY;
  } else {
    t = time(0)+sec;
    rb_inserti(Sleepq, t, p);
    p->state = SLEEPING;
  }
  block_myself();
}  

pt_yield(function, arg)
void (*function)();
void *arg;
{
  pt_sleep(0, function, arg);
}

pt_exit()
{
  Thread *p;

  /* If the thread should exit -- if there is
     a joiner, put it back on the ready queue and free yourself.
     Otherwise, become a zombie */

  if (debug) { fprintf(stderr, "0x%x: Exiting\n", pt_self); }
  if (pt_self->joiner != NULL) {
    p = pt_self->joiner;
    p->state = READY;
    dl_insert_b(Readyq, p);
    free(pt_self);
    block_myself();
  } else if (Joinall != NULL) {
    free(pt_self);
    block_myself();
  } else {
    pt_self->state = ZOMBIE;
    block_myself();
  }
}

