//: C07:BankTeller.cpp {RunByHand}
// Z knihy "Myslíme v jazyce C++, část 2" od Bruce Eckela a Chucka Allisona.
// (c) 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.
// Použití queue a simulovaného vce vláknového prostředí 
// k modelování systému bankovních pokladníků.
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <iterator>
#include <list>
#include <queue>
using namespace std;

class Zakaznik {
  int dobaObsluhy;
public:
  Zakaznik() : dobaObsluhy(0) {}
  Zakaznik(int tm) : dobaObsluhy(tm) {}
  int zjistiCas() { return dobaObsluhy; }
  void nastavCas(int novyCas) { dobaObsluhy = novyCas; }
  friend ostream&
  operator<<(ostream& os, const Zakaznik& c) {
    return os << '[' << c.dobaObsluhy << ']';
  }
};

class Pokladnik {
  queue<Zakaznik>& zakaznici;
  Zakaznik aktualni;
  enum { PODIL = 5 };
  int zcas; // Čas zbývající z podílu
  bool zamestnan; // Obsluhuje pokladník zákazníka?
public:
  Pokladnik(queue<Zakaznik>& cq)
  : zakaznici(cq), zcas(0), zamestnan(false) {}
  Pokladnik& operator=(const Pokladnik& rv) {
    zakaznici = rv.zakaznici;
    aktualni = rv.aktualni;
    zcas = rv.zcas;
    zamestnan = rv.zamestnan;
    return *this;
  }
  bool jeZamestnan() { return zamestnan; }
  void run(bool rekurze = false) {
    if(!rekurze)
      zcas = PODIL;
    int dobaObsluhovani = aktualni.zjistiCas();
    if(dobaObsluhovani > zcas) {
      dobaObsluhovani -= zcas;
      aktualni.nastavCas(dobaObsluhovani);
      zamestnan = true; // Stále pracuje na aktualnim zákazníkovi
      return;
    }
    if(dobaObsluhovani < zcas) {
      zcas -= dobaObsluhovani;
      if(!zakaznici.empty()) {
        aktualni = zakaznici.front();
        zakaznici.pop(); // Odstranit zakaznika
        zamestnan = true;
        run(true); // Rekurze
      }
      return;
    }
    if(dobaObsluhovani == zcas) {
      //Aktuálník zákazník vyřízen, vyprázdnit:
      aktualni = Zakaznik(0);
      zamestnan = false;
      return; // V tomto podílu už není žádný čas
    }
  }
};

//Dědičností získat přístup k chráněné implementaci:
class FrontaZakazniku : public queue<Zakaznik> {
public:
  friend ostream&
  operator<<(ostream& os, const FrontaZakazniku& cd) {
    copy(cd.c.begin(), cd.c.end(),
      ostream_iterator<Zakaznik>(os, ""));
    return os;
  }
};

int main() {
  FrontaZakazniku zakaznici;
  list<Pokladnik> pokladnici;
  typedef list<Pokladnik>::iterator PoklIt;
  pokladnici.push_back(Pokladnik(zakaznici));
  srand(time(0)); // inicializovat generátor náhodných čísel
  clock_t tiky = clock();
  // Spustit simulaci nejméně na 5 sekund:
  while(clock() < tiky + 5 * CLOCKS_PER_SEC) {
  	//Vložit do fronty náhodný počet zákazníků, 
		//s náhodně dlouhou dobou obsluhy:
    for(int i = 0; i < rand() % 5; i++)
      zakaznici.push(Zakaznik(rand() % 15 + 1));
    cout << '{' << pokladnici.size() << '}'
         << zakaznici << endl;
    // Pokladníci obsluhujkí frontu:
    for(PoklIt i = pokladnici.begin();
      i != pokladnici.end(); i++)
      (*i).run();
    cout << '{' << pokladnici.size() << '}'
         << zakaznici << endl;
    //Jestliže je fronta příliš dlouhá, přidat dalšího pokladníka:
    if(zakaznici.size() / pokladnici.size() > 2)
      pokladnici.push_back(Pokladnik(zakaznici));
    // Jestliže je fronta dostatečně krátká, odebrat pokladníka:
    if(pokladnici.size() > 1 &&
      zakaznici.size() / pokladnici.size() < 2)
      for(PoklIt i = pokladnici.begin();
        i != pokladnici.end(); i++)
        if(!(*i).jeZamestnan()) {
          pokladnici.erase(i);
          break; // Konec cyklu for
        }
  }
} ///:~
