#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <semaphore.h>

//Numero de hilos a crear
#define N 20
//Maximo numero de hilos que pueden pasar la barrera
#define LIMIT 5

/*
Author: Erwin Meza Vega
En este programa se implementa la primitiva de sincronizacion
llamada "Barrera". Esta primitiva permite que un numero determinado
de procesos puedan pasar un punto de sincronizacion, mientras otros
esperan hasta que el ultimo haya terminado de pasar por la barrera.
Se simula una espera aleatoria con la llamada al sistema "sleep", para
permitir que la CPU se distribuya entre los hilos.
*/

/* Tipo thread_body 
   tipo para una funcion void que recibe como parametro un
   argumento de tipo void *
*/
typedef void (*thread_body)(void * arg);

/*
 Estructura de datos para el hilo
*/
typedef struct {
  pthread_t id; //Identificador del hilo, retornado por pthread_create
  thread_body start_func; //funcion (rutina) que ejecutara el hilo
  void * arg; //Parametro para pasar al hilo
}Thread;

//Tipo para el semaforo
typedef sem_t semaphore;

/*
Rutina para crear un hilo.
Parametros de entrada:
    func: Nombre de la funcion void (void *) que ejecutar el hilo
    arg:  Argumento a pasar al hilo.
Valor de retorno:
    Estructura de tipo Thread con el hilo creado, o nulo si ocurre
    error en la creacion del hilo.
*/
Thread * start_thread(thread_body func, void * arg) {
  Thread* t;
  t=(Thread*)malloc(sizeof(Thread));
  t->start_func=func;
  t->arg = arg;
  pthread_create(&(t->id), NULL, (void*)t->start_func, t->arg);  
  return t;
}


//Rutina down: Realiza la llamada sem_wait (down) sobre un semaforo
//Parametros de entrada: apuntador al semaforo
//Parametros de salida: ninguno
void down(semaphore *sem) {
  sem_wait(sem);
}

//Rutina up: Realiza la llamada sem_post (up) sobre un semaforo
//Parametros de entrada: apuntador al semaforo
//Parametros de salida: ninguno
void up(semaphore *sem) {
  sem_post(sem);
}


//Declaracion de los semaforos
semaphore enter_mutex; //Mutex para los procesos que llegan a la barrera
semaphore exit_mutex; //Mutex para los procesos que salen de la barrera
semaphore s; //Semaforo para cada proceso
int count; //Contador de los procesos que desean entrar a la barrera
int exit_count; //Contador de los procesos que ya han pasado la barrera

/*
Rutina que va a ejecutar cada hilo.
Parametros de entrada:
  param: Apuntador (void *) al parametro de entrada al hilo.
  En este caso es un numero entero, que especifica el 
  id del hilo.
*/

void process(void *param) {
  int i;
  int id;
 
  id = *((int*)param);
	
  printf("Thread %d created\n", id);
  sleep(rand() % 3); //Simular espera aleatoria en la entrada
  printf("Thread %d is ready to count\n",id);
  down(&enter_mutex);
	printf("Thread %d has count = %d \n",id, count);
	count = count + 1;		
	if(count == LIMIT){
		printf("Last thread %d is opening the barrier!\n",id);
		up(&s);
		printf("Thread %d has not released enter_mutex!\n",id);
		//Observe que el ultimo proceso (count = LIMIT)
		//no libera el enter_mutex!
	}else {
		up(&enter_mutex); //Los LIMIT - 1 procesos liberan el mutex
	}  

  printf("Thread %d trying to pass the barrier\n",id);
  down(&s); 	
  printf("Thread %d has passed the barrier!\n",id);
  down(&exit_mutex);	
	exit_count = exit_count + 1;
	sleep(rand() %3); //Simular espera aleatoria dentro de la barrera	
	//Si el hilo no es el ultimo en salir de la barrera,
	//dar paso al siguiente
	//El ultimo hilo (exit_count = LIMIT no debe dar paso a mas
	//hilos en la barrera, y ademas debe liberar enter_mutex para
	//permitir que otros hilos pasen la barrera.
	if(exit_count != LIMIT){ 
		printf("Thread %d opens the barrier for next process\n",id);
		up(&s);
	}
	else{
		printf("Thread %d is the last to pass the barrier!\n",id);
		exit_count = 0;
		count = 0;
		printf("Thread %d is releasing enter_mutex..!\n",id);
		up(&enter_mutex);
	}
  up(&exit_mutex);
	

  printf("Thread %d finished\n", id);
  pthread_exit(0);
}

//Arreglo de apuntadores a los hilos
Thread * hilos[N];
//Arreglo para los identificadores de los hilos
int id[N];

//Hilo principal del proceso.
//Este hilo se encarga de crear N hilos, que ejecutaran
//la rutina llamada process.
int main(int argc, char * argv[]) {

  int i;
  printf("Main Started\n");
  //Inicializamos los contadores
  count = 0;
  exit_count =0;
  //Inicializar el enter_mutex
   sem_init(&enter_mutex, 0, 1);

  //Inicializar el semaforo s (la barrera)
   sem_init(&s,0,0);

  //Inicializar el semaforo exit_mutex
   sem_init(&exit_mutex, 0, 1);

  //Crear los N hilos
  for (i=0; i<N; i++) { 
    id[i] = i;
    hilos[i] = start_thread(process, (void*)&id[i]);
  }

   //El hilo principal espera hasta que terminen todos los procesos
   for (i=0; i<N; i++) { 
    pthread_join(hilos[i]->id, NULL);
  }
  printf("Main Finished\n");
}