//=============================================================================

#include <cmath>
#include <iomanip>
#include <iostream>
#include <random>
#include <vector>
using namespace std;

//-----------------------------------------------------------------------------

class person
{
public:
   person() : state_(state::unknowing) {}

   void set_telling() { state_ = state::telling; }

   bool is_unknowing() const { return state_ == state::unknowing; }
   bool is_telling() const { return state_ == state::telling; }
   bool is_closemouthed() const { return state_ == state::closemouthed; }

   static bool meet(person&, person&);

   void print() const;

private:
   enum class state { unknowing, telling, closemouthed };
   state state_;
};

//-----------------------------------------------------------------------------

class village
{
public:
   village(int);

   bool next();
   bool stable_state() const;

   void print() const;

private:
   static random_device rd;
   mt19937 engine;
   uniform_int_distribution<> dist;

   const int width_;
   vector<person> persons_;
};

random_device village::rd;

//-----------------------------------------------------------------------------

bool person::meet(person& p1, person& p2)
{
   if (p1.is_unknowing() && p2.is_unknowing())
   {
      //p1.state_ = state::unknowing;
      //p2.state_ = state::unknowing;
      return false;
   }
   if (p1.is_unknowing() && p2.is_telling())
   {
      p1.state_ = state::telling;
      //p2.state_ = state::telling;
      return true;
   }
   if (p1.is_unknowing() && p2.is_closemouthed())
   {
      //p1.state_ = state::unknowing;
      //p2.state_ = state::closemouthed;
      return false;
   }
   if (p1.is_telling() && p2.is_unknowing())
   {
      //p1.state_ = state::telling;
      p2.state_ = state::telling;
      return true;
   }
   if (p1.is_telling() && p2.is_telling())
   {
      p1.state_ = state::closemouthed;
      p2.state_ = state::closemouthed;
      return true;
   }
   if (p1.is_telling() && p2.is_closemouthed())
   {
      p1.state_ = state::closemouthed;
      //p2.state_ = state::closemouthed;
      return true;
   }
   if (p1.is_closemouthed() && p2.is_unknowing())
   {
      //p1.state_ = state::closemouthed;
      //p2.state_ = state::unknowing;
      return false;
   }
   if (p1.is_closemouthed() && p2.is_telling())
   {
      //p1.state_ = state::closemouthed;
      p2.state_ = state::closemouthed;
      return true;
   }
   //p1.state_ = state::closemouthed;
   //p2.state_ = state::closemouthed;
   return false;
}

//-----------------------------------------------------------------------------

void person::print() const
{
   switch (state_)
   {
   case state::unknowing:
      cout << '_';
      break;
   case state::telling:
      cout << '|';
      break;
   case state::closemouthed:
      cout << '*';
      break;
   }
}

//-----------------------------------------------------------------------------

village::village(int count)
 : engine(rd()), dist(0, count-1),
   width_(static_cast<int>(log10(static_cast<double>(count))) + 1),
   persons_(count)
{
   persons_[0].set_telling();
}

//-----------------------------------------------------------------------------

bool village::next()
{
   int rnd1 = dist(engine);
   int rnd2 = dist(engine);
   while (rnd1 == rnd2)
   {
      rnd2 = dist(engine);
   }
   return person::meet(persons_[rnd1], persons_[rnd2]);
}

//-----------------------------------------------------------------------------

bool village::stable_state() const
{
   for (const person& p : persons_)
   {
      if (p.is_telling())
      {
         return false;
      }
   }
   return true;
}

//-----------------------------------------------------------------------------

void village::print() const
{
   int unknowing_count = 0, telling_count = 0, closemouthed_count = 0;
   for (const person& p : persons_)
   {
      if (p.is_unknowing()) ++unknowing_count;
      if (p.is_telling()) ++telling_count;
      if (p.is_closemouthed()) ++closemouthed_count;
   }

   cout << "U(" << setw(width_) << unknowing_count
      << ") E(" << setw(width_) << telling_count
      << ") S(" << setw(width_) << closemouthed_count
      << ") ";

   for (const person& p : persons_)
   {
      p.print();
   }
}

//-----------------------------------------------------------------------------

int read_int(const string& msg, int lower_limit)
{
   int res;
   for (;;)
   {
      cout << "- " << msg << ": ";
      cin >> res;
      if (!cin.fail() && res >= lower_limit) break;
      cin.clear();
      cin.ignore(numeric_limits<streamsize>::max(), '\n');
   }
   return res;
}

//-----------------------------------------------------------------------------

void print_line(const int width, const int n, const village& v)
{
   cout << setw(width) << n << ": ";
   v.print();
   cout << '\n';
}

//-----------------------------------------------------------------------------

int main()
{
   cout << "Aufg_14_05_Geruechtekueche_1\n\n";

   cout << "Geruechtekueche\n";

   int count = read_int("Anzahl Personen", 2);
   int loops = read_int("Max Durchlaeufe", 1);

   cout << '\n';


   village v(count);
   const int loops_output_width = static_cast<int>(log10(static_cast<double>(loops))) + 1;
   print_line(loops_output_width, 0, v);

   for (int i = 1; i <= loops; ++i)
   {
      if (v.next())
      {
         print_line(loops_output_width, i, v);
         if (v.stable_state()) break;
      }
   }

   cout << "\nEnde wegen "
      << (v.stable_state() ? "stabilen Zustands" : "Erreichen der max. Durchlaeufe")
      << '\n';
}
