// prod-cons.cpp - Productor-consumidor con semáforos
#include <iostream>
#include <iomanip>
#include "hilo.h"
using namespace std;

#define ELEMENTOS_BUFER 16 
#define TOTAL_DATOS 512

typedef struct
{
  int bufer[ELEMENTOS_BUFER]; // matriz de datos
  int ocupados;          // elementos de la matriz ocupados
  int ind_p, ind_c;      // índices del productor y del consumidor
  pthread_mutex_t exmut; // exclusión mutua
  pthread_cond_t vacios; // semáforo contador de elementos vacíos
  pthread_cond_t llenos; // semáforo contador de elementos llenos
} st_bufer_t;

class CHiloProductor : public CHilo
{
  private:
    st_bufer_t *st_b;
  public:
    CHiloProductor(st_bufer_t *bufer)
    {
      st_b = bufer;
    }
    
    void *fnHilo()
    {
      int i;
      for(i = 0; i < TOTAL_DATOS; i++)
      {
        pthread_mutex_lock(&st_b->exmut);
        while (st_b->ocupados == ELEMENTOS_BUFER)
          pthread_cond_wait(&st_b->vacios, &st_b->exmut);
        assert(st_b->ocupados < ELEMENTOS_BUFER);
        st_b->bufer[st_b->ind_p] = i;
        st_b->ind_p = (st_b->ind_p + 1) % ELEMENTOS_BUFER;
        st_b->ocupados++;
        /* ahora, o st_b->ocupados < ELEMENTOS_BUFER y st_b->ind_p
           es el índice del siguiente elemento vacío en el bufer, o
           st_b->ocupados == ELEMENTOS_BUFER y st_b->ind_p es el índice
           del siguiente elemento lleno que será vaciado por un
           consumidor (st_b->ind_p == st_b->ind_c) */
        cout << "bufer  <-- " << setw(5) << i << "  hilo productor\n";
        pthread_cond_signal(&st_b->llenos);
        pthread_mutex_unlock(&st_b->exmut);
      }
    }
};

class CHiloConsumidor : public CHilo
{
  private:
    st_bufer_t *st_b;
  public:
    CHiloConsumidor(st_bufer_t *bufer)
    {
      st_b = bufer;
    }
    
    void *fnHilo()
    {
      int i, a;
      for(i = 0; i < TOTAL_DATOS; i++)
      {
        pthread_mutex_lock(&st_b->exmut);
        while(st_b->ocupados == 0)
          pthread_cond_wait(&st_b->llenos, &st_b->exmut);
        assert(st_b->ocupados > 0);
        a = st_b->bufer[st_b->ind_c];
        st_b->ind_c = (st_b->ind_c + 1) % ELEMENTOS_BUFER;
        st_b->ocupados--;
        /* ahora, o st_b->ocupados > 0 y st_b->ind_c is el índice
           del siguiente elemento ocupado en el bufer, o
           st_b->ocupados == 0 y st_b->ind_c es el índice del siguiente
           elemento vacío que será llenado por un productor
           (st_b->ind_c == st_b->ind_p) */
        cout << "bufer  --> " << setw(5) << a << "  hilo consumidor\n";
        pthread_cond_signal(&st_b->vacios);
        pthread_mutex_unlock(&st_b->exmut);
      }
    }
};

int main()
{
  // Estructura de datos compartida por los hilos
  st_bufer_t st_bufer;
  // Iniciar la estructura st_bufer
  st_bufer.ind_p = st_bufer.ind_c = st_bufer.ocupados = 0;
  pthread_mutex_init(&st_bufer.exmut, 0);
  pthread_cond_init(&st_bufer.vacios, 0);
  pthread_cond_init(&st_bufer.llenos, 0);

  try
  {
    // Crear e iniciar los hilos
    CHiloProductor hilo_p(&st_bufer);
    hilo_p.iniciar();
    CHiloConsumidor hilo_c(&st_bufer);
    hilo_c.iniciar();
  
    // Esperar a que los hilos trabajadores terminen
    hilo_p.esperar_finalizacion();
    hilo_c.esperar_finalizacion();
  }
  catch(string str)
  {
    cout << str << endl;
  }
  
  system("pause");
}

