/* 
 * By George Peter Staplin
 *
 * 1. design task_in() and task_out() 
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <signal.h>
#include <ucontext.h>
#include <assert.h>
#include <string.h>
#include <poll.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>

struct task {
 void *stack;
 ucontext_t context;
 dlinkedlist;
};

struct task *tasks = NULL;
struct task *current_task = NULL;
struct task *task_pool;

enum {
 TASK_STACK_SIZE = 4096
};

sigset_t task_block (void) {
 sigset_t set, oset;

 sigemptyset (&set);
 sigaddset (&set, SIGALRM);
 if (sigprocmask (SIG_BLOCK, &set, &oset)) {
  perror ("sigprocmask");
  exit (EXIT_FAILURE);
 }

 return oset;
}

void task_unblock (sigset_t set) {
 if (sigprocmask (SIG_SETMASK, &set, NULL)) {
  perror ("sigprocmask");
  exit (EXIT_FAILURE);
 }
}

struct task *task_alloc (void) {
 struct task *t;

 t = malloc (sizeof *t);
 if (NULL == t) {
  perror ("malloc struct task");
  exit (EXIT_FAILURE);
 }

 t->stack = malloc (TASK_STACK_SIZE);
 if (NULL == t->stack) {
  perror ("malloc task stack");
  exit (EXIT_FAILURE);
 }

 dlinit t;

 return t;
}

struct task *task_create (void (*func) ()) {
 struct task *t;

 t = task_alloc ();
 
 if (getcontext (&t->context)) {
  perror ("getcontext");
  exit (EXIT_FAILURE);
 }

 t->context.uc_link = NULL;
 t->context.uc_stack.ss_sp = t->stack;
 t->context.uc_stack.ss_size = TASK_STACK_SIZE;
 t->context.uc_stack.ss_flags = 0;

 makecontext (&t->context, func, 0);
 

 return t;
}

void task_die (void) {
 sigset_t set = task_block ();

 dlremove tasks, current_task;
 dlinit current_task;
 dlinsert task_pool, current_task;
 current_task = tasks;

 foreach (i, tasks) {
  printf ("i from tasks %p\n", (void *)i);
 }

 task_unblock (set);
}


void task_scheduler (int sig, siginfo_t *info, void *context) {
 ucontext_t *c = context;
 sigset_t set;

 set = task_block ();

 current_task->context = *c;

 if (NULL == current_task->_coop_next) {
  current_task = tasks;
 } else {
  current_task = current_task->_coop_next;
 }

 *c = current_task->context;

 task_unblock (set);
}

void box (void) {
 int i = 5000; 

 while (i > 0) {
  puts ("BOX");
  --i;
 }

 task_die ();
}


int main () { 
 int error;
 struct itimerval timer;
 struct sigaction action;
 char *stack;
 ucontext_t context;
 struct task *t, *current;
 
 action.sa_handler = SIG_IGN;
 action.sa_sigaction = task_scheduler;
 sigemptyset (&action.sa_mask);
 action.sa_flags = SA_SIGINFO | SA_RESTART;

 error = sigaction (SIGALRM, &action, NULL);

 if (error) {
  perror ("sigaction");
  return EXIT_FAILURE;
 }

 current = task_alloc ();
 current_task = current;

 dlinsert tasks, current;

 t = task_create (box);
 t->context.uc_link = &current->context;
 
 dlinsert tasks, t;


 timer.it_interval.tv_sec = 0;
 timer.it_interval.tv_usec = 20;
 
 timer.it_value.tv_sec = 0;
 timer.it_value.tv_usec = 10;

 
 {
  struct pollfd pfd;
  char buf[1024];
  sigset_t set;

  pfd.fd = STDIN_FILENO;
  pfd.events = POLLIN;
 
  set = task_block ();

  error = setitimer (ITIMER_REAL, &timer, NULL);

  if (error) {
   perror ("setitimer");
   return EXIT_FAILURE;
  } 

  if (getcontext (&current->context)) {
   perror ("getcontext");
   return (EXIT_FAILURE);
  }

  puts ("AFT GETCONTEXT");

  task_unblock (set);

 
  while (1) {
   errno = 0;
   if (poll (&pfd, 1, INFTIM) < 0) {
    if (EINTR == errno)
     continue; 
    perror ("poll");
    exit (EXIT_FAILURE);
   }
   puts ("STDIN READABLE");
   if (NULL == fgets (buf, sizeof buf, stdin)) {
    perror ("fgets");
    exit (EXIT_FAILURE);
   }
   puts ("buf");
  }
 }

 return EXIT_SUCCESS;
}
