/*
 * Problema do barbeiro dorminhoco com vários barbeiros.
 */

/*
 * Modificado por Marília Felippe Chiozo para o projeto de implementação. A
 * cadeira do barbeiro é representada por /   \, o barbeiro dormindo por ZZZ,
 * o barbeiro acordado por B, as cadeiras da fila de espera por |   |, os
 * clientes por seus números (avançando sobre as letras minúsculas caso o
 * (número de clientes seja maior que 10), a porta da barbearia (onde aparecem
 * os clientes que não conseguem entrar) por ---   ---, e o visor por # #. Esta
 * implementação reserva um dígito só por cliente, precisando ser modificada
 * caso se queira mais de um dígito.
 */
#include <assert.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define N_CLIENTES 10
#define N_BARBEIROS 2
#define N_CADEIRAS 5
#define DRAWING_STR "%s\n\n%s\n\n%s\n_____________________________\n"

sem_t sem_cadeiras;
sem_t sem_cad_barbeiro[N_BARBEIROS];
sem_t sem_cabelo_cortado[N_BARBEIROS];
sem_t sem_cliente_cadeira[N_BARBEIROS];
pthread_mutex_t screen;

sem_t sem_escreve_visor, sem_le_visor;
int visor;

char line1[5 + 12 * N_BARBEIROS]; /* Barber's chair. */
char line2[2 + 4 * N_CADEIRAS]; /* Waiting chairs. */
char line3[] = "---   ---"; /* Barber shop door. */
char idmap[] = "0123456789abcdefghijklmnopqrstuvwxyz";

void* f_barbeiro(void* v)
{
 int id = *((int*) v);

 while(1)
 {
  /* Draws the barber asleep. */
  pthread_mutex_lock(&screen);
  line1[4 + 11 * id] = line1[5 + 11 * id] = line1[6 + 11 * id] = 'Z';
  printf(DRAWING_STR, line1, line2, line3);
  pthread_mutex_unlock(&screen);
  /* Waits for a client. */
  sem_wait(&sem_escreve_visor);
  visor = id;
  /* Draws the visor. */
  pthread_mutex_lock(&screen);
  line1[1] = idmap[id];
  printf(DRAWING_STR, line1, line2, line3);
  pthread_mutex_unlock(&screen);
  sem_post(&sem_le_visor);
  sem_wait(&sem_cliente_cadeira[id]);
  /* Draws the barber awake. */
  pthread_mutex_lock(&screen);
  line1[4 + 11 * id] = line1[5 + 11 * id] = ' ';
  line1[6 + 11 * id] = 'B';
  printf(DRAWING_STR, line1, line2, line3);
  pthread_mutex_unlock(&screen);
  sem_post(&sem_cabelo_cortado[id]);
  /* The barber goes back to sleep. */
  pthread_mutex_lock(&screen);
  line1[4 + 11 * id] = line1[5 + 11 * id] = line1[6 + 11 * id] = 'Z';
  printf(DRAWING_STR, line1, line2, line3);
  pthread_mutex_unlock(&screen);
  sleep(random() % 3);
 }
 return NULL;
}

void* f_cliente(void* v)
{
 int i;
 int id = *((int*) v);
 int minha_cadeira;

 sleep(random() % 3);
 if (sem_trywait(&sem_cadeiras) == 0)
 {
  /* Draws the client in the first free waiting chair he can find. */
  pthread_mutex_lock(&screen);
  for (i = 0; i < N_CADEIRAS; i++)
   if (line2[2 + 4 * i] == ' ')
    break;
  line2[2 + 4 * i] = idmap[id];
  printf(DRAWING_STR, line1, line2, line3);
  pthread_mutex_unlock(&screen);
  /* Waits for the visor. */
  sem_wait(&sem_le_visor);
  minha_cadeira = visor;
  sem_post(&sem_escreve_visor);
  sem_wait(&sem_cad_barbeiro[minha_cadeira]);
  /* Draws the client in a barber's chair. */
  pthread_mutex_lock(&screen);
  if (line1[1] == idmap[minha_cadeira])
   line1[1] = ' ';
  line1[9 + 11 * minha_cadeira] = idmap[id];
  line2[2 + 4 * i] = ' ';
  printf(DRAWING_STR, line1, line2, line3);
  pthread_mutex_unlock(&screen);
  /* Signals others waiting on the resources and waits for the haircut. */
  sem_post(&sem_cliente_cadeira[minha_cadeira]);
  sem_post(&sem_cadeiras);
  sem_wait(&sem_cabelo_cortado[minha_cadeira]);
  /* Signals others waiting on a barber's chair. */
  sem_post(&sem_cad_barbeiro[minha_cadeira]);
  /* Erases the client from the drawing. */
  pthread_mutex_lock(&screen);
  if (line1[9 + 11 * minha_cadeira] == idmap[id])
  {
   line1[9 + 11 * minha_cadeira] = ' ';
   printf(DRAWING_STR, line1, line2, line3);
  }
  pthread_mutex_unlock(&screen);
 }
 else
 {
  pthread_mutex_lock(&screen);
  line3[4] = idmap[id];
  printf(DRAWING_STR, line1, line2, line3);
  line3[4] = ' ';
  pthread_mutex_unlock(&screen);
 }
 return NULL;
}

int main()
{
 pthread_t thr_clientes[N_CLIENTES], thr_barbeiros[N_BARBEIROS];
 int i, id_cl[N_CLIENTES], id_bar[N_BARBEIROS];

 assert(N_CLIENTES <= 36);
 /* pthreads initialization stuff. */
 sem_init(&sem_cadeiras, 0, 5);
 sem_init(&sem_escreve_visor, 0, 1);
 sem_init(&sem_le_visor, 0, 0);
 for (i = 0; i < N_BARBEIROS; i++)
 {
  sem_init(&sem_cad_barbeiro[i], 0, 1);
  sem_init(&sem_cliente_cadeira[i], 0, 0);
  sem_init(&sem_cabelo_cortado[i], 0, 0);
 }
 /* Initializes the string with the chairs. */
 line2[0] = '|';
 for (i = 0; i < N_CADEIRAS; i++)
  strcpy((line2 + 1 + 4 * i), "   |");
 /* Initializes the string with the barbers' chairs. */
 strcpy(line1, "# # ");
 for (i = 0; i < N_BARBEIROS; i++)
  strcpy((line1 + 4 + 11 * i), "zzz/   \\   ");
 /* Client threads creation. */
 for (i = 0; i < N_CLIENTES; i++)
 {
  id_cl[i] = i;
  pthread_create(&thr_clientes[i], NULL, f_cliente, (void*) &id_cl[i]);
 }
 /* Barber thread creation. */
 for (i = 0; i < N_BARBEIROS; i++)
 {
  id_bar[i] = i;
  pthread_create(&thr_barbeiros[i], NULL, f_barbeiro, (void*) &id_bar[i]);
 }
 /* Waits for client threads to finish. */
 for (i = 0; i < N_CLIENTES; i++)
  pthread_join(thr_clientes[i], NULL);
 /* Barbeiros são assassinados */
 return 0;
}