#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <dispatch/dispatch.h>

#define MAX_CLIENTES 1000
#define MAX_ALEATORIO 5000
#define VAZIO -1

bool esperando[MAX_CLIENTES];
int filaDeClientes[MAX_CLIENTES];

dispatch_queue_t lockQueue;

int n, m; /* numero de processos */
int cabeca, clientesNaFila;
bool terminar = false;


/* Devolve valor aleatorio entre 0 e MAX_ALEATORIO */
unsigned long aleatorio(){
    return (unsigned long)(((double) rand())/RAND_MAX) * MAX_ALEATORIO;
}

int msleep(unsigned long milisec)
{
    struct timespec req={0},rem={0};
    time_t sec=(int)(milisec/1000);
    milisec=milisec-(sec*1000);
    req.tv_sec=sec;
    req.tv_nsec=milisec*1000000L;
    nanosleep(&req,&rem);
    return 1;
}

int atendeCliente(){
    int idCliente;
    if (clientesNaFila < 1)
        return VAZIO;
    idCliente = filaDeClientes[cabeca];
    cabeca = (cabeca + 1) % n;
    clientesNaFila--;
    return idCliente;       
}

void entraNaFila(int idCliente){
    filaDeClientes[cabeca + clientesNaFila] = idCliente;
    esperando[idCliente] = true;
    clientesNaFila++;
}


/* Processo Atendente */
void Atendente(int id) {
    __block int idCliente; /* pode sofrer alteracao dentro do bloco */
    while (! terminar) {
   	    dispatch_sync(lockQueue, ^{
   		    idCliente = atendeCliente();
   	    });
    	if (idCliente != VAZIO){
	        printf("Atendente %d pegou cliente %d\n", id , idCliente);
	        msleep(aleatorio());
	        esperando[idCliente] = false;
	        printf("Atendente %d finalizou atendimento");
    	}
    	else
            msleep(aleatorio());
    }
    return;
}

/* Processo Cliente */
void Cliente(int id){
    while (! terminar) {
        msleep(aleatorio());
        dispatch_sync(lockQueue, ^{
    		entraNaFila(id);
    	});
        printf("Cliente %d esperando\n", id);
        while (esperando[id])
            msleep(200);
        printf("Cliente %d atendido\n", id);
    }
    return;
}

void inicializa(int argc, char *argv[]){
    srand (time(NULL));

    if (argc < 3){
    	printf("Entre com o numero de clientes e atendentes\n");
        exit(0);
    }
    n = atoi(argv[1]); /* Clientes */
    m = atoi(argv[2]); /* Atendentes */
    cabeca = clientesNaFila = 0;
    lockQueue = dispatch_queue_create("secao.critica", NULL);
}

void despachaTarefas(dispatch_group_t clienteGrp, dispatch_group_t atendenteGrp){
	int i;
    dispatch_queue_t globalQueue;

    globalQueue = dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    for (i = 0; i < n; i++) {
        dispatch_group_async(clienteGrp, globalQueue, ^{
            Cliente(i);
        });
    }

    for (i = 0; i < m; i++) {
        dispatch_group_async(atendenteGrp, globalQueue, ^{
            Atendente(i);
        });    
    }
}

void esperaPeriodoDeExecucao(int argc, char *argv[]){
   msleep((argc > 3) ? atoi(argv[3]) : 30000);
}

void mandaTerminar(dispatch_group_t clienteGrp, dispatch_group_t atendenteGrp){
    terminar = true;
    dispatch_group_wait(clienteGrp, DISPATCH_TIME_FOREVER);
    dispatch_group_wait(atendenteGrp, DISPATCH_TIME_FOREVER);
}

int main(int argc, char *argv[]) {
  dispatch_group_t clienteGrp, atendenteGrp;
  
  inicializa(argc, argv);
  clienteGrp = dispatch_group_create();
  atendenteGrp = dispatch_group_create();

  despachaTarefas(clienteGrp, atendenteGrp);
  esperaPeriodoDeExecucao(argc, argv);
  mandaTerminar(clienteGrp, atendenteGrp);
  return 0;
}