// This may look like C code, but it is really -*- C++ -*-
// Gestion de block de donnees swapable
//                         R. Ansari  Mars 2005
// LAL (Orsay) / IN2P3-CNRS  DAPNIA/SPP (Saclay) / CEA
#ifndef SWSEGDATABLOCK_H
#define SWSEGDATABLOCK_H

#include "machdefs.h"
#include "segdatablock.h"
#include "pexceptions.h"
#include <vector>
#include <typeinfo>

/*!
  \class SOPHYA::DataSwapperInterface
  \ingroup BaseTools
  Interface definition for  data swapper (pure virtual) classes to be used 
  with SOPHYA::SwSegDataBlock classes.
*/
/*!
  \class SOPHYA::SwSegDataBlock
  \ingroup BaseTools
  Segmented data structure with swap space management.
*/

namespace SOPHYA {

////////////////////////////////////////////////////////////////
//// ------------- Class DataSwapperInterface --------------- //
//// ---------------- Class SwSegDataBlock ------------------ //
////////////////////////////////////////////////////////////////

template <class T> 
class DataSwapperInterface {
public:
  virtual ~DataSwapperInterface() { }
  /*! Swap out the data array pointed by \b d with size \b sz
    Return the swap position which might be used later to retrieve the data from swap
    \param d : Pointer to the memory segment
    \param sz : Number of elements (type T) 
    \param idx : An integer which might be used to identify the data (optional)
    \param oswp : Old swap position, if the data has already been swapped
    \param osw : true -> data has already been swapped
  */
  virtual int_8 WriteToSwap(const T * d, size_t sz, int_8 idx, int_8 oswp=0, bool osw=false) = 0;
  /*! Swap in the data array pointed by \b d with size \b sz
    Retrieves the data from swap space and copies it to \b d
    \param idx : optional data identifier
    \param swp : swap position (obtained from a previous call to WriteToSwap()
    \param d : pointer to T , where the data will be copied from swap space
    \param sz : Number of data elements (type T) 
   */
  virtual void  ReadFromSwap(int_8 idx, int_8 swp, T* d, size_t sz) = 0;

  /*! Duplicate the swapper object and return the new object pointer.
      The returned pointer should be deleted when not needed any more.
      This method is used by SwSegDataBlock<T> 
  */
  virtual DataSwapperInterface<T>* Clone() = 0;
};

template <class T>
class SwSegDataBlock : public SegDBInterface<T> {
public:
  //! Constructor - creation from swap position tags (values)
  SwSegDataBlock(DataSwapperInterface<T> & dsw, vector<int_8> const & swpos, size_t segsz)
  {
    mSRef = NULL;
    SetSize(segsz, swpos.size());
    SetSwapper(dsw);
    mSRef->swp = swpos;
    for(size_t k=0; k<mSRef->fgwp.size(); k++)  mSRef->fgwp[k] = true; 
    }
  //! Constructor - optional specification of segment size and number of segments
  SwSegDataBlock(DataSwapperInterface<T> & dsw, size_t segsz=32, size_t nbseg=0)
  {
    mSRef = NULL;
    SetSize(segsz, nbseg);
    SetSwapper(dsw);
  }
  //! copy constructor - shares the data
  SwSegDataBlock(const SwSegDataBlock<T>& a)
  {
    mSRef = a.mSRef;
    mSRef->nref++;
  }

  //! Destructor. The memory is freed when the last object referencing the data segment is destroyed
  virtual ~SwSegDataBlock() { Delete(); }
  //! Adds one segment to the data structure - returns the pointer to the allocated segment.
  virtual size_t Extend() 
  {
    mSRef->swp.push_back(0);
    mSRef->fgwp.push_back(false);
    return mSRef->swp.size();
  }
  /*! \brief Changes the data segment size and reallocates the memory segments
    \warning SetSwapper() must be called after call to SetSize() 
  */
  // segsz : Segment size ;  nbseg : Number of data segments
  virtual void SetSize(size_t segsz, size_t nbseg=0)
  {
    Delete();
    mSRef = new SWSDREF;
    mSRef->nref = 1;
    mSRef->segsize = segsz;
    mSRef->dsid = AnyDataObj::getUniqueId();
    mSRef->buff = new T[segsz];
    mSRef->bidx = -1;        
    mSRef->fgcstbuff = true; 
    for(size_t k=0; k<nbseg; k++) {
      mSRef->swp.push_back(0);
      mSRef->fgwp.push_back(false);
    }
    mSRef->swapper = NULL;
  }

