#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <time.h>
#include <pthread.h>
#include <errno.h>
#include <malloc.h>
#include "semaphore.h"

#define CAPACITY              10
#define CONSUMERS              2
#define COKES_PER_CONSUMER    10
#define TIME_BETWEEN_COKES   3.0
#define TIME_BETWEEN_REFILLS 1.0

int verbose = 1;

typedef struct timeval Timeval;

typedef struct {
  int cokes;
} Environment;

static void perror_exit (char *s)
{
  perror (s);
  exit (-1);
}

Environment *make_environment ()
{
  Environment *env = (Environment *) malloc (sizeof (Environment));
  if (env == NULL) {
    perror_exit ("malloc failed");
    exit (-1);
  }
  env->cokes = 0;
  return env;
}

/* DRANDOM: choose a random floating-point number between zero and
   one, based on a 31-bit random integer */

double drandom ()
{
  return (double) (random() & 0x7fffffff) / (double) 0x7fffffff;
}

/* CHOOSE FROM EXPONENTIAL : choose a value from an exponential
   distribution with the given parameter */

double choose_from_exponential (double lambda)
{
  double x;

  do x = drandom (); while (x == 0.0);
  return -log (drandom ()) / lambda;
}

void microsleep (int usecs)
{
  Timeval time[1];
  time->tv_sec = 0;
  time->tv_usec = usecs;
  select (0, NULL, NULL, NULL, time);
}

void random_sleep (double duration)
{
  int time = (int) choose_from_exponential (1/duration);
  microsleep (time);
}

void *producer_entry (void *arg)
{
  int i;
  Environment *env = (Environment *) arg;

  for (i=0; i<CONSUMERS * COKES_PER_CONSUMER; i++) {
    env->cokes++;
    if (verbose) {
      printf ("I just put in a coke.  There are %d left.\n", env->cokes);
    }
    random_sleep (TIME_BETWEEN_REFILLS);
  }
  pthread_exit (NULL);
}

void *consumer_entry (void *arg)
{
  int i;
  Environment *env = (Environment *) arg;

  for (i=0; i<COKES_PER_CONSUMER; i++) {
    env->cokes--;
    if (verbose) {
      printf ("I just took a coke. There are %d left.\n", env->cokes);
    }
    random_sleep (TIME_BETWEEN_COKES);
  }
  pthread_exit (NULL);
}

int main ()
{
  Environment *env;
  pthread_t producer;
  pthread_t consumer;
  int i, ret;

  env = make_environment ();

  // seed the random number generator
  srandom (17);

  // make the threads
  ret = pthread_create (&producer, NULL, producer_entry, (void *) env);
  if (ret == -1) perror_exit ("pthread_create failed");

  for (i=0; i<CONSUMERS; i++) {
    ret = pthread_create (&consumer, NULL, consumer_entry, (void *) env);
    if (ret == -1) perror_exit ("pthread_create failed");
  }

  sleep(10);
  
  return 0;
}
