/*
 * Swarthmore College, CS 31
 * Copyright (c) 2021 Swarthmore College Computer Science Department,
 * Swarthmore PA
 */

/*
 * Starting point for in-class problem:
 *  Implement a solution to the bounded buffer problem spawn off num_prods
 *  producers and num_consumers consumers. Each producer thread produces
 *  num_items items. Each consumer thread consumes num_items items.
 */

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

// use global variables for shared buffer state
static char *buff;     // the buffer
static int  N;         // total capacity
static int  size;      // current num elements
static int  next_in;   // next insertion index
static int  next_out;  // next remove index
static int  num_items; // number of items each thread should produce/consume

// add any synchronization primitives here (e.g. mutex, condition variable)



// the thread main for producers and consumers
void *producer(void *arg);
void *consumer(void *arg);

// these functions are implemented for you
static void *my_malloc(size_t size);
static void add_to_queue(char item);
static char remove_from_queue();
static void print_buffer();       // for debugging

/***************************************************************/
int main(int argc, char **argv) {

  pthread_t *ptids, *ctids;
  int i, num_prods, num_cons;
  // add other local variables you need

  if (argc != 4) {
    printf("usage: ./prodcons n_prods_cons n_items buff_size\n");
    exit(1);
  }

  // no error checking here - you can add if you want
  num_prods = num_cons = atoi(argv[1]);
  num_items = atoi(argv[2]);
  N = atoi(argv[3]);

  printf("%d prods/cons, each produces/consumes %d items, buff size %d\n",
      num_prods, num_items, N);

  ptids = my_malloc(sizeof(pthread_t)*num_prods);
  ctids = my_malloc(sizeof(pthread_t)*num_cons);

  buff = (char *)my_malloc(sizeof(char)*N);
  size = 0;
  next_in = 0;
  next_out = 0;

  // TODO: initialize any synchronization primitives prior creating threads


  // TODO: create the producer and consumer threads
  for (i=0; i < num_prods; i++) {

  }

  for (i=0; i < num_cons; i++) {

  }

  // wait for threads to exit
  for (i=0; i < num_prods; i++) {
    pthread_join(ptids[i], 0);
  }

  for (i=0; i < num_cons; i++) {
    pthread_join(ctids[i], 0);
  }

  free (buff);
  free (ptids);
  free (ctids);
  exit(0);
}

/****************************************************************/
void *producer(void *arg){
  // TODO: implement this (feel free to change the return value)
  return NULL;
}

/****************************************************************/
void *consumer(void *arg){
  // TODO: implement this (feel free to change the return value)
  return NULL;
}

/****************************************************************/
// Add an item to the circular buffer
// Note: this function does no checking that there is enough
// space to add, the caller is responsible for that!
//   parameter item: the value to add (a character)
static void add_to_queue(char item) {
  buff[next_in] = item;
  next_in = (next_in + 1) % N;
  size += 1;
}

/****************************************************************/
// Remove an item from the circular buffer
// Note: this function does no checking that there is something
// in the queue to remove, the caller is responsible for that!
//   returns: the next item that was removed
static char remove_from_queue() {
  char item;
  item = buff[next_out];
  next_out = (next_out + 1) % N;
  size -= 1;
  return item;
}

/****************************************************************/
// print out the contents of the buffer (for debugging)
static void print_buffer() {
  int i, index;
  printf("Buffer size %d ************************\n", size);
  for(i=0; i < size; i++) {
     index = (next_out + i) % N;
     printf("%2d:%2d ", index, buff[index]);
  }
  printf("\n next in = %d  next out %d\n", next_in, next_out);
  printf("***************************************\n");
}

/*******************************************************************/
// A wrapper function around malloc that calls perror and exits on
// an error. Saves you from having to do error checking every time.
static void *my_malloc(size_t size){
  void *ret;
  ret = malloc(size);
  if(!ret) {
    perror("malloc array error");
    exit(1);
  }
  return ret;
}