/***************************************************************************
**
**  This file is part of matfiles.
**
**  This file may be distributed and/or modified under the terms of the
**  GNU General Public License version 2 or 3 as published by the Free
**  Software Foundation and appearing in the file LICENSE.GPL included
**  in the packaging of this file.
**
**  This file is distributed in the hope that it will be useful, but WITHOUT
**  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
**  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
**  more details.
**
**  You should have received a copy of the GNU General Public License
**  along with this program. If not, see <http://www.gnu.org/licenses/>.
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2010-07-20
**  Authors:
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#ifdef GP_MATLAB_LIBS
#include <mat.h>
#endif

#include "MatFormat.h"
#include "MatlabVariableName.h"
#include "MatReader.h"
#include "MatContext.h"

#ifdef GP_MATLAB_LIBS
class MatFormatPrivate
{
public:
  static MATFile * open(const QString& fileName);
  static mxArray * getHeaderVariable(MATFile * fmat, const QByteArray& variableName);
  static bool loadHeader(QMap<QByteArray, QVariant>& headerValues, const QString& fileName);
  static void addHeader(MATFile * fmat, QMap<QByteArray, QVariant>& headerValues, MetaDataFactory::StandardData d);
};

MATFile * MatFormatPrivate::open(const QString& fileName)
{
  TRACE;
  if(QString(fileName.toLatin1())!=fileName) {
    Message::warning(MSG_ID, tr("Loading Mat-files"),
                     tr("File path contains non-ASCII characters. This is a current limitation of Matlab engine. "
                        "Move your file to a directory with ASCII characters only.\n%1").arg(fileName), Message::cancel(), true);
    return 0;
  }
  MATFile * fmat=matOpen(fileName.toLatin1().data(), "r");
  if(!fmat) {
    Message::warning(MSG_ID, tr("Loading Mat-files"),
                     tr("Cannot open file %1.").arg(fileName), Message::cancel(), true);
    return 0;
  }
  return fmat;
}

mxArray * MatFormatPrivate::getHeaderVariable(MATFile * fmat, const QByteArray& variableName)
{
  TRACE;
  mxArray * value=matGetVariable(fmat, variableName.data());
  if(value) {
    if(mxGetNumberOfDimensions(value)==2 &&
       mxGetDimensions(value)[0]==1 &&
       mxGetDimensions(value)[1]==1) {
      return value;
    } else {
      mxDestroyArray(value);
    }
  }
  return 0;
}

void MatFormatPrivate::addHeader(MATFile * fmat, QMap<QByteArray, QVariant>& headerValues, MetaDataFactory::StandardData d)
{
  TRACE;
  QString hName=MetaDataFactory::standardName(MetaDataFactory::SamplingFrequency);
  mxArray * value=getHeaderVariable(fmat, varName);
  if(value) {
    headerValues.insert(varName, mxGetPr(value)[0]);
    mxDestroyArray(value);
  }

}

bool MatFormatPrivate::loadHeader(QMap<QByteArray, QVariant>& headerValues, const QString& fileName)
{
  TRACE;
  MATFile * fmat=open(fileName);
  if(!fmat) {
    return false;
  }
  mxArray * value;

  addHeader(fmat, headerValues, MetaDataFactory::SamplingFrequency);
  addHeader(fmat, headerValues, MetaDataFactory::SamplingPeriod);
  addHeader(fmat, headerValues, MetaDataFactory::SamplingT0);

  matClose(fmat);
  return true;
}
#endif // GP_MATLAB_LIBS

/*!
  \class MatFormat MatFormat.h
  \brief Brief description of class still missing

  Full description of class still missing
*/

const QString MatFormat::xmlMatFormatTag="MatFormat";

/*!
  Description of constructor still missing
*/
MatFormat::MatFormat()
    : AbstractFileFormat()
{
  TRACE;
  setName("MatlabMat");
  setCaption(tr("Matlab Mat-files"));
  addSuffix("mat");

#ifndef GP_MATLAB_LIBS
  _context=new MatContext;
#endif
}

/*!
  Description of destructor still missing
*/
MatFormat::~MatFormat()
{
  TRACE;
#ifndef GP_MATLAB_LIBS
  delete _context;
#endif
}

bool MatFormat::isValid(const QString& fileName) const
{
  TRACE;
#ifdef GP_MATLAB_LIBS
  MATFile * fmat=MatFormatPrivate::open(fileName);
  if(!fmat) {
    return false;
  }
  int nVariables=0;
  char ** variableNames=reinterpret_cast<char **>(matGetDir(fmat, &nVariables));
  mxFree(variableNames);
  matClose(fmat);
  if(!variableNames || nVariables==0) {
    App::log(tr("No variable stored in file %1.\n").arg(fileName) );
    return false;
  }
  return true;
#else
  return MatReader::isValidHeader(fileName);
#endif
}

