#include <condition_variable>
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <chrono>
#include <random>
 
class CyclicBuffer{
private:
  std::vector<int> buffer;
  size_t takeIndex;
  size_t putIndex;
  size_t remainingCapacity;
 
  std::mutex m;
  std::condition_variable notFull, notEmpty;
 
public:
  CyclicBuffer(size_t size){
    buffer.resize(size);
    takeIndex = -1;
    putIndex = 0;
    remainingCapacity = buffer.size();
  }
 
  CyclicBuffer(){
    takeIndex = -1;
    putIndex = 0;
  }
 
  void init(size_t size){
    buffer.resize(size);
    remainingCapacity = buffer.size();
  }
 
  void put(int e){
    std::unique_lock<std::mutex> lk(m);
    while(remainingCapacity==0){
      notFull.wait(lk);
    }
    buffer[putIndex] = e;
    if(takeIndex==-1) takeIndex = putIndex;
    remainingCapacity--;
    putIndex = (putIndex+1) % buffer.size();
    notEmpty.notify_one();
  }
 
  int take(){
    std::unique_lock<std::mutex> lk(m);
    while(takeIndex==-1 || remainingCapacity==buffer.size()){
      notEmpty.wait(lk);
    }
    int e = buffer[takeIndex];
    remainingCapacity++;
    takeIndex = (takeIndex+1) % buffer.size();
    notFull.notify_one();
    return e;
  }
};
 
CyclicBuffer buffer;
 
void consumer(int n){
  // initialisation d'un generateur pseudo aleatoire
  std::mt19937_64 eng{std::random_device{}()};
  std::uniform_int_distribution<> dist{10, 100};
 
  for(int i=0; i<n; i++){
    std::cout << "I want to consume ! " << std::endl;
    auto res = buffer.take();
    // attente d'un petit temps aleatoire
    std::this_thread::sleep_for(std::chrono::milliseconds{dist(eng)});
    std::cout << "I got " << res << std::endl;
  }
}
 
void producer(int n, int val){
  // initialisation d'un generateur pseudo aleatoire
  std::mt19937_64 eng{std::random_device{}()};
  std::uniform_int_distribution<> dist{10, 100};
  std::uniform_int_distribution<> dist2{500, 1500};
 
  for(int i=0; i<n; i++){
    std::cout << "I produce " << val << std::endl;
    // attente d'un temps aleatoire
    std::this_thread::sleep_for(std::chrono::milliseconds(dist2(eng)));
    buffer.put(val);
    // attente d'un petit temps aleatoire
    std::this_thread::sleep_for(std::chrono::milliseconds{dist(eng)});
    std::cout << "done" << std::endl;
  }
}
 
int main(int argc, char** argv){
 
  std::vector<std::thread> consumers;
  std::vector<std::thread> producers;
  buffer.init(5);
 
  // 100 consommateurs consommant 10 elements chacun
  for(int i=0; i<100; i++){
    consumers.push_back(std::thread(consumer, 10));
  }
 
  // 10 producteurs produisant 100 elements chacun
  for(int i=0; i<10; i++){
    producers.push_back(std::thread(producer, 100, i));
  }
 
  for(std::thread& c: consumers) c.join();
  for(std::thread& p: producers) p.join();
 
  return 0;
}