/* Miron Moderau   C1-ISIII                                                  */
/*                                                                           */
/* Prezentacja klasy boost::any                                              */
/*                                                                           */
/* Klasa boost::any stanowi pojemnik na obiekty niemalże dowolnego typu.     */
/* Potrafi przechować każdy obiekt zaopatrzony w konstruktor kopiujący.      */
/* Obiekt any jest świadomy typu przechowywanego obiektu (poprzez type_info, */
/* wymaga to skompilowania programu z włączonym RTTI). Pryechowywany obiekt  */
/* może być odzyskany przez zastosowanie jednego z wariantów funkcji         */
/* any_cast.                                                                 */
/* Z drugiej strony, any_cast nie posiada żadnych możliwości konwersji       */
/* przechowywanych danych, w szczególności nie umożliwia żadnego rodzaju     */
/* rzutowania, nawet typów podstawowych.                                     */
/*                                                                           */
/*                                                                           */
/* Główną przewagę boost::any nad union stanowi to, iż pojedynczy obiekt any */
/* może zawrzeć w sobie praktycznie każdy typ, dodatkowo zaś any wyposażony  */
/* jest w mechanizm kontroli poprawności w czasie wykoannia.                 */
/*                                                                           */
/* UWAGA: Przypominam o konieczności kompilacji z włąćzonym RTTI.            */
/*        Dla cl.exe jest to flaga /GR, dla GCC RTTI jest domyślnie włączone.*/
/*                                                                           */

#include <boost/any.hpp>

#include <string>
#include <map>

#include <iostream>

using boost::any;
using boost::any_cast;
using boost::bad_any_cast;

using std::exception;
using std::type_info;

using std::string;
using std::map;

using std::cout;
using std::endl;

class Registry
{
private:
  typedef map<string, any> InternalRegistryType;
  bool typesLocked_;
  InternalRegistryType reg_;

public:
  class InvalidName: public exception
  {
  public:
    virtual const char* what() const throw() { return "Invalid registry entry name!"; }
  };
  class InvalidType: public exception
  {
  public:
    virtual const char* what() const throw() { return "Invalid registry entry type!"; }
  };

  Registry() : reg_()
             , typesLocked_(true) {}

  bool has (const string& name) { return reg_.find(name) != reg_.end(); }

/*Korzystam z tego, że boost::any posiada szablonowy konstruktor implicit.   */
/*Dlatego można użyć tej metody z każdym obiektem umieszczalnym w boost::any.*/
/*Jeśli typesLocked jest ustawione na true, to metoda ta pozwoli jedynie na  */
/* zamiane wartości z zachowaniem typu lub na zainicjowanie pustego wpisu.   */
  void set (const string& name, const any& value)
    {
      InternalRegistryType::iterator it = reg_.find(name);
      if (it != reg_.end())
      {
        if(!typesLocked_
           || it->second.type() == typeid(void)   //Puste any, nieokreślony typ
           || it->second.type() == value.type()) {//Zgodny typ
          it->second = value;
        }
        else throw InvalidType();
      }
      else reg_[name] = value;
    }

  const any& get (const string& name) const
    {
      InternalRegistryType::const_iterator it = reg_.find(name);
      if (it != reg_.end())
        return it->second;
      else throw InvalidName();
    }

  template<typename V> V get (const string& name) const
    {
      try
      {
//Jeśli typ przechowywany w obiekcie any nie jest identyczny z V,
//to any_cast<V> rzuci wyjątkiem boost::bad_any_cast.
        return any_cast<V>(get(name));
      }
      catch(const bad_any_cast&)
      {
        throw InvalidType();
      }
    }

  void lockTypes (bool lock = true)  { typesLocked_ = lock; }

};

/*---------------------------------------------------------------------------*/

int main(int args, char** argv)
{
  Registry reg;
  const string editor_entry = "editor";
  const string empty_entry = "empty";

  reg.set(editor_entry, string("emacs")); //Domyślna konwersja na any

  cout << reg.get(editor_entry).type().name() << endl; //Nazwa typu przechowywanego obiektu
  cout << reg.get<string>(editor_entry) << endl;

  reg.set(editor_entry, string("vi"));

  try {
    reg.set(editor_entry, 0);
  }
  catch (const Registry::InvalidType& e)
  {
    cout << e.what() << endl;
  }

  reg.lockTypes(false);

  reg.set(editor_entry, 0);

  reg.lockTypes();

  reg.set("x", 10);
  reg.set("y", 7);
  cout << "x + y = " << (reg.get<int>("x") + reg.get<int>("y")) << endl;

  try {
    reg.get<long>("x"); //Błąd: long nie jet tym samym typem co int
                        //W normalnych warunkach, rzutowanie nie powodowałoby utraty danych
                        // i byłoby dozwolone.
  }
  catch (const Registry::InvalidType& e)
  {
    cout << e.what() << endl;
  }

  reg.set(empty_entry, any());
  reg.set(empty_entry, 0);

  return 0;
}