bool MatFormat::load(SignalFile * file) const
{
  TRACE;
#ifdef GP_MATLAB_LIBS
  // Query header information
  QMap<QByteArray, QVariant> headerValues;
  MatFormatPrivate::loadHeader(headerValues, file->name());
  // Scan for all the other variables
  MATFile * fmat=MatFormatPrivate::open(file->name());
  if(!fmat) {
    return false;
  }
  int iSig=0;
  int iVar=0;
  const char * variableName=0;
  while(true) {
    mxArray * array=matGetNextVariableInfo(fmat, &variableName);
    if(!array) {
      if(feof(matGetFp(fmat))) {
        matClose(fmat);
        return true;
      } else {
        App::log(tr("Error after reading %1 variables in file %2.\n").arg(iVar).arg(file->name()) );
      }
    }
    if(!headerValues.contains(variableName)) { // Skip variables used for meta data
      if(mxGetNumberOfDimensions(array)==2) {
        int nChannels=mxGetDimensions(array)[0];
        int nSamples=mxGetDimensions(array)[1];
        int n0=(int)ceil(log10(nChannels));
        for(int iChan=0; iChan<nChannels; iChan++) {
          Signal * newSignal=new Signal(file->database());
          newSignal->setFile(file);
          newSignal->setNumberInFile(iSig);
          newSignal->setNSamples(nSamples);
          newSignal->setOffsetInFile(iChan); // Store channel index for current variable
          newSignal->setMetaData(MatlabVariableName(variableName));
          newSignal->addMetaData(vn);
          newSignal->setType(Signal::Waveform);
          SignalFile::setSignalName(newSignal, variableName, QString("_%1").arg(iChan, n0, 10, QChar('0')), iChan, file->shortName());
          newSignal->setComponent(Signal::UndefinedComponent);
          iSig++;
        }
      } else {
        App::log(tr("Skip variable %1 not having 2 dimensions in file %2.\n").arg(variableName).arg(file->name()) );
      }
    }
    mxDestroyArray(array);
    iVar++;
  }
  return false;
#else
  MatReader mr(_context);
  if(!mr.read(file->name())) {
    App::log(tr("Error reading file %1\n").arg(file->name()) );
    return false;
  }
  // Query header information
  //QMap<QByteArray, QVariant> headerValues;
  //MatFormatPrivate::loadHeader(headerValues, file->name());
  // Scan for all the other variables
  int iSig=0;
  for(int i=0; i<_context->arrayCount(); i++) {
    MatArray * array=_context->array(i);
    //if(!headerValues.contains(array->name())) { // Skip variables used for meta data
      int nChannels=array->dimension(0);
      int nSamples=array->dimension(1);
      int n0=(int)ceil(log10(nChannels));
      for(int iChan=0; iChan<nChannels; iChan++) {
        Signal * newSignal=new Signal(file->database());
        newSignal->setFile(file);
        newSignal->setNumberInFile(iSig);
        newSignal->setNSamples(nSamples);
        newSignal->setOffsetInFile(iChan); // Store channel index for current variable
        newSignal->setMetaData(MatlabVariableName(array->name()));
        newSignal->setType(Signal::Waveform);
        SignalFile::setSignalName(newSignal, array->name(), QString("_%1").arg(iChan, n0, 10, QChar('0')), iChan, file->shortName());
        newSignal->setComponent(Signal::UndefinedComponent);
        iSig++;
      }
    //}
  }
  return true;
#endif
}

bool MatFormat::load(const Signal * sig, double * samples) const
{
  TRACE;
  const MatlabVariableName& variableName=sig->metaData<MatlabVariableName>();
#ifdef GP_MATLAB_LIBS
  MATFile * fmat=MatFormatPrivate::open(sig->file()->name());
  if(!fmat) {
    return false;
  }
  mxArray * array=matGetVariable(fmat, variableName.value().toLatin1().data());
  // TODO: like text files in columns: loads all signal of the same matrix together
  int nSamples=sig->nSamples();
  double * ptr=mxGetPr(array)+nSamples*sig->numberInFile();
  memcpy(samples, ptr, sizeof(double)*nSamples);
  mxDestroyArray(array);
  matClose(fmat);
  return true;
#else
  MatArray * array=_context->array(variableName.value());
  if(array) {
    int nSamples=sig->nSamples();
    double * ptr=array->realDoubleValues()+sig->numberInFile();
    int delta=array->dimension(0);
    for(int i=0; i<nSamples; i++) {
      samples[i]=*ptr;
      ptr+=delta;
    }
  } else {
    qWarning("No MatArray available with name %s", variableName.value().toLatin1().data());
    return false;
  }
  return false;
#endif
}

bool MatFormat::save(const SubSignalPool& subpool, const QString& filePath) const
{
  TRACE;
#ifdef GP_MATLAB_LIBS
  App::log(tr("Export of mat-files not yet supported (if you need it, send a request to the developers.\n") );
  return true;
#else
  Q_UNUSED(subpool)
  Q_UNUSED(filePath)
  return false;
#endif
}