  //! Define the data swapper object. Should only be called if SetSize() is called
  void SetSwapper(DataSwapperInterface<T> & dsw)
  {
    if (mSRef == NULL) return;
    if (mSRef->swapper) delete mSRef->swapper;  
    mSRef->swapper = dsw.Clone();
  }

  //! Return the segment size data structure
  virtual  size_t SegmentSize() const  { return mSRef->segsize; }
  //! Return the number of data segments
  virtual size_t NbSegments() const { return mSRef->swp.size(); } ;
  //! Return the current size of the segmented data structure
  inline size_t Size() const    { return  mSRef->swp.size()*mSRef->segsize; }
  //! Return the pointer to data segment \b k
  virtual T* GetSegment(size_t k) 
  {
    getSeg(k);
    mSRef->fgcstbuff = false;
    return mSRef->buff;
  }
  //! Return the const (read-only) pointer to data segment \b k         
  virtual T const * GetCstSegment(size_t k) const 
  {
    getSeg(k);
    mSRef->fgcstbuff = true;
    return mSRef->buff;
  }

  //! Equal operator. Shares the data with \b a
  inline SwSegDataBlock<T>&  operator = (const SwSegDataBlock<T>& a)
  {
    Delete();
    mSRef = a.mSRef;
    mSRef->nref++;
    return *this;
  }

  //! Empties all memory buffers to swap stream 
  void SwapOutBuffer() const
  {
    if ((mSRef->bidx >= 0) && !mSRef->fgcstbuff) {
      int_8 nswp = mSRef->swapper->WriteToSwap(mSRef->buff, mSRef->segsize, mSRef->bidx, 
					       mSRef->swp[mSRef->bidx], mSRef->fgwp[mSRef->bidx]);
      mSRef->swp[mSRef->bidx] = nswp;
      mSRef->fgwp[mSRef->bidx] = true;
      mSRef->bidx = -1;
      mSRef->fgcstbuff = true;  
   }
  }
  //! Return the position tag (swap position) table, after call to SwapOutBuffer()
  std::vector< int_8 > & GetSwapPosTagTable() const 
  { 
    SwapOutBuffer();
    return  mSRef->swp; 
  } 

protected: 
  SwSegDataBlock() 
  {
    throw ForbiddenError("SwSegDataBlock() default constructor not allowed (swsegdb.h)");
  }
  void Delete()   
  {
    if (mSRef == NULL) return;
    mSRef->nref--;
    if (mSRef->nref > 0)  { mSRef = NULL; return; }
    if (mSRef->swapper) delete mSRef->swapper;  
    delete[] mSRef->buff;
    delete mSRef;
    mSRef = NULL;  
  } 
  void getSeg(size_t k) const
  {
    if (k == mSRef->bidx)  return ;
    if ((mSRef->bidx >= 0) && !mSRef->fgcstbuff) {
      int_8 nswp = mSRef->swapper->WriteToSwap(mSRef->buff, mSRef->segsize, mSRef->bidx, 
					       mSRef->swp[mSRef->bidx], mSRef->fgwp[mSRef->bidx]);
      mSRef->swp[mSRef->bidx] = nswp;
      mSRef->fgwp[mSRef->bidx] = true;
    }
    if (mSRef->fgwp[k]) 
      mSRef->swapper->ReadFromSwap(k, mSRef->swp[k], mSRef->buff, mSRef->segsize);
    else { delete[] mSRef->buff;  mSRef->buff = new T[mSRef->segsize]; }  
    mSRef->bidx = k;
    return;
  }

  //! \cond
  typedef struct {
    size_t nref;      // Number of references to the data structure  
    uint_8 dsid;      // Data structure id
    size_t segsize;   // data segment size
    mutable std::vector< int_8 > swp;  // swap position tag for each segment
    mutable std::vector< bool > fgwp;  // swap flag (true = already swapped)  for each segment
    mutable T * buff;                // Data buffer   
    mutable int_8 bidx;              // segment index (number) corresponding to buffer 
    mutable bool fgcstbuff;            // true : this is a constant T * buff<
    DataSwapperInterface<T> * swapper;   // Data swapper
  } SWSDREF;
  //! \endcond
  SWSDREF *  mSRef;    // SWSDREF structure for reference sharing
};


} // Fin du namespace

#endif
