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

#define SIZE 1000000

int verbose = 0;
int interval = 1000;

typedef struct {
  int count;
  int array[SIZE];
  Semaphore *semaphore;
} Environment;

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

Environment *make_environment ()
{
  int i;

  Environment *env = (Environment *) malloc (sizeof (Environment));
  if (env == NULL) perror_exit ("malloc failed");
  env->count = 0;

  for (i=0; i<SIZE; i++) {
    env->array[i] = 0;
  }

  /* initial value = 1, which represents an unlocked mutex */
  env->semaphore = make_semaphore (1);
  return env;
}

int get_count (Environment *env)
{
  int id = env->count;
  env->count++;
  return id;
}

void loop_and_count (pthread_t self, Environment *env)
{
  int id;

  while (1) {
    semaphore_wait (env->semaphore);
    id = get_count (env);
    semaphore_signal (env->semaphore);

    if (verbose && id % interval == 0) {
      printf ("%ld got %d\n", self, id);
    }

    if (id >= SIZE) return;
    env->array[id]++;
  }
}

void check_array (Environment *env)
{
  int i;

  printf ("Checking...\n");

  for (i=0; i<SIZE; i++) {
    if (env->array[i] != 1) {
      printf ("%d\t%d\n", i, env->array[i]);
    }
  }
}

/* The entry procedure is where the child thread will start executing. */

void *entry (void *arg)
{
  pthread_t self = pthread_self ();

  /* The entry procedure takes a single pointer as an argument.
     It is a pointer to the Environment structure that contains
     all the variables the parent and child will share. */

  Environment *env = (Environment *) arg;

  /* the child keeps getting ids and incrementing array
     elements until it gets an id that is bigger than the
     array; then it quits */

  loop_and_count (self, env);
  printf ("Child done looping.\n");

  pthread_exit (NULL);
}

int main ()
{
  Environment *env;
  pthread_t self;
  pthread_t child;
  int ret;

  self = pthread_self ();

  env = make_environment ();

  ret = pthread_create (&child, NULL, entry, (void *) env);
  if (ret == -1) perror_exit ("pthread_create failed");

  loop_and_count (self, env);

  printf ("Parent done looping.\n");

  ret = pthread_join (child, NULL);
  if (ret == -1) perror_exit ("pthread_join failed");
  
  check_array (env);
  return 0;
}
