//: C11:CarBuilder.cpp {RunByHand}
// Z knihy "Myslíme v jazyce C++, část 2" od Bruce Eckela a Chucka Allisona.
// (a) 1995-2004 MindView, Inc. Všechna práva vyhrazena.
// Přečtěte si podmínky používání zdrojových kódů umístěné v souboru 
// 'Licence.txt' dodávaném spolu s balíčkem kódu dostupným na www.MindView.net.
// Jak funguje funkce broadcast().
//{L} ZThread
#include <iostream>
#include <string>
#include "zthread/Thread.h"
#include "zthread/Mutex.h"
#include "zthread/Guard.h"
#include "zthread/Condition.h"
#include "zthread/ThreadedExecutor.h"
#include "TQueue.h"
using namespace ZThread;
using namespace std;

class Auto {
  int id;
  bool motor, prevodovka, kola;
public:
  Auto(int idn) : id(idn), motor(false),
  prevodovka(false), kola(false) {}
  // Prázdný objekt Auto:
  Auto() : id(-1), motor(false),
  prevodovka(false), kola(false) {}
  // Nesybnchronizováno -- Předpokládají se 
	// atomické operace s typem bool:
  int getId() { return id; }
  void pridatMotor() { motor = true; }
  bool motorNamontovan() { return motor; }
  void pridatPrevodovku() { prevodovka = true; }
  bool prevodovkaNamontovana() { return prevodovka; }
  void pridatKola() { kola = true; }
  bool kolaNamontovana() { return kola; }
  friend ostream& operator<<(ostream& os, const Auto& a) {
    return os << "Auto " << a.id << " ["
      << " motor: " << a.motor
      << " prevodovka: " << a.prevodovka
      << " kola: " << a.kola << " ]";
  }
};

typedef CountedPtr< TQueue<Auto> > FrontaAut;

class StavitelRamu : public Runnable {
  FrontaAut frontaAut;
  int pocitadlo;
public:
  StavitelRamu(FrontaAut& fa) : frontaAut(fa),pocitadlo(0) {}
  void run() {
    try {
      while(!Thread::interrupted()) {
        Thread::sleep(1000);
        // Vytvořit rám:
        Auto a(pocitadlo++);
        cout << a << endl;
        // vlořit do fronty
        frontaAut->put(a);
      }
    } catch(Interrupted_Exception&) { /* Konec */ }
    cout << "StavitelRamu ukončen" << endl;
  }
};

class Drzak {
  Auto a; // Uchovává auto, na kterém se právě pracuje
  bool obsazen;
  Mutex zamekPrace, zamekHotovo;
  Condition podminkaPrace, podminkaHotovo;
  bool pracujeMotorovyRobot, pracujeKolovyRobot, 
			 pracujePrevodovkovyRobot;
public:
  Drzak()
  : podminkaPrace(zamekPrace), podminkaHotovo(zamekHotovo) {
    obsazen = false;
    pracujeMotorovyRobot = true;
    pracujeKolovyRobot = true;
    pracujePrevodovkovyRobot = true;
  }
  void vlozitAuto(Auto ram) {
    a = ram;
    obsazen = true;
  }
  Auto sundatAuto() { // Může vytáhnout auto jen jednou
    if(!obsazen) {
      cerr << "v Drzaku neni zadne auto, které by šlo sundat pomocí sundatAuto()" << endl;
      return Auto(); // "Nulový" objekt Auto
    }
    obsazen = false;
    return a;
  }
  // přístup k autu v držáku:
  Auto* operator->() { return &a; }
	// Umožnit robotům nabídnout své služby držáku:
  void nabidnoutSluzbyMotorBota() {
    Guard<Mutex> g(zamekPrace);
    while(pracujeMotorovyRobot)
      podminkaPrace.wait();
    pracujeMotorovyRobot = true; // Přijmout jeho práci
  }
  void nabidnoutSluzbyKolBota() {
    Guard<Mutex> g(zamekPrace);
    while(pracujeKolovyRobot)
      podminkaPrace.wait();
    pracujeKolovyRobot = true; // Přijmout jeho práci
  }
  void nabidnoutSluzbyPrevodBota() {
    Guard<Mutex> g(zamekPrace);
    while(pracujePrevodovkovyRobot)
      podminkaPrace.wait();
    pracujePrevodovkovyRobot = true; // Přijmout jeho práci
  }
  // Říct čekajícím robotům, že je práce připravena
  void zacitPracovat() {
    Guard<Mutex> g(zamekPrace);
    pracujeMotorovyRobot = false;
    pracujeKolovyRobot = false;
    pracujePrevodovkovyRobot = false;
    podminkaPrace.broadcast();
  }
  // Každý robot nahlásí, žře je hotový
  void ukolDokoncen() {
    Guard<Mutex> g(zamekHotovo);
    podminkaHotovo.signal();
  }
  // Vedouci čeká, až budou splněny všechny úkoly.
  void cekejNaSkonceniPrace() {
    Guard<Mutex> g(zamekHotovo);
    while(!(a.motorNamontovan() && a.prevodovkaNamontovana()
            && a.kolaNamontovana()))
      podminkaHotovo.wait();
  }
};

