#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include "printqsim.h"

/*
 * job queue structure
 */
typedef struct {
  Job **jobs;
  int head;
  int tail;
  int njobs;
  pthread_mutex_t *lock;
  pthread_cond_t *full;		/* condition variable controlling fullness */
  pthread_cond_t *empty;	/* condition variable controlling emptiness */
} Buffer;
  
void initialize_state(SimParameters *p)
{
  Buffer *b;
  pthread_attr_t attr;

  b = (Buffer *) malloc(sizeof(Buffer));
  b->jobs = (Job **) malloc(sizeof(Job *)*p->bufsize);
  b->head = 0;
  b->tail = 0;
  b->njobs = 0;
  b->lock = (pthread_mutex_t *) malloc(sizeof(pthread_mutex_t));
  /*
   * make space enough to hold condition variables
   */
  b->full = (pthread_cond_t *) malloc(sizeof(pthread_cond_t));
  b->empty = (pthread_cond_t *) malloc(sizeof(pthread_cond_t));
  /*
   * initialize the mutex and cond vars
   */
  pthread_mutex_init(b->lock, NULL);
  /*
   * if this is set to SCHED_FIFO the solution works
   */
  pthread_attr_setschedpolicy(&attr,SCHED_RR);
  pthread_cond_init(b->full, NULL);
  pthread_cond_init(b->empty, NULL);
  p->state = (void *)b;

  return;
}

void submit_job(Agent *s, Job *j)
{
	SimParameters *p;
  	Buffer *b;

	p = s->p;
	b = (Buffer *)p->state;

	/*
	 * lock this buffer so we can test under lock
	 */
	pthread_mutex_lock(b->lock);
	while(1)
	{
		/*
		 * if a new job will fit
		 */
		if (b->njobs < p->bufsize) 
		{
			/*
			 * enqueue it at the head
			 */
      			b->jobs[b->head] = j;
			/*
			 * bump the head pointer
			 */
      			b->head = (b->head + 1) % p->bufsize;
 			b->njobs++;
			/*
			 * if the queue was empty, signal a printer
			 * thread waiting for a job to arrive
			 */
      			if (b->njobs == 1) 
			{
				pthread_cond_signal(b->empty);
			}
			/*
			 * drop the lock -- we are leaving the critical
			 * section
			 */
      			pthread_mutex_unlock(b->lock);
			/*
			 * job successfully queued, bail out
			 */
      			return;
    		} 
		else /* the queue is full -- we must wait until it has space */ 
		{
      		printf("%4d: user %2d blocking because the queue is full\n", 
             		time(0)-p->starttime, s->id);
      			fflush(stdout);
			/*
			 * wait here -- printer thread will signal
			 * when there is space
			 */
      			pthread_cond_wait(b->full, b->lock);
			/*
			 * when we wake up here, we are holding the lock
			 * and we are the only thread in the critical section
			 */
		}
    	}
}


Job *get_print_job(Agent *s)
{
	SimParameters *p;
  	Buffer *b;
  	Job *j;

	p = s->p;
	b = (Buffer *)p->state;


	/*
	 * lock this buffer -- we are going to mess with it
	 */
	pthread_mutex_lock(b->lock);
	while(1)
	{
		/*
		 * if there are jobs in the queue
		 */
		if (b->njobs > 0)
		{
			/*
			 * get the one at the tail
			 */
			j = b->jobs[b->tail];
			b->tail = (b->tail + 1) % p->bufsize;
			b->njobs--;
			/*
			 * if the buffer was full before we took this
			 * job off the queue, we must signal any waiting
			 * user threads
			 */
			if (b->njobs == p->bufsize-1) 
			{
				pthread_cond_signal(b->full);
			}
			/*
			 * those threads won't run (if they are there)
			 * until we leave the critical section
			 *
			 * drop the lock to get out
			 */
      			pthread_mutex_unlock(b->lock);
      			return j;
		}
		else /* there are no jobs in the queue -- 
			wait until there are */
		{
      printf("%4d: prnt %2d blocking because the queue is empty\n", 
             		time(0)-p->starttime, s->id);
    			fflush(stdout);
			/*
			 * wait here until a user job signals that the
			 * queue is no longer empty
			 */
      			pthread_cond_wait(b->empty, b->lock);
			/*
			 * when we wake up, we know that a user has signaled
			 * us.  We are holding the lock and the only thread
			 * in the critical section
			 */
    		}
  	}
}