﻿#ifndef SERIAL_HPP
#define SERIAL_HPP
/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX*
 X                                                                              X
 X                                  XXXXXXXX                                    X
 X                               XXXXXXXXXXXXXX                                 X
 X                              XXXXXXXXXXXXXXXX                                X
 X                              XXXXXXXXXXXXXXXX                                X
 X                              XX  XXXXXXXX  XX                                X
 X                               X    XXXX    X                                 X
 X                              XXXXXXX  XXXXXXX                                X
 X                              XXXXXX    XXXXXX                                X
 X                                XXXXXXXXXXXX                                  X
 X                                  XX XX XX                                    X
 X                                  XX XX XX                                    X
 X                                                                              X
 X                                CONFIDENTIAL                                  X
 X                   Copyright Pirate Neural Transmissions 2006                 X
 X                            All rights reserved.                              X
 X                                                                              X
 *XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX*
 X                                                                              X
 X  NAME:      Serial.hpp                                                       X
 X  DATE:      5, 2013                                                          X
 X  AUTHOR:    Mark Riphenburg                                                  X
 X  PURPOSE:                                                                    X
      CSerial is a class that simplifies serialization of complex interlinked
      networks of classes and data structures.  objects may be direct, heap or
      stack allocated and indirect -either owned or shared.
 X  USAGE:                                                                      X
 X  NOTES:                                                                      X
      pointers in serialized objects must be null if not valid
 X  TODO:                                                                       X
 X                                                                              X
 *XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX*/
#include <deque>
#include <set>

//#include "BaseType.h"
typedef unsigned char         U8;     /*  8-bit unsigned */
typedef signed char           S8;     /*  8-bit signed   */
typedef unsigned short        U16;    /* 16-bit unsigned */
typedef signed short          S16;    /* 16-bit signed   */
typedef unsigned long         U32;    /* 32-bit unsigned */
typedef signed long           S32;    /* 32-bit signed   */
typedef unsigned long long    U64;    /* 64-bit unsigned ( unsigned __int64 ) */
typedef long long             S64;    /* 64-bit signed ( __int64 ) */
typedef float                 F32;    /* 32-bit floating point */
typedef double                F64;    /* 64-bit floating point */


//#define mSERIAL_CLEAN( ... ) __VA_ARGS__
#define mSERIAL_CLEAN( ... )

using namespace std;

class CMemberDetectorNull { public: enum{ kNull = 0 }; };
template<bool _Test, class _Type = void>
struct MemberDetectorWrap_if { typedef _Type  type; }; // type is passed through unwrapped if false
template<class _Type>
struct MemberDetectorWrap_if<true, _Type>	{	typedef CMemberDetectorNull type; }; // type is substituted for a placeholder calss type if true

#define mCreateMemberDetector(X)                                                    \
template<typename T> class has_##X {                                                \
    typedef typename MemberDetectorWrap_if< is_scalar<T>::value, T >::type wtype;   \
    struct Fallback { int X; };                                                     \
    struct Derived : wtype, Fallback { };                                           \
    template<typename U, U> struct Check;                                           \
    typedef char ArrayOfOne[1];                                                     \
    typedef char ArrayOfTwo[2];                                                     \
    template<typename U> static ArrayOfOne & func(Check<int Fallback::*, &U::X> *); \
    template<typename U> static ArrayOfTwo & func(...);                             \
  public:                                                                           \
    typedef has_##X type;                                                           \
    enum { value = sizeof(func<Derived>(0)) == 2 };                                 \
};

mCreateMemberDetector(Serialize)
#define mSERIAL S32 SerializeV; CSerial& Serialize( CSerial& Serial )
template<typename Type>
class CSerial_ : public std::deque<Type>{ public:
  struct MemItem
  {
    U32   Size;
    U8 *  hAdr;
    S32   Idx;

    MemItem& Set(  U32 iSize, void * ihAdr, S32 iIdx = 0 ) { Size = iSize; hAdr = (U8*)ihAdr; Idx = iIdx; return *this; };
    static class CmpIdx { public: bool operator()(const MemItem &A, const MemItem &B) {  return A.Idx < B.Idx; }; } const; 
    static class CmpAdr { public: bool operator()(const MemItem &A, const MemItem &B) {  return A.hAdr < B.hAdr; }; } const;
  } M, * hM;

  S32       fIn, EnumIdx;
  typedef set<MemItem,typename MemItem::CmpIdx> TMapIn; TMapIn MapIn;
  typedef set<MemItem,typename MemItem::CmpAdr> TMapOut; TMapOut MapOut;

  S32       Size( S32 iSz = kMaxS32 ) { if(iSz==kMaxS32) return size()*sizeof(Type); if(iSz==-1) return capacity()*sizeof(Type);
                                        if(!iSz){clear(); return 0;} resize((iSz+(sizeof(Type)-1))/sizeof(Type)); return iSz; };
  template< typename T >
  CSerial_&  Push( T& Val ) { Type * hCur = (Type*)&Val, * hEnd = &hCur[sizeof(T)/sizeof(Type)];
                              while( hCur < hEnd ) push_back(*hCur++); return *this; };
  template< typename T >
  CSerial_&  Pop ( T& Val ) { Type * hCur = (Type*)&Val, * hEnd = &hCur[sizeof(T)/sizeof(Type)];
                              while( hCur < hEnd ) {*hCur++ = front(); pop_front();} return *this; };
  /* all literal items that are gauranteed to be embedded in a prior serialized object */
  template< typename T >
  CSerial_&  IO( T& Val ) { return _IO(Val); } 
  /* for explicit memory item creation all literal items get a MemItem, but most will be fEmbedded and be immediately erased */
  template< typename T >
  CSerial_&  IOS( T& Val ) { M.Set(sizeof(T),&Val); MemIO(); return _IO(Val); } 
  /* auto detect helper for simple members that have no pointers or constructors that reference external data */
  template< typename T >
  CSerial_&  _IO( T& Val, typename enable_if<!has_Serialize<T>::value, T >::type * S = 0 ) { return fIn ? Pop(Val) : Push(Val); };
  /* auto detect helper for members that have a Serialize function to handle pointers or constructors that reference external data */
  template< typename T >
  CSerial_&  _IO( T& Val, typename enable_if<has_Serialize<T>::value, T >::type * S = 0 ) { return Val.Serialize(*this); };

