/***************************************************************************
**
**  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: 2013-11-21
**  Authors:
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#ifndef GP_MATLAB_LIBS

#include <zlib.h>

#include "MatReader.h"
#include "MatContext.h"

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

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
MatReader::MatReader(MatContext * context)
{
  TRACE;
  _context=context;
}

/*!
  Description of destructor still missing
*/
MatReader::~MatReader()
{
  TRACE;
}

bool MatReader::isValidHeader(const QString& fileName)
{
  TRACE;
  QFile f(fileName);
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("MatReader: cannot open file %1\n").arg(fileName) );
    return false;
  }
  QDataStream s(&f);
  char buf[20];
  s.readRawData(buf, 19);
  return strncmp(buf, "MATLAB 5.0 MAT-file", 19)==0;
}

bool MatReader::read(const QString& fileName)
{
  TRACE;
  QFile f(fileName);
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("MatReader: cannot open file %1\n").arg(fileName) );
    return false;
  }
  QDataStream s(&f);
  // skip text header
  s.skipRawData(126);
  // Byte order
  ushort im;
  s >> im;
  if(im!=0x4D49) {
    s.setByteOrder(QDataStream::LittleEndian);
  }

  return parse(s);
}

bool MatReader::parse(QDataStream& s)
{
  TRACE;

  while(!s.atEnd()) {
    uint dataType, dataSize;
    s >> dataType;
    dataSize=dataType & 0xFFFF0000;
    dataType&=0x0000FFFF;
    uint tagSize;
    if(dataSize) {             // small data element
      dataSize=dataSize >> 16;
      tagSize=4;
    } else {
      s >> dataSize;
      tagSize=8;
    }
    App::log(5, tr("MatReader: datatype %1 dataSize %2\n").arg(dataType).arg(dataSize));
    switch(dataType) {
    case 1: { // miINT8
        qint8 val;
        uint valueCount=dataSize;
        if(!_context->beginElement(valueCount)) {
          return false;
        }
        for(uint i=0; i<valueCount; i++) {
          s >> val;
          _context->addInt8(val);
          App::log(5, tr("MatReader: INT8 %1\n").arg(val));
        }
        if(!_context->endElement()) {
          return false;
        }
      }
      break;
    case 5: { // miINT32
        qint32 val;
        uint valueCount=dataSize >> 2;
        if(!_context->beginElement(valueCount)) {
          return false;
        }
        for(uint i=0; i<valueCount; i++) {
          s >> val;
          _context->addInt32(val);
          App::log(5, tr("MatReader: INT32 %1\n").arg(val));
        }
        if(!_context->endElement()) {
          return false;
        }
      }
      break;
    case 6: { // miUINT32
        quint32 val;
        uint valueCount=dataSize >> 2;
        if(!_context->beginElement(valueCount)) {
          return false;
        }
        for(uint i=0; i<valueCount; i++) {
          s >> val;
          _context->addUInt32(val);
          App::log(5, tr("MatReader: UINT32 %1\n").arg(val));
        }
        if(!_context->endElement()) {
          return false;
        }
      }
      break;
    case 9: { // miDOUBLE
        double val;
        uint valueCount=dataSize >> 3;
        if(!_context->beginElement(valueCount)) {
          return false;
        }
        for(uint i=0; i<valueCount; i++) {
          s >> val;
          _context->addDouble(val);
          App::log(5, tr("MatReader: DOUBLE %1\n").arg(val));
        }
        if(!_context->endElement()) {
          return false;
        }
      }
      break;
    case 14:  // miMATRIX
      if(!parseMatrix(s, dataSize)) {
        return false;
      }
      break;
    case 15:  // miCOMPRESSED
      if(!parseCompressed(s, dataSize)) {
        return false;
      }
      break;
    case 16:  // miUTF8
      if(!parseUtf8(s, dataSize)) {
        return false;
      }
      break;
    case 17:  // miUTF16
      if(!parseUtf16(s, dataSize)) {
        return false;
      }
      break;
    case 18:  // miUTF32
      if(!parseUtf32(s, dataSize)) {
        return false;
      }
      break;
    default:
      App::log(tr("MatReader: data type '%1' currently not supported, skipping\n").arg(dataType) );
      return false;
    }
    // accounts for padding bytes, 64 bit alignment
    int paddingCount=8-(dataSize+tagSize)%8;
    if(paddingCount<8) {
      App::log(tr("MatReader: padding %1 bytes\n").arg(paddingCount) );
      s.skipRawData(paddingCount);
    }
    //printf("POS %i\n", s.device()->pos());
  }
  //printf("EXIT\n");
  return true;
}

