#include <functional>
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>

using namespace std::chrono_literals;

using TypeCallbackRun = std::function<void (int)>;
using TypeCallbackBreak = std::function<bool (int)>;
using TypeCallbackFinish = std::function<void ()>;

std::mutex Mutex;

// ───────────────────────────────────────────────────────────────────────────────────

class Producer {
    TypeCallbackRun CallbackRun;
    TypeCallbackBreak CallbackBreak;
    TypeCallbackFinish CallbackFinish;
  public:
    void Run() {
      Mutex.lock();
      std::cout << "Run started (10 times): " << std::this_thread::get_id() << "\n";
      Mutex.unlock();
      for (int i = 0; i < 1000; ++i) {
        if (CallbackBreak && CallbackBreak(i)) break;
        if (CallbackRun) CallbackRun(i);
        Mutex.lock();
        std::cout << ".";
        Mutex.unlock();
        std::this_thread::sleep_for(10ms);
      }
      if (CallbackFinish) CallbackFinish();
      Mutex.lock();
      std::cout << "\nRun finished: " << std::this_thread::get_id() << "\n";
      Mutex.unlock();
    }
    void SetCallbacks(TypeCallbackRun r, TypeCallbackBreak b, TypeCallbackFinish f) {
      CallbackRun = r;
      CallbackBreak = b;
      CallbackFinish = f;
    }
};

// ───────────────────────────────────────────────────────────────────────────────────

class Consumer {
  public:
    std::atomic<int> Counter;
    std::atomic<bool> Ready;
    Consumer () {
      Counter.store(0);
      Ready.store(false);
    }
    void ProcCallbackRun(const int i) {
      std::lock_guard<std::mutex> Lock(Mutex);
      std::cout << "(" << i << ") ";
    }
    bool ProcCallbackBreak([[maybe_unused]] const int i) {
      return Counter.load() > 75;
    }
    void ProcCallbackFinish() {
      std::lock_guard<std::mutex> Lock(Mutex);
      std::cout << "\nthread finish: " << std::this_thread::get_id() << std::endl;
      Ready = true;
    }
};

// ───────────────────────────────────────────────────────────────────────────────────

int main() {
  Consumer Cons;
  Producer Prod;    
  Prod.SetCallbacks(
      std::bind(&Consumer::ProcCallbackRun, &Cons, std::placeholders::_1),
      std::bind(&Consumer::ProcCallbackBreak, &Cons, std::placeholders::_1),
      std::bind(&Consumer::ProcCallbackFinish, &Cons)
    );  
  // запуск
  std::cout << "main thread: " << std::this_thread::get_id() << std::endl;
  std::thread([&]() {
    Prod.Run();
  }).detach();
  while (!Cons.Ready) {
    std::this_thread::sleep_for(200ms);
    Mutex.lock();
    std::cout << "[" << Cons.Counter.load() << "] ";
    Cons.Counter++;
    Mutex.unlock();
  }
  std::cout << "exit program: " << std::this_thread::get_id() << std::endl;
  return 0;
}