#include <iostream>
#include <vector>
#include <stdexcept>
#include <utility>

class EndOfTheWorldAsWeKnowIt { };

class Disk
{
  unsigned diameter;
  void brahma_check() const
  {
    if (diameter == 0)
      throw std::logic_error("Brahma ist zornig! "
                             "Du hast die heiligen Regeln verletzt, "
                             "als du eine nicht existierende Scheibe nahmst."
                             );
  }
public:
  Disk(unsigned diameter = 0) : diameter(diameter) { }
  Disk(Disk &&other): diameter(other.diameter) 
  {
    other.brahma_check(); 
    other.diameter = 0;
  }
  Disk(const Disk&) = delete;
  void operator=(const Disk&) = delete;
  unsigned get_diameter() const { brahma_check(); return diameter; }

  friend std::ostream& operator<<(std::ostream &out, Disk const& disk)
  {
    return out << "|\t" << disk.diameter << "\t|";
  }
};

class Tower
{
  std::vector<Disk> disks;
public:
  void add_disk(Disk &&disk) 
  { 
    if (!disks.empty() && disks.back().get_diameter() < disk.get_diameter() )
      throw std::logic_error("Brahma ist zornig! "
                             "Du hast die heiligen Regeln verletzt, "
                             "als du eine große Scheibe auf eine kleine legtest."
                             );


    disks.push_back(std::move(disk)); 
  }
  Disk remove_top() 
  {
    if (disks.empty()) 
      throw std::logic_error("Brahma ist zornig! "
                             "Du hast die heiligen Regeln verletzt, "
                             "als du eine Scheibe von einem leeren Turm nahmst."
                             );

    Disk disk = std::move(disks.back());
    disks.pop_back();
    return std::move(disk);
  }
  bool empty() const { return disks.empty(); }
  Tower() = default;
  Tower(const Tower&) = delete;
  void operator=(const Tower&) = delete;

  friend std::ostream& operator<<(std::ostream &out, Tower const& tower)
  {
    for(auto const& disk: tower.disks)
      out << disk;
    return out;
  }
};

template <unsigned num_disks> class Temple
{
  Tower towers[3];
public:
  Temple()
  {
    for(unsigned i = num_disks; i != 0; --i)
      towers[0].add_disk(Disk(i));
  }
  void move(unsigned from, unsigned to)
  {
    towers[to].add_disk(towers[from].remove_top());
  }
  void show_to_brahma() const
  {
    if (towers[0].empty() && (towers[1].empty() || towers[2].empty()))
      throw EndOfTheWorldAsWeKnowIt();
    else
      throw std::logic_error("Brahma ist zornig! "
                             "Du hast die heiligen Regeln verletzt, "
                             "als du ihm eine unvollstandige Lösung zeigtest."
                             );
  }

  friend std::ostream& operator<<(std::ostream &out, Temple<num_disks> const& temple)
  {
    return out << "----------------------------------------------------------\n"
               << temple.towers[0] << '\n' 
               << temple.towers[1] << '\n'
               << temple.towers[2] << '\n'
               << "----------------------------------------------------------\n";
  }
};

class Monk
{
  unsigned rank;
  static unsigned other_tower(unsigned t1, unsigned t2)
  {
    if (t1 == 0 && t2 == 1) return 2;
    if (t1 == 1 && t2 == 0) return 2;
    if (t1 == 2 && t2 == 1) return 0;
    if (t1 == 1 && t2 == 2) return 0;
    if (t1 == 2 && t2 == 0) return 1;
    if (t1 == 0 && t2 == 2) return 1;
    throw std::logic_error("Brahma ist zornig! "
                           "Du hast die heiligen Regeln verletzt, "
                           "als du totalen Unsinn programmiert hast."
                           );
  }
public:
  Monk(unsigned rank): rank(rank) { }
  template <typename Temple> void solve_puzzle(Temple &temple, unsigned from, unsigned to)
  {
    if (rank > 1)
      {
        // Ask subordinate to move all the top disks:
        Monk minor_monk(rank - 1);
        unsigned other = other_tower(from, to);
        minor_monk.solve_puzzle(temple, from, other);
        // Move the last piece
        temple.move(from, to);
        // Proudly draw the result:
        std::cout << temple;
        // Ask subordinate to move all the rest:
        minor_monk.solve_puzzle(temple, other, to);        
      }
    else
      {
        // This task is simple, even for the lowliest monk:
        temple.move(from, to);
        // Proudly draw the result:
        std::cout << temple;
      }
  }
};

int main()
{
  const unsigned num_disks = 5;
  Temple<num_disks> temple;
  try
    {
      std::cout << temple;
      Monk abbot(num_disks);
      abbot.solve_puzzle(temple, 0, 1);
      temple.show_to_brahma();
    }
  catch (EndOfTheWorldAsWeKnowIt)
    {
      std::cout << "Brahma ist zufrieden. Das Universum hat seinen Zweck "
        "erfüllt und kann nun neu erschaffen werden. Einen schönen Tag noch!\n";
    }
}