typedef CountedPtr<Drzak> DrzakPtr;

class Vedouci : public Runnable {
  FrontaAut frontaRamu, frontaHotovych;
  DrzakPtr drzak;
public:
  Vedouci(FrontaAut& fa, FrontaAut& fq, DrzakPtr dr)
  : frontaRamu(fa), frontaHotovych(fq), drzak(dr) {}
  void run() {
    try {
      while(!Thread::interrupted()) {
        // Blokovat, dokud není k dispozici rám:
        drzak->vlozitAuto(frontaRamu->get());
        // Upozornit roboty, že auto je připraveno:
        drzak->zacitPracovat();
        // Čekar, než je práce dokončena:
        drzak->cekejNaSkonceniPrace();
        // Vložit auto do fronty pro další úkoly:
        frontaHotovych->put(drzak->sundatAuto());
      }
    } catch(Interrupted_Exception&) { /* Konec */ }
    cout << "Vedouci ukončen" << endl;
  }
};

class MotorovyRobot : public Runnable {
  DrzakPtr drzak;
public:
  MotorovyRobot(DrzakPtr dr) : drzak(dr) {}
  void run() {
    try {
      while(!Thread::interrupted()) {
        // Blokovat dokud není nabídnuta/přijata práce:
        drzak->nabidnoutSluzbyMotorBota();
        cout << "Instaluji motor" << endl;
        (*drzak)->pridatMotor();
        drzak->ukolDokoncen();
      }
    } catch(Interrupted_Exception&) { /* Konec */ }
    cout << "MotorovyRobot ukončen" << endl;
  }
};

class PrevodovkovyRobot : public Runnable {
  DrzakPtr drzak;
public:
  PrevodovkovyRobot(DrzakPtr dr) : drzak(dr) {}
  void run() {
    try {
      while(!Thread::interrupted()) {
        // Blokovat dokud není nabídnuta/přijata práce:
        drzak->nabidnoutSluzbyPrevodBota();
        cout << "Instaluji převodovku" << endl;
        (*drzak)->pridatPrevodovku();
        drzak->ukolDokoncen();
      }
    } catch(Interrupted_Exception&) { /* Konec */ }
    cout << "PrevodovkovyRobot ukončen" << endl;
  }
};

class KolovyRobot : public Runnable {
  DrzakPtr drzak;
public:
  KolovyRobot(DrzakPtr dr) : drzak(dr) {}
  void run() {
    try {
      while(!Thread::interrupted()) {
        // Blokovat dokud není nabídnuta/přijata práce:
        drzak->nabidnoutSluzbyKolBota();
        cout << "Instaluji Kola" << endl;
        (*drzak)->pridatKola();
        drzak->ukolDokoncen();
      }
    } catch(Interrupted_Exception&) { /* Konec */ }
    cout << "KolovyRobot ukončen" << endl;
  }
};

class Reporter : public Runnable {
  FrontaAut frontaAut;
public:
  Reporter(FrontaAut& fa) : frontaAut(fa) {}
  void run() {
    try {
      while(!Thread::interrupted()) {
        cout << frontaAut->get() << endl;
      }
    } catch(Interrupted_Exception&) { /* Konec */ }
    cout << "Reporter ukončen" << endl;
  }
};

int main() {
  cout << "Ukončit stiskem klávesy <Enter>" << endl;
  try {
    FrontaAut frontaRamu(new TQueue<Auto>),
             frontaHotovych(new TQueue<Auto>);
    DrzakPtr drzak(new Drzak);
    ThreadedExecutor montazniLinka;
    montazniLinka.execute(new MotorovyRobot(drzak));
    montazniLinka.execute(new PrevodovkovyRobot(drzak));
    montazniLinka.execute(new KolovyRobot(drzak));
    montazniLinka.execute(
      new Vedouci(frontaRamu, frontaHotovych, drzak));
    montazniLinka.execute(new Reporter(frontaHotovych));
    // Všechno spustit zahájním výroby rámů:
    montazniLinka.execute(new StavitelRamu(frontaRamu));
    cin.get();
    montazniLinka.interrupt();
  } catch(Synchronization_Exception& e) {
    cerr << e.what() << endl;
  }
} ///:~
