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

struct argumento {
  int *avail_;
  pthread_mutex_t *mutex_;
  int incr_;
};

typedef enum { FALSE = 0, TRUE = 1 } booleano;

void *threadFunc(void *arg) {
  struct argumento *sol = (struct argumento *)arg;
  int s, j;

  for (j = 0; j < sol->incr_; j++) {
    // Lock the mutex
    s = pthread_mutex_lock(sol->mutex_);
    if (s != 0) /*Minimo control de errores*/
    {
      printf("mutex_lock error...\n");
      pthread_exit(NULL);
    }

    /* Let consumer know another unit is available */
    (*sol->avail_)++; /*Critical section*/
    printf("Variable avail incrementada por la hebra %lu, avail=%d\n",
           (unsigned long)pthread_self(), *sol->avail_);

    // Unlock mutex
    s = pthread_mutex_unlock(sol->mutex_);
    if (s != 0) /*Minimo control de errores*/
    {
      printf("mutex_unlock error...\n");
      pthread_exit(NULL);
    }
  }
  pthread_exit(NULL);
}

int main(int argc, char *argv[]) {
  if (argc < 3) {
    printf(
        "Ussage: ./a.out Number_of_increasing_for_thread1 "
        "Number_of_increasing_for_thread2 Number_of_increasing_for_threadN\n");
    exit(EXIT_FAILURE);
  }

  int avail = 0;
  pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

  pthread_t *thr;
  int s, j;
  int totThreads;
  int totRequired = 0;
  int numConsumed; /* Total units so far consumed */
  struct argumento *args;
  booleano done;

  // El numero de hebras a crear sera el numero de argumentos menos uno
  totThreads = argc - 1;
  thr = (pthread_t *)malloc((totThreads) * sizeof(pthread_t));
  args = (struct argumento *)malloc(totThreads * sizeof(struct argumento));

  /* Create all threads */
  for (j = 0; j < totThreads; j++) {
    args[j].avail_ = &avail;
    args[j].mutex_ = &mtx;
    args[j].incr_ = atoi(argv[j + 1]);

    totRequired += args[j].incr_; // Sum is the total of increments

    s = pthread_create(&thr[j], NULL, threadFunc, (void *)&args[j]);
    if (s != 0) /*Minimo control de errores*/
    {
      printf("pthread_create error...\n");
      exit(EXIT_FAILURE);
    }
  }

  printf("Main o hebra principal. Total de veces a decrementar la variable "
         "avail = %d\n",
         totRequired);
  /* Use a polling loop to check for available units */
  numConsumed = 0;
  done = FALSE;
  for (;;) // Continuous simulation
  {
    // Lock mutex
    s = pthread_mutex_lock(&mtx);
    if (s != 0) /*Minimo control de errores*/
    {
      printf("mutex_lock error...\n");
      exit(EXIT_FAILURE);
    }

    /*Critical section*/
    while (avail > 0) /* Consume all available units */
    {
      /* Do something with produced unit */
      numConsumed++;
      avail--;
      printf("Main o hebra principal, decrementando. avail=%d, "
             "numDecrementos=%d\n",
             avail, numConsumed);

      if (numConsumed == totRequired)
        done = TRUE; // enumeracion definida en la zona de variables globales
      else
        done = FALSE;
    }

    /* Una vez el main() ha consumido todo lo disponible en avail desbloquea el
       mutex para que otra hebra pueda realizar nuevos incrementos que poder
       consumir */
    s = pthread_mutex_unlock(&mtx); /*Desbloqueo de la barrera*/
    if (s != 0)                     /*Minimo control de errores*/
    {
      printf("mutex_unlock error...\n");
      exit(EXIT_FAILURE);
    }

    if (done) {
      printf("Main o hebra principal. Saliendo...Los %d incrementos producidos "
             "ya se han decrementado.\n",
             totRequired);
      break;
    } else
      printf("Main o hebra principal. Espera improductiva mientras avail sea "
             "<= 0\n");
  }

  for (j = 0; j < (argc - 1); j++) {
    s = pthread_join(thr[j], NULL);
    if (s != 0) /*Minimo control de errores*/
    {
      printf("pthread_join error...\n");
      exit(EXIT_FAILURE);
    }
  }

  free(thr);
  free(args);

  exit(EXIT_SUCCESS);
}