#include "swppfdtable.h"
#include "pexceptions.h"
#include "thsafeop.h"

namespace SOPHYA { 

/*!
   \class SwPPFDataTable
   \ingroup HiStats
   This class can be used to organize data in table (row-column) form.
   Each column holds homogeneous data (same data type), while different
   columns can be used for different data types 
   (integer, float, string ...).
   A PPF stream is used as swap space. Due to limitations in the current
   implementation of PPF streams, read operations (acces to table data) cannot
   be performed when a table is being filled. 

   \warning 
   - When creating a table, the output PPF stream (POutPersist) must not be closed
   (destroyed) before the SwPPFDataTable object is written to the stream.
   - It is not possible to make a complete (deep) copy of a SwPPFDataTable
   Copy constructor and equal operator shares the data.
   - The destructor  DOES NOT save the table object itself to the swap 
   stream. The << operator should be used on the output PPF stream being 
   used as swap in order to save the table. 

   \sa SOPHYA::MuTyV
   \sa SOPHYA::DataTableRow
   \sa SOPHYA::DataTable
   \sa SOPHYA::SwSegDataBlock  SOPHYA::PPFDataSwapper

   \code
   #include "swppfdtable.h"
   // ...
   {
   // ---- Creation of the table
   // Create the swap stream 
   POutPersist so("myswtable.ppf");
   SwPPFDataTable dt(so, 64);
   // define table columns
   dt.AddFloatColumn("X0_f");
   dt.AddFloatColumn("X1_f");
   dt.AddDoubleColumn("X0X0pX1X1_d");
   // Fill the table
   r_8 x[5];
   for(int i=0; i<63; i++) {
     x[0] = (i%9)-4.;  x[1] = (i/9)-3.;  x[2] = x[0]*x[0]+x[1]*x[1];
     dt.AddRow(x);
   }
   // Printing table info
   cout << dt ;
   // Swap out all data and write the table structure to the PPF stream 
   so << dt ;
   // ....
   }
   {
   // ---- Accessing information from a previously created table
   SwPPFDataTable dt;
   PInPersist si("myswtable.ppf");
   si >> dt;
   // Printing table info
   cout << dt ;   
   }
   \endcode
*/
//! Default constructor with optional specification of block (or segment) size 
SwPPFDataTable::SwPPFDataTable(sa_size_t segsz)
  : BaseDataTable(segsz),
    mSwOut(NULL), mSwIn(NULL)
{
}
/*! Constructor with the specification of the output swap stream -
    and optional specification of block (or segment) size
*/
SwPPFDataTable::SwPPFDataTable(POutPersist & os, sa_size_t segsz)
  : BaseDataTable(segsz) ,
    mSwOut(&os), mSwIn(NULL),
    mISwapper(os), mLSwapper(os), mFSwapper(os), mDSwapper(os), 
    mYSwapper(os), mZSwapper(os), mSSwapper(os)
{
}
//! Protected constructor for creation from a swap stream 
SwPPFDataTable::SwPPFDataTable(PInPersist & is, sa_size_t segsz)
  : BaseDataTable(segsz) ,
    mSwOut(NULL), mSwIn(NULL)
{
  PInPersist* pis = new PInPersist(is.FileName(), false);
  mSwIn = new St_InSwap;
  mSwIn->pis = pis;  mSwIn->refcnt = 1;
  mISwapper.SetInStream(*pis);
  mLSwapper.SetInStream(*pis);
  mFSwapper.SetInStream(*pis);
  mDSwapper.SetInStream(*pis);
  mYSwapper.SetInStream(*pis);
  mZSwapper.SetInStream(*pis);
  mSSwapper.SetInStream(*pis);
}

//! copy constructor - shares the data (and copies the thread safety state)

SwPPFDataTable::SwPPFDataTable(SwPPFDataTable const & a)
  : BaseDataTable(a.SegmentSize()),
    mSwOut(NULL), mSwIn(NULL)
{
  Share(a);
}
//! Destructor - Deletes / closes the input PPF swap stream  
SwPPFDataTable::~SwPPFDataTable()
{
  if (mSwIn) { 
    mSwIn->refcnt--;
    if (mSwIn->refcnt == 0) {
      delete mSwIn->pis;
      delete mSwIn;
      mSwIn = NULL;
    }
  }
}

void SwPPFDataTable::Share(SwPPFDataTable const & a)
{
  // Recopie (attention !) brutale des swappers  et les streams associes
  mISwapper = a.mISwapper;
  mLSwapper = a.mLSwapper;
  mFSwapper = a.mFSwapper;
  mDSwapper = a.mDSwapper;
  mYSwapper = a.mYSwapper;
  mZSwapper = a.mZSwapper;
  mSSwapper = a.mSSwapper;
  
  mSwOut = a.mSwOut;
  mSwIn = a.mSwIn;
  if (mSwIn)  mSwIn->refcnt++;  // comptage de reference pour le stream input 

  // On recopie la taille de segment
  mSegSz = a.SegmentSize();
  if (a.NVar() == 0)  return;  // Table sans colonne 

  // On copie la structure de table 
  CopyStructure(a);
  if (a.IsThreadSafe())  SetThreadSafe(true);
  else SetThreadSafe(false);

  //
  // Update nombre d'entree, ...
  mNEnt = a.mNEnt;
  mNSeg = a.mNSeg;
  if (a.mInfo)  mInfo = new DVList(*(a.mInfo));

  // mis a jour des tableax min-max 
  mMin = a.mMin;
  mMax = a.mMax;
  mMinMaxNEnt = a.mMinMaxNEnt;

  // Et on partage les donnees des colonnes 
  for (size_t kk=0; kk<mNames.size(); kk++) {
    sa_size_t sk = mNames[kk].ser;
    sa_size_t ska = a.mNames[kk].ser;
    switch (mNames[kk].type) {
    case IntegerField :
      mICols[sk] = a.mICols[ska];
    break;
    case LongField :
      mLCols[sk] = a.mLCols[ska]; 
      break;
    case FloatField :
      mFCols[sk] = a.mFCols[ska]; 
      break;
    case DoubleField :
    case DateTimeField :
      mDCols[sk] = a.mDCols[ska]; 
      break;
    case ComplexField :
      mYCols[sk] = a.mYCols[ska]; 
      break;
    case DoubleComplexField :
      mZCols[sk] = a.mZCols[ska]; 
      break;
    case StringField :
      mSCols[sk] = a.mSCols[ska]; 
      break;
    default:
      throw ForbiddenError("SwPPFDataTable::Share() : unknown column type ");
    break;
    }
  }  
}
void SwPPFDataTable::SwapOutAll() const
{
  // Et on vide les buffers de swap 
  for (size_t kk=0; kk<mNames.size(); kk++) {
    sa_size_t sk = mNames[kk].ser;
    switch (mNames[kk].type) {
    case IntegerField :
      mICols[sk].SwapOutBuffer();
    break;
    case LongField :
      mLCols[sk].SwapOutBuffer();
      break;
    case FloatField :
      mFCols[sk].SwapOutBuffer();
      break;
    case DoubleField :
    case DateTimeField :
      mDCols[sk].SwapOutBuffer();
      break;
    case ComplexField :
      mYCols[sk].SwapOutBuffer();
      break;
    case DoubleComplexField :
      mZCols[sk].SwapOutBuffer();
      break;
    case StringField :
      mSCols[sk].SwapOutBuffer();
      break;
    default:
      throw ForbiddenError("SwPPFDataTable::Share() : unknown column type ");
    break;
    }
  }  
}

void SwPPFDataTable::Clear()
{
  if ( (NVar() == 0) && (NEntry() == 0)) return;
  mNEnt = 0;
  mNSeg = 0;
  if (mVarD) delete[] mVarD;
  mVarD = NULL;
  if (mVarMTV) delete[] mVarMTV;
  mVarMTV = NULL;
  mNames.clear();
  if (mInfo) delete mInfo;
  mInfo = NULL;
  if (mThS) delete mThS;
  mThS = NULL;
  mMin.clear();
  mMax.clear();
  mMinMaxNEnt.clear();
  mIColsP.clear();
  mLColsP.clear();
  mFColsP.clear();
  mDColsP.clear();
  mYColsP.clear();
  mZColsP.clear();
  mSColsP.clear();

  mIColIdx.clear();
  mLColIdx.clear();
  mFColIdx.clear();
  mDColIdx.clear();
  mYColIdx.clear();
  mZColIdx.clear();
  mSColIdx.clear();

  mICols.clear();
  mLCols.clear();
  mFCols.clear();
  mDCols.clear();
  mYCols.clear();
  mZCols.clear();
  mSCols.clear();
}


/*! 
  Implements the action defined in the BaseDataTable interface.
  In the current implementation, throws an exception (ParmError) 
  if the table contains some data already.
*/
sa_size_t SwPPFDataTable::AddColumn(FieldType ft, string const & cnom)
{
  if (NEntry() > 0) 
    throw ParmError("SwPPFDataTable::AddColumn() Table contains already data ");
  CheckColName(cnom);
  sa_size_t ser; 
  sa_size_t idx = NVar(); 
  switch (ft) {
  case IntegerField :
    ser = mICols.size();
    mICols.push_back(SwSegDataBlock<int_4>(mISwapper, mSegSz));
    mIColIdx.push_back(idx);
    mIColsP.push_back(NULL);
    for(sa_size_t kk=0; kk<mICols.size(); kk++)
      mIColsP[kk] = &(mICols[kk]);
    break;
  case LongField :
    ser = mLCols.size();
    mLCols.push_back(SwSegDataBlock<int_8>(mLSwapper, mSegSz));
    mLColIdx.push_back(idx);
    mLColsP.push_back(NULL);
    for(sa_size_t kk=0; kk<mLCols.size(); kk++)
      mLColsP[kk] = &(mLCols[kk]);
    break;
  case FloatField :
    ser = mFCols.size();
    mFCols.push_back(SwSegDataBlock<r_4>(mFSwapper, mSegSz));
    mFColIdx.push_back(idx);
    mFColsP.push_back(NULL);
    for(sa_size_t kk=0; kk<mFCols.size(); kk++)
      mFColsP[kk] = &(mFCols[kk]);
    break;
  case DoubleField :
  case DateTimeField :
    ser = mDCols.size();
    mDCols.push_back(SwSegDataBlock<r_8>( mDSwapper,mSegSz));
    mDColIdx.push_back(idx);
    mDColsP.push_back(NULL);
    for(sa_size_t kk=0; kk<mDCols.size(); kk++)
      mDColsP[kk] = &(mDCols[kk]);
    break;
  case ComplexField :
    ser = mYCols.size();
    mYCols.push_back(SwSegDataBlock< complex<r_4> >(mYSwapper, mSegSz));
    mYColIdx.push_back(idx);
    mYColsP.push_back(NULL);
    for(sa_size_t kk=0; kk<mYCols.size(); kk++)
      mYColsP[kk] = &(mYCols[kk]);
    break;
  case DoubleComplexField :
    ser = mZCols.size();
    mZCols.push_back(SwSegDataBlock< complex<r_8> >(mZSwapper, mSegSz));
    mZColIdx.push_back(idx);
    mZColsP.push_back(NULL);
    for(sa_size_t kk=0; kk<mZCols.size(); kk++)
      mZColsP[kk] = &(mZCols[kk]);
    break;
  case StringField :
    ser = mSCols.size();
    mSCols.push_back(SwSegDataBlock<string>(mSSwapper, mSegSz));
    mSColIdx.push_back(idx);
    mSColsP.push_back(NULL);
    for(sa_size_t kk=0; kk<mSCols.size(); kk++)
      mSColsP[kk] = &(mSCols[kk]);
    break;
  default:
    throw ParmError("SwPPFDataTable::AddColumn() unknown field type ");
    break;
  }
  colst col;
  col.nom = cnom;
  col.type = ft;
  col.ser = ser;
  mNames.push_back(col);
  // On est oblige de calculer les min-max lors du remplissage
  // On ne peut pas en effet 'relire' le swap pendant l'ecriture
  mMin.push_back(9.E39);
  mMax.push_back(-9.E39);
  mMinMaxNEnt.push_back(0);

  return NVar();
}

//! Adds a row (or line to the table) with r_8* input data.
/*! 
  The min/max values for each column is updated, in addition 
  to the actions performed by the base class AddRow()
*/
sa_size_t SwPPFDataTable::AddRow(const r_8* data)
{
  // On est oblige de calculer les min-max lors du remplissage
  // On ne peut pas en effet 'relire' le swap pendant l'ecriture
  if (mThS) mThS->lock();   // tread-safety
  for(sa_size_t k=0; k<NVar(); k++) {
    double x = data[k];
    if (x < mMin[k])  mMin[k] = x;
    if (x > mMax[k])  mMax[k] = x;
    mMinMaxNEnt[k]++;
  }
  if (mThS) mThS->unlock(); // tread-safety
  return BaseDataTable::AddRow(data);
}

//! Adds a row (or line to the table) with input data as an array of MuTyV
/*! 
  The min/max values for each column is updated, in addition 
  to the actions performed by the base class AddRow()
*/
sa_size_t SwPPFDataTable::AddRow(const MuTyV * data)
{
  // On est oblige de calculer les min-max lors du remplissage
  // On ne peut pas en effet 'relire' le swap pendant l'ecriture
  if (mThS) mThS->lock();   // tread-safety
  for(sa_size_t k=0; k<NVar(); k++) {
    double x = (double)data[k];
    if (x < mMin[k])  mMin[k] = x;
    if (x > mMax[k])  mMax[k] = x;
    mMinMaxNEnt[k]++;
  }
  if (mThS) mThS->unlock(); // tread-safety
  return BaseDataTable::AddRow(data);
}


//! Adds a row (or line) to the table with input data as DataTableRow object
/*! 
  The min/max values for each column is updated, in addition 
  to the actions performed by the base class AddRow()
*/
sa_size_t SwPPFDataTable::AddRow(DataTableRow const& data)
{
  if ( data.Size() != NCols() )
    throw SzMismatchError(" SwPPFDataTable::AddRow() - data.Size() != NCols() ");
  return AddRow(data.MTVPtr());
}

} // FIN namespace SOPHYA 
