/////////////////////////////////////////////////////////////////////////
//                                                                     //
// This file contains the declaration and the implementation of the    //
// CPropertyObject class.                                              //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
#ifndef __CPROPERTYOBJECT_HPP__
#define __CPROPERTYOBJECT_HPP__


#include <string>
#include <vector>
#include <map>
#include "popassert.hpp"
#include "real.hpp"
#include "CColor.hpp"
#include "CVector.hpp"


/////////////////////////////////////////////////////////////////////////
//                                                                     //
// This class represents an object that has properties, each           //
// identified by a string.                                             //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
class CPropertyObject
{
public:
  CPropertyObject(const std::string& strName)
    : m_strName(strName)
  {
  }
  virtual ~CPropertyObject()
  {
  }

  // Instance of this class have a name to identify the object
  inline const std::string& getName(void) const;

  // This function assigns a value to a specific property
  template <class T>
  bool Assign(const std::string& strProperty,
              const T& Value,
              bool bTopLevel = true);


protected:
  // This function registers a property so that it can be assigned
  template <class T>
  void Register(const std::string& strProperty, T& pVariable);

  // This function add a subobject to this property object so that it
  // can be assigned property by the owner object
  void AddSubObject(CPropertyObject *pSubObject);


private:
  // Name of the object
  const std::string m_strName;

  // Subobjects
  std::vector<CPropertyObject*> m_vSubObjects;

  // This macro declares a map that maps strings (property name) to
  // values of the proper type.  It also declares a function to get
  // the proper map according to the type so that the assign and
  // register functions can be written as generic templates
#define DECLARE_MAP(type)                                 \
  std::map<std::string, type*> m_##type##Properties;      \
  inline void getMap(std::map<std::string, type*> *&pMap) \
  {                                                       \
    pMap = &m_##type##Properties;                         \
  }

  DECLARE_MAP(CColor);
  DECLARE_MAP(real_t);
  DECLARE_MAP(int);
  DECLARE_MAP(CVector);
  typedef std::string stdstring;
  DECLARE_MAP(stdstring);
};




// This function returns the name of the object
inline const std::string& CPropertyObject::getName() const
{
  return m_strName;
}


// This function add a subobject to this object
inline void CPropertyObject::AddSubObject(CPropertyObject *pSubObject)
{
  m_vSubObjects.push_back(pSubObject);
}


// This function registers a property so that it can be assigned
template <class T>
void CPropertyObject::Register(const std::string& strProperty, T& pVariable)
{
  std::map<std::string, T*> *pMap = NULL;
  getMap(pMap);
  POP_ASSERT(pMap->find(strProperty) == pMap->end(),
             "Try to register an already registered property.");

  (*pMap)[strProperty] = &pVariable;
}


// This function assigns a specific value to a given property
template <class T>
bool CPropertyObject::Assign(const std::string& strProperty,
                             const T& Value,
                             bool bTopLevel)
{
  bool returnValue = false;

  // Get the proper map for this type of property.  This is done just
  // to be able to use templates for different maps and different
  // property types
  std::map<std::string, T*> *pMap = NULL;
  getMap(pMap);

  // Try to find the property in the map
  typename std::map<std::string, T*>::iterator it = pMap->find(strProperty);
  if (it != pMap->end())
    {
      *(it->second) = Value;
      returnValue   = true;
    }

  // Assign it to subobjects
  for (std::vector<CPropertyObject*>::iterator it = m_vSubObjects.begin();
       it != m_vSubObjects.end();
       ++it)
    {
      returnValue = returnValue || (*it)->Assign(strProperty, Value, false);
    }

  // If it could not be assigned to any object
  if (!returnValue && bTopLevel)
    {
      std::string message = "Property \"";
      message += strProperty;
      message += "\" is not in object \"";
      message += getName();
      message += '"';
      throw message;
    }

  return returnValue;
}




// Macro to make it shorter to register properties
#define REGISTER_PROPERTY(prop) Register( #prop , m_Prop##prop )


#endif // __CPROPERTYOBJECT_HPP__