bool MatReader::parseCompressed(QDataStream& s, uint dataSize)
{
  TRACE;

  // MAT files are badly designed. We do not know the size of the decompressed data, we assume initially 3 times the compressed size
  char * compressedBuf=new char[dataSize];
  s.readRawData(compressedBuf, dataSize);
  int compressionFactor=3;
  bool uncompressed=false;
  unsigned long uncompressedLen;
  char * uncompressedBuf=0;
  while(!uncompressed) {
    uncompressedLen=compressionFactor*dataSize;
    uncompressedBuf=new char[compressionFactor*dataSize];
    int err=uncompress(reinterpret_cast<Bytef *>(uncompressedBuf), &uncompressedLen,
                       reinterpret_cast<const Bytef *>(compressedBuf), dataSize);
    switch(err) {
    case Z_OK:
      uncompressed=true;
      break;
    case Z_BUF_ERROR:
      compressionFactor++;
      App::log(tr("MatReader: destination buffer was too small, increasing compression factor to %1\n").arg(compressionFactor) );
      delete [] uncompressedBuf;
      break;
    default:
      delete [] compressedBuf;
      delete [] uncompressedBuf;
      App::log(tr("MatReader: %1\n").arg(zError(err)) );
      return false;
    }
  }
  delete [] compressedBuf;
  App::log(tr("MatReader: compressed len %1 -> uncompressed len %2\n").arg(dataSize).arg(uncompressedLen) );
  QByteArray data=QByteArray::fromRawData(uncompressedBuf, uncompressedLen);
  QDataStream datas(&data, QIODevice::ReadOnly);
  datas.setByteOrder(s.byteOrder());
  App::log(5, tr("MatReader: begin parsing compressed data\n"));
  bool ret=parse(datas);
  App::log(5, tr("MatReader: end parsing compressed data\n"));
  delete [] uncompressedBuf;
  return ret;
}

bool MatReader::parseMatrix(QDataStream& s, uint dataSize)
{
  TRACE;
  char * buf=new char[dataSize];
  s.readRawData(buf, dataSize);
  QByteArray data=QByteArray::fromRawData(buf, dataSize);
  QDataStream datas(&data, QIODevice::ReadOnly);
  datas.setByteOrder(s.byteOrder());
  _context->openMatrix();
  App::log(5, tr("MatReader: begin parsing array\n"));
  bool ret=parse(datas);
  App::log(5, tr("MatReader: end parsing array\n"));
  _context->closeMatrix();
  delete [] buf;
  return ret;
}

bool MatReader::parseUtf8(QDataStream& s, uint dataSize)
{
  TRACE;
  char * buf=new char[dataSize];
  s.readRawData(buf, dataSize);
  QByteArray data=QByteArray::fromRawData(buf, dataSize);
  QString text;
  text=QString::fromUtf8(data);
  App::log("utf8 "+text+"\n");
  delete [] buf;
  return true;
}

bool MatReader::parseUtf16(QDataStream& s, uint dataSize)
{
  TRACE;
  char * buf=new char[dataSize];
  s.readRawData(buf, dataSize);
  App::log("utf16\n");
  delete [] buf;
  return true;
}

bool MatReader::parseUtf32(QDataStream& s, uint dataSize)
{
  TRACE;
  char * buf=new char[dataSize];
  s.readRawData(buf, dataSize);
  App::log("utf32\n");
  delete [] buf;
  return true;
}

#endif // GP_MATLAB_LIBS
