/***************************************************************************
**
**  This file is part of DinverCore.
**
**  DinverCore is free software: you can redistribute it and/or modify
**  it under the terms of the GNU General Public License as published by
**  the Free Software Foundation, either version 3 of the License, or
**  (at your option) any later version.
**
**  DinverCore 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 Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2005-12-20
**  Copyright: 2005-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <QGpCoreTools.h>
#include "ReportReader.h"
#include "ReportWriter.h"

namespace DinverCore {

/*!
  \class ReportReader ReportReader.h

  Reports partially inherits the ".report" format used by na_viewer and qtbsurfacewave before 2005.

  It is a generic class for report file management. Report files contain records of variable lengths.

  Contrary to its ancestor, the maximum number models inside a report is not fixed at
  the creation of the file, and it does not contain the "goal" curves any longer, nor the parameterization.
  We prefer using XML text files for the description of goal curves and the parameterization.

  These files, contrary to what would be expected, are not for random access. Originally, this was one
  of the requirements. However, for huge files, random access may generate lots of swap between buffered
  data and disk, takes a long long time. Nevertheless this object can offer a random access by constructing
  an offset table (see synchronize()).

  Description of the file format:

  8             Tag, 'DINVER05', for automatic file recognition
  4             Version number: current=1
  ...           Model blocks of variable lengths pointed by offsets

  Each model block starts with the raw parameters (stored by dinvercore in all situations)

  4             Block tag: "MBLK" for model blocks
  8             Offset of the next model block (0 for the last block under construction)
  8             Misfit in double precision (64 bits)
  4             Number of parameters (nd)
  4             Unique parameterization checksum (32 bits, unsigned int)
  nd*8          Parameter values for model in double precision (64 bits)
  4             User specific data tag, e.g. "DISP" for all blocks produced by dinverdc
  4             Version of user specific data (introduced in version 1 20071121)
  ...           Any user specific data of any length

  ReportWriter can be used to create/add models to a report.

  ReportReader is a SharedObject to be deleted automatically when no longer referenced. This feature is use by DCModelInfo
*/

ReportReader::ReportReader(QString fileName)
{
  TRACE;
  _f.setFileName(fileName);
}

/*!
 * Opens the report file, checks initial tag and general version.
 * The offset table is not loaded into memory for random access (\sa synchronize()).
 */
bool ReportReader::open()
{
  TRACE;
  if(_f.open(QIODevice::ReadOnly)) {
    _s.setDevice(&_f);
    char buf[8];
    _s.readRawData(buf, 8);
    if(strncmp(buf, REPORT_FILE_TAG, 8)==0) {
      _s >> _version;
      if(_version>REPORT_CURRENT_VERSION) {
        App::log(tr("This inversion report has been generated with a more recent version\n") );
        return false;
      } else {
        // file is now ready for reading
        return true;
      }
    } else {
      App::log(tr("Bad file tag for inversion report, corrupted file\n") );
      return false;
    }
  } else {
    App::log(tr("Cannot open file %1 for reading\n").arg(_f.fileName()) );
    return false;
  }
}

bool ReportReader::isReportFormat(QString fileName)
{
  TRACE;
  QFile f(fileName);
  if(f.open(QIODevice::ReadOnly)) {
    QDataStream s(&f);
    char buf[8];
    s.readRawData(buf, 8);
    if(strncmp(buf, REPORT_FILE_TAG, 8)==0) {
      int version;
      s >> version;
      if(version>REPORT_CURRENT_VERSION) {
        return false;
      } else {
        return true;
      }
    } else {
      return false;
    }
  } else {
    return false;
  }
}

qint64 ReportReader::lastBlockOffset()
{
  TRACE;
  if(_offsets.isEmpty())
    return 0;
  else
    return _offsets.last();
}

/*!
  Synchronizes offset table with file contents. This is mandatory for random access.
  Offsets points right after tag and offset to next block (12-byte shift).
*/
void ReportReader::synchronize()
{
  TRACE;
  char buf[4];
  qint64 offset;
  if(_offsets.isEmpty()) {
    offset=12;
  } else {
    offset=lastBlockOffset()-12;
    _offsets.removeLast();
  }
  _f.seek(offset);
  while(true) {
    _s.readRawData(buf, 4);
    if(strncmp(buf, REPORT_BLOCK_TAG, 4)==0) {
      _offsets << (offset+12); // store offset where header is skipped
      _s >> offset;
      if(offset==0) break;
      _f.seek(offset);
    } else break;
  }
}

double ReportReader::misfit(int index)
{
  TRACE;
  _f.seek(_offsets.at(index));
  double m;
  _s >> m;
  return m;
}

/*!
  Must be called just after a call to misfit(int index).
  Returns the version number of the user block or -1 if a bad tag is encountered.
  0 is returned for all reports with general version 0.
*/
int ReportReader::userBlockVersion(const char * tag)
{
  TRACE;
  int nd;
  _s >> nd;
  _f.seek(_f.pos()+nd*8+4);
  char buf[4];
  _s.readRawData(buf,4);
  if(strncmp(buf, tag, 4)==0) {
    int userVersion;
    if(_version>=1) {  // version 0 had no version number for user blocks
      _s >> userVersion;
    } else {
      userVersion=0;
    }
    return userVersion;
  } else {
    App::log(tr("Bad tag for user block\n") );
  }
  return -1;
}

} // namespace DinverCore