  /* for pointers to single objects */
  template< typename T>
  CSerial_&  IO( T*& hVal ) { M.Set(sizeof(T),hVal); if( !(hVal = (T*)MemIO()) && M.Size ) hM->hAdr = (U8*)(hVal = new T); if( M.Size ) IO(*hVal); return *this; };
  /* for members that are allocated arrays */
  template< typename T> 
  CSerial_&  IO( T*& hArray, S32 Cnt ) { S32 i=0; IO(Cnt); M.Set(sizeof(T)*Cnt,hArray); if( !(hArray = (T*)MemIO()) && M.Size && Cnt ) hM->hAdr = (U8*)(hArray = new T[Cnt]);
                                         if( M.Size ) while(i < Cnt) IO(hArray[i++]); return *this; };                                        
  /* for members that are constant size arrays (unallocated) */
  template< typename T, int NSz >
  CSerial_&  IO( T(&hArray)[NSz], S32 Cnt=kMaxS32 ) { S32 i=0; IO(Cnt=Cnt<NSz?Cnt:NSz); M.Set(sizeof(T)*NSz,hArray); MemIO(); while(i < Cnt) IO(hArray[i++]); return *this; };
  /* call this to begin serialization and reset everything (including the IO queue if fOut) */
  template< typename T>
  CSerial_&  Serialize( T& What, S32 fIO = 1 ) { M.Set(0,0); MapIn.clear(); MapOut.clear();  EnumIdx=0;
                        if( (fIn = fIO) ) MapIn.insert(M); else {clear(); MapOut.insert(M); } return IOS(What); };

  // on entry, Size and hAdr must be set
  // on exit, Size is set to 0 == do no de|serializ, !0 == do de|serialize 
  // exiting return must point to correct address for callers pointers can be updated (happens during fIn mode)
  // additionally, if return == 0 and size != 0 the caller must allocate a new object and both hM->hAdr and its own pointer must be set to the new allocation
  void *   MemIO( ) { S32 Offset = M.Idx = 0;
                    if(fIn)
                    {
                      Pop(M.Idx); if( M.Idx < 0 ) { Pop(Offset); M.Idx *= -1; } //M.Idx is - it has an offset and is embedded in another item that has already been deserialized
                      if( ! M.Idx ) { mSERIAL_CLEAN( if(M.hAdr) delete M.hAdr; ) M.Size = 0; return M.hAdr = 0;  }  //if null ID, delete old allocation and set to 0;
                      pair<TMapIn::iterator,bool> P = MapIn.insert(M); //insert or find the existing object (by ID)
                      hM = const_cast<MemItem *>(&(*P.first)); //the caller will need to record address because only it has the typ info to allocate new
                      if( ! (M.Size = P.second) ) // item already exists and has been allocated and deserialized at least once
                      {
                        mSERIAL_CLEAN( if( M.hAdr && !Offset && M.hAdr != hM->hAdr ) delete M.hAdr; ) //is there a pointer with an old/inconsistent linkage?
                        M.hAdr = &hM->hAdr[Offset];
                      }
                      return &M.hAdr[Offset];
                    }
                    else
                    {
                      if( !M.hAdr) M.Size = 0;
                      if( M.hAdr ) {
                        pair<TMapOut::iterator,bool> P = MapOut.insert(M); //insert or find the existing object (by address)
                        M.Idx = (hM = const_cast<MemItem *>(&(*P.first)))->Idx; //the const restriction is a defect of set
                        M.Size = 0; //only a successful insertion state should trigger serialization
                        // E, NWO: item already exists and has been saved once 
                        if( P.second )
                          if( &(--P.first)->hAdr[P.first->Size] > M.hAdr ) // item was added but it is embedded in another object
                          {
                            Offset = M.hAdr - P.first->hAdr; //-EO, NWO
                            M.Idx = -P.first->Idx;  //-Idx signals an offset
                            //MapOut.size();
                            //++P.first;
                            MapOut.erase(++P.first); //embedded items do not get an entry
                          }
                          else  //item was added and it is unique and not embedded
                            M.Size = hM->Idx = M.Idx = ++EnumIdx; //E, WO -the just inserted item is modified to give it an index
                      }
                      Push( M.Idx ); if(Offset) Push(Offset);
                      return M.hAdr; //hM is undefined during Out operation, M will carry all needed states back to caller
                    }
                }

  };
typedef CSerial_<U8>  CSerial;
//typedef CSerial_<U16> CSerial;
//typedef CSerial_<U32> CSerial;  //for object sizes gauranteed to be multiples of U32 granularity - i.e. size % 4 == 0
//typedef CSerial_<U64> CSerial;



#endif