#ifndef EZSAVE_H
#define EZSAVE_H

#if defined(__DECCXX)
#include <assert.h>
#include <string.h>
#else
#include <cstring>   // for strcpy
#include <cassert>
#endif
#include <map>       // for STL maps

#if !defined(_RWSTD_NO_NAMESPACE)
using namespace std;

// all EZSave classes are in namespace EZSave
namespace EZSave
{
#endif

// define Object ID
typedef void * OID;

/**********
Three types are required to instantiate an STL map class.  The first type is the key, the third
type must define:
bool operator () (first_key_type, first_key_type) const;
The following class CompareOIDs is used to instantiate STL map classes to define
OID2NameTableType and OID2OIDTableType.
**********/
class CompareOIDs
{
public:
  bool operator () (OID o1, OID o2) const { return o1 < o2; }
};

// create typedefs for OID2OIDTable, OIDToNameTable
typedef map<OID, const char *, CompareOIDs> OID2NameTableType;  // key = OID
typedef map<OID, OID, CompareOIDs> OID2OIDTableType;  // key = OID

// each EZUniverse object manages a universe of persistent objects
class EZUniverse
{
public:
  // constructor takes file name containing saved universe
  EZUniverse(const char *pFileName);  // file may not exist

  // destructor saves universe to the file named in constructor
  ~EZUniverse();

  // get root object, will be 0 if saved universe file doesn't exist
  OID GetRoot() const { return m_Root; }

  // member functions to set and get the current universe
  static void SetCurrentUniverse(EZUniverse *p) { assert(p != 0); m_pCurrentEZUniverse = p; }
  static EZUniverse * GetCurrentUniverse() { return m_pCurrentEZUniverse; }

  // boolean indicates whether universe is completely restored
  bool InProcessOfRestoringUniverse() const { return m_bInProcessOfRestoringUniverse; }

  /**********
  The following four member functions should be private since they are
  only needed by class EZPtr<T>.  Unfortunately, the following friendship
  declaration produces errors in Visual C++ 6.0 if they are declared
  private.

  template <class T> friend class EZPtr;

  We, therefore, make them public.
  **********/
  // member function to add to OID2NameTable
  void AddToOID2NameTable(OID, const char *pClassName);

  // member function to remove from OID2NameTable
  void DeleteFromOID2NameTable(OID);

  // member function to add to OID2OIDTable
  void AddToOID2OIDTable(OID oldOID, OID newOID);

  // member function to read OID2OIDTable
  OID ReadOID2OIDTable(OID oldOID);
private:
  // pointer to currently active EZUniverse, initialized to 'this' by ctor
  static EZUniverse * m_pCurrentEZUniverse;

  // root object for persistent universe
  OID m_Root;

  // flag to indicate whether a universe is being restored
  bool m_bInProcessOfRestoringUniverse;

  // name of file containing saved universe
  char * m_pFileName;

  // define OID2NameTable, OID2OIDTable pointers
  OID2NameTableType *m_pOID2NameTable;
  OID2OIDTableType *m_pOID2OIDTable;
};  // end of class EZUniverse definition

// EZPtr's have semantics of ordinary pointers
template <class T>
class EZPtr
{
private:
  T * m_p;
public:
  // constructors - note there is no destructor
  EZPtr();  // code too complex to make inline
  EZPtr(T *p) { m_p = p; }

  // operators that make EZPtrs behave like pointers
  T * operator -> () const { return m_p; }
  T & operator * () const { return *m_p; }
  T ** operator & () { return &m_p; }  // can take &(EZPtr object)

  // conversion operator
  operator T * () const { return m_p; }

  // assignment operator
  EZPtr & operator = (T *p) { m_p = p; return *this; }
};

// default constructor for EZPtr<T> class
template <class T>
EZPtr<T>::EZPtr()
{
  // get current EZUniverse
  EZUniverse *pUniverse = EZUniverse::GetCurrentUniverse();
  assert(pUniverse != 0);

  if(pUniverse->InProcessOfRestoringUniverse())
  {
    // if old address is 0, leave it that way
    if(m_p != 0)
      m_p = _RWSTD_STATIC_CAST(T *,pUniverse->ReadOID2OIDTable(m_p));
  }
  else
    m_p = 0;
}

template <class T>
void EZDelete(EZPtr<T> &ptr)
{
  // get current EZUniverse
  EZUniverse *pUniverse = EZUniverse::GetCurrentUniverse();
  assert(pUniverse != 0);

  T *p = ptr;  // conversion operator used

  if(p != 0)
  {
    delete p;  // may call other destructors causing multiple deletes
               // from OID2NameTable
    pUniverse->DeleteFromOID2NameTable(p);

    // the following is necessary for the restore process
    ptr = 0;
  }
}

////////// EZTools Section //////////

/**********
Each Persistent Class, e.g., class MyClass, must have the following line in one and only
one .cpp file.
static EZTools<MyClass> i;
**********/

#if defined(__DECCXX)
#define EZTOOLS(c,i) static EZTools<c> i(#c)
#else
#define EZTOOLS(c,i) static EZTools<c> i
#endif

// define compare class, used to instantiate an STL map class
class CompareClassNames
{
public:
  bool operator () (const char *pClassName1, const char *pClassName2) const
   { return strcmp(pClassName1, pClassName2) < 0; }
};

class EZToolsBase;  // forward declaration, incomplete type at this point

// define EZToolsTableType
typedef map<const char *, const EZToolsBase *, CompareClassNames>  EZToolsTableType;

// the three public static member functions are the reason for defining the EZTools classes
class EZToolsBase
{
private:
  static EZToolsTableType *m_pEZToolsTable;
  virtual size_t vObjectSize() const = 0;
  virtual void vFixPointers(OID) const = 0;
  virtual void vDestroyObject(OID) const = 0;
public:
  EZToolsBase(const char * pClassName);  // implemented in .cpp file
  ~EZToolsBase();  // note destructor is not virtual
  static size_t ObjectSize(const char *pClassName);
  static void FixPointers(const char *pClassName, OID oid);
  // DestroyObject assumes object was created with EZNew
  static void DestroyObject(const char *pClassName, OID oid);
};

template <class T>
class EZTools : public EZToolsBase
{
private:
  size_t vObjectSize() const { return sizeof(T); }
  void vFixPointers(OID oid) const { new(oid) T; }
  void vDestroyObject(OID oid) const
  {
    EZPtr<T> ptr(_RWSTD_STATIC_CAST(T *,oid));
#if defined(__DECCXX)
    EZDelete(ptr) ;
#else
    EZDelete<T>(ptr);
#endif
  }
public:
#if defined(__DECCXX)
  EZTools(const char* theTypeName) : EZToolsBase(theTypeName) {} ;
#else
  EZTools() : EZToolsBase(typeid(T).name()) {}
#endif
};

#if !defined(_RWSTD_NO_NAMESPACE)
}  // end of namespace EZSave
#endif

// ANSI C++ requires all allocators to be defined in the global namespace
// EZSave placement form of operator new - use to initialize an EZPtr
void * operator new(size_t amount, const char *pClassName);

// define macro to use above allocator
#define EZNew(type) new(#type)type

#endif
