/***************************************************************************
**
**  This file is part of DinverDCCore.
**
**  DinverDCCore 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.
**
**  DinverDCCore 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: 2006-01-05
**  Copyright: 2006-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <DinverCore.h>
#include <QGpCompatibility.h>
#include <QGpCoreWave.h>

#include "DCReportBlock.h"
#include "TargetList.h"
#include "TargetList2D.h"
#include "ParamProfile.h"

namespace DinverDCCore {

/*!
  \class DCReportBlock
  DISP block containing the following sub-blocks:
    \li version = current is 2
    \li 5 Ground model profiles (1 is not used)
    \li dispersion
    \li autocorr
    \li ellipticity
    \li refractionVp
    \li refractionVs

  4       Tag "DISP"
  20*8    Offsets to sub-blocks (currently only 9 are in use)
          0 if block not available
*/

DCReportBlock::DCReportBlock(QDataStream& s)
{
  TRACE;
  _profileOffsets=new qint64[20];
  _modalStorageOffsets=nullptr;
  _s=&s;
  _reader=nullptr;
}

DCReportBlock::~DCReportBlock()
{
  TRACE;
  delete [] _profileOffsets;
  delete [] _modalStorageOffsets;
  delete _reader;
}

void DCReportBlock::writeOffsetTable(QDataStream& s)
{
  TRACE;
  qint64 offset=0;
  // room for 20 offsets, up to 160 bytes
  for(int i=0; i<20; i++) {
    s << offset;
  }
}

void DCReportBlock::write(ReportWriter * report, const TargetList& tl,
                          const Profile * vp, const Profile * vs,
                          const Profile * rho, const Profile * pitch,
                          const Profile * res)
{
  TRACE;
  // Tag and block version
  report->userBlockHeader("DISP", DCREPORTBLOCK_CURRENT_VERSION);
  QDataStream& s=report->stream();
  qint64 tableOffset=s.device()->pos();
  writeOffsetTable(s);
  // Save profiles
  if(vp) {
    ReportWriter::setCurrentOffset(s, tableOffset);
    vp->writeReport(s);
  }
  if(vs) {
    ReportWriter::setCurrentOffset(s, tableOffset+8);
    vs->writeReport(s);
  }
  if(rho) {
    ReportWriter::setCurrentOffset(s, tableOffset+16);
    rho->writeReport(s);
  }
  if(pitch) {
    ReportWriter::setCurrentOffset(s, tableOffset+24);
    pitch->writeReport(s);
  }
  if(res) {
    ReportWriter::setCurrentOffset(s, tableOffset+32);
    res->writeReport(s);
  }
  // Surface waves
  if(tl._dispersionFactory) {
    ReportWriter::setCurrentOffset(s, tableOffset+40);
    writeFactory(s, *tl._dispersionFactory);
  }
  if(tl._autocorrFactory) {
    ReportWriter::setCurrentOffset(s, tableOffset+48);
    writeFactory(s, *tl._autocorrFactory);
  }
  if(tl._ellipticityFactory) {
    ReportWriter::setCurrentOffset(s, tableOffset+56);
    writeFactory(s, *tl._ellipticityFactory);
  }
  // Refraction
  if(tl._refractionVpFactory) {
    ReportWriter::setCurrentOffset(s, tableOffset+64);
    tl._refractionVpFactory->writeReport(s);
  }
  if(tl._refractionVsFactory) {
    ReportWriter::setCurrentOffset(s, tableOffset+72);
    tl._refractionVsFactory->writeReport(s);
  }
  // Magneto-telluric
  if(tl._magnetoTelluricFactory) {
    ReportWriter::setCurrentOffset(s, tableOffset+80);
    tl._magnetoTelluricFactory->writeReport(s);
  }
}

/*!
  Copy one model from \a inReport to \a outReport. \a inReport must point to the
  beginning of the user block ("DISP"), after tag and version. The model is appended to \a outReport.
*/
void DCReportBlock::write(ReportWriter * outReport, ReportReader * inReport)
{
  TRACE;
  int inVersion=inReport->userBlockVersion("DISP");
  if(inVersion<0) {
    return;
  }
  DCReportBlock dcBlock(inReport->stream());
  dcBlock.readProfiles(inVersion);
  // Tag and block version
  outReport->userBlockHeader("DISP", DCREPORTBLOCK_CURRENT_VERSION);
  QDataStream& sOut=outReport->stream();
  qint64 tableOffset=sOut.device()->pos();
  writeOffsetTable(sOut);
  // Save profiles
  for(int i=0;i<5;i++) {
    if(dcBlock.profile(i)) {
      Profile p;
      ReportWriter::setCurrentOffset(sOut, tableOffset + i*8);
      p.readReport(inReport->stream());
      p.writeReport(sOut);
    }
  }
  if(dcBlock.profile(5)) {   // Dispersion
    ReportWriter::setCurrentOffset(sOut, tableOffset+40);
    writeFactory(sOut, dcBlock, 4);
  }
  if(dcBlock.profile(6)) { // Autocorr
    ReportWriter::setCurrentOffset(sOut, tableOffset+48);
    int nRings;
    dcBlock.stream() >> nRings;
    writeFactory(sOut, dcBlock, 3*nRings);
  }
  if(dcBlock.profile(7)) { // Ellipticity
    ReportWriter::setCurrentOffset(sOut, tableOffset+56);
    writeFactory(sOut, dcBlock, 1);
  }
  if(dcBlock.profile(8)) { // Refraction Vp
    ReportWriter::setCurrentOffset(sOut, tableOffset+64);
    RefractionFactory f;
    f.readReport(dcBlock.stream());
    f.writeReport(sOut);
  }
  if(dcBlock.profile(9)) { // Refraction Vs
    ReportWriter::setCurrentOffset(sOut, tableOffset+72);
    RefractionFactory f;
    f.readReport(dcBlock.stream());
    f.writeReport(sOut);
  }
  if(dcBlock.profile(10)) { // Magneto-telluric
    ReportWriter::setCurrentOffset(sOut, tableOffset+80);
    MagnetoTelluricFactory f;
    f.readReport(dcBlock.stream());
    f.writeReport(sOut);
  }
}

/*!
  Write to report in \a s from \a dcBlock read in another report
*/
void DCReportBlock::writeFactory(QDataStream& s, DCReportBlock& dcBlock,
                                 int storageCount)
{
  TRACE;
  qint64 tableOffset=s.device()->pos();
  qint64 offset=0;
  for(int iS=0; iS<storageCount; iS++) {
    s << offset;
  }
  dcBlock.readModalStorages(storageCount);
  for(int iS=0; iS<storageCount; iS++) {
    if(dcBlock.modalStorage(iS)) {
      ReportWriter::setCurrentOffset(s, tableOffset);
      ModalStorageReader reader(dcBlock.stream());
      s << reader.nModes();
      for(int i=0; i<reader.nModes(); i++) {
        if(reader.seek(dcBlock.stream(), i)) {
          reader.toStream(dcBlock.stream(), s);
        }
      }
    }
    tableOffset+=8;
  }
}

void DCReportBlock::writeFactory(QDataStream& s, const ModalFactory& f)
{
  TRACE;
  f.writeReportHeader(s);
  qint64 tableOffset=s.device()->pos();
  qint64 offset=0;
  int nStorages=f.storageCount();
  for(int i=0; i<nStorages; i++) {
    s << offset;
  }
  // Save storages
  for(int i=0;i<nStorages;i++) {
    ModalStorage * ms=f.storage(i);
    if(ms) {
      ReportWriter::setCurrentOffset(s, tableOffset);
      ms->writeReport(s);
    }
    tableOffset+=8;
  }
}

/*!
  Read the profile offset table and cache it
*/
void DCReportBlock::readProfiles(int version)
{
  TRACE;
  switch (version) {
  case 0:
  case 1:
    for(int i=0;i<9;i++)
      stream() >> _profileOffsets[i];
    for(int i=9;i<20;i++)
      _profileOffsets[i]=0;
    break;
  case 2:
  case 3:
    for(int i=0;i<20;i++)
      stream() >> _profileOffsets[i];
    break;
  default:
    App::log(tr("Report has been produced by a more recent version of this library %1 (current=%2).\n")
                 .arg(version).arg(DCREPORTBLOCK_CURRENT_VERSION));
    for(int i=0;i<20;i++)
      _profileOffsets[i]=0;
    break;
  }
}

/*!
  Read the modal storage offset table and cache it
*/
void DCReportBlock::readModalStorages(int nStorages)
{
  TRACE;
  delete [] _modalStorageOffsets;
  _modalStorageOffsets=new qint64[nStorages];
  for(int i=0;i<nStorages;i++) stream() >> _modalStorageOffsets[i];
}

Seismic1DModel * DCReportBlock::surfaceWaveModel(const QVector<double>& zProf,
                                               QVector<double>& vpProf,
                                               const QVector<double>& vsProf,
                                               const QVector<double>& rhoProf,
                                               const QVector<double> * nuMinProf,
                                               const QVector<double> * nuMaxProf)
{
  TRACE;
  ASSERT(!nuMinProf || (nuMinProf && nuMaxProf));
  int nLayers=zProf.size();
  Seismic1DModel * surfModel=new Seismic1DModel(nLayers);
  int i;
  int nLayers1=nLayers - 1;
  double z=0.0;
  for(i=0;i < nLayers1;i++ ) {
    surfModel->setH(i, zProf[i] - z);
    z=zProf[i];
    surfModel->setSlowS(i, 1.0/vsProf[i] );
    if(nuMinProf && nuMinProf->at(i)>=nuMaxProf->at(i)) {
      vpProf[i]=vsProf[i]*sqrt(2.0*(nuMinProf->at(i)-1.0)/(2.0*nuMinProf->at(i)-1.0));
      surfModel->setSlowP(i, 1.0/vpProf[i] );
    }
    surfModel->setSlowP(i, 1.0/vpProf[i] );
    surfModel->setRho(i, rhoProf[i] );
    surfModel->setQp(i, 0.0);
    surfModel->setQs(i, 0.0);
  }
  if(nuMinProf && nuMinProf->at(i)>=nuMaxProf->at(i)) {
    vpProf[i]=vsProf[i]*sqrt(2.0*(nuMinProf->at(i)-1.0)/(2.0*nuMinProf->at(i)-1.0));
  }
  surfModel->setSlowP(i, 1.0/vpProf[i] );
  surfModel->setSlowS(i, 1.0/vsProf[i] );
  surfModel->setRho(i, rhoProf[i] );
  surfModel->setQp(i, 0.0);
  surfModel->setQs(i, 0.0);
  return surfModel;
}

Seismic1DModel * DCReportBlock::vspModel(const QVector<double>& zProf,
                                       const QVector<double>& vpProf,
                                       const QVector<double>& vsProf)
{
  TRACE;
  int nLayers=zProf.size();
  Seismic1DModel * surfModel=new Seismic1DModel(nLayers);
  int i;
  int nLayers1=nLayers - 1;
  double z=0.0;
  for(i=0;i < nLayers1;i++ ) {
    surfModel->setH(i, zProf[i] - z);
    z=zProf[i];
    surfModel->setSlowP(i, 1.0/vpProf[i] );
    surfModel->setSlowS(i, 1.0/vsProf[i] );
    surfModel->setRho(i, 0.0);
    surfModel->setQp(i, 0.0);
    surfModel->setQs(i, 0.0);
  }
  surfModel->setSlowP(i, 1.0/vpProf[i] );
  surfModel->setSlowS(i, 1.0/vsProf[i] );
  surfModel->setRho(i, 0.0);
  surfModel->setQp(i, 0.0);
  surfModel->setQs(i, 0.0);
  return surfModel;
}

Seismic1DModel * DCReportBlock::vpModel(const QVector<double>& zProf,
                                      const QVector<double>& vpProf)
{
  TRACE;
  int nLayers=zProf.size();
  Seismic1DModel * surfModel=new Seismic1DModel(nLayers);
  int i;
  int nLayers1=nLayers - 1;
  double z=0.0;
  for(i=0;i < nLayers1;i++ ) {
    surfModel->setH(i, zProf[i] - z);
    z=zProf[i];
    surfModel->setSlowP(i, 1.0/vpProf[i] );
    surfModel->setSlowS(i, 0.0);
    surfModel->setRho(i, 0.0);
    surfModel->setQp(i, 0.0);
    surfModel->setQs(i, 0.0);
  }
  surfModel->setSlowP(i, 1.0/vpProf[i] );
  surfModel->setSlowS(i, 0.0);
  surfModel->setRho(i, 0.0);
  surfModel->setQp(i, 0.0);
  surfModel->setQs(i, 0.0);
  return surfModel;
}

Seismic1DModel * DCReportBlock::vsModel(const QVector<double>& zProf,
                                      const QVector<double>& vsProf)
{
  TRACE;
  int nLayers=zProf.size();
  Seismic1DModel * surfModel=new Seismic1DModel(nLayers);
  int i;
  int nLayers1=nLayers - 1;
  double z=0.0;
  for(i=0;i < nLayers1;i++ ) {
    surfModel->setH(i, zProf[i] - z);
    z=zProf[i];
    surfModel->setSlowP(i, 0.0);
    surfModel->setSlowS(i, 1.0/vsProf[i] );
    surfModel->setRho(i, 0.0);
    surfModel->setQp(i, 0.0);
    surfModel->setQs(i, 0.0);
  }
  surfModel->setSlowP(i, 0.0);
  surfModel->setSlowS(i, 1.0/vsProf[i] );
  surfModel->setRho(i, 0.0);
  surfModel->setQp(i, 0.0);
  surfModel->setQs(i, 0.0);
  return surfModel;
}

/*!
  Reads profiles and build a layered model according to available profiles.
*/
Seismic1DModel * DCReportBlock::readSeismicModel()
{
  // Read all profiles available to produce a full sampling of the depths
  Profile vpProf, vsProf, rhoProf, pitchProf;
  if(vp()) vpProf.readReport(*_s);
  if(vs()) vsProf.readReport(*_s);
  if(rho()) rhoProf.readReport(*_s);
  if(pitch()) pitchProf.readReport(*_s); // Used only to ensure complete sampling
  QVector<double> depths;
  if(vpProf.count()==0) {
    if(vsProf.count()==0) {
      return nullptr;
    } else {
      depths << vsProf.depths();
      depths << pitchProf.depths(); // It matters only if pitch is not empty
      std::sort(depths.begin(), depths.end());
      unique(depths);
      vsProf.resample(depths);
      return vsModel(vsProf.depths() ,vsProf.values());
    }
  } else {
    if(vsProf.count()==0) {
      depths << vpProf.depths();
      depths << pitchProf.depths(); // It matters only if pitch is not empty
      std::sort(depths.begin(), depths.end());
      unique(depths);
      vpProf.resample(depths);
      return vpModel(vpProf.depths() ,vpProf.values());
    } else if(rhoProf.count()==0) {
      depths << vpProf.depths();
      depths << vsProf.depths();
      depths << pitchProf.depths();
      std::sort(depths.begin(), depths.end());
      unique(depths);
      vpProf.resample(depths);
      vsProf.resample(depths);
      return vspModel(depths ,vpProf.values() ,vsProf.values());
    } else {
      depths << vpProf.depths();
      depths << vsProf.depths();
      depths << rhoProf.depths();
      depths << pitchProf.depths();
      std::sort(depths.begin(), depths.end());
      unique(depths);
      vpProf.resample(depths);
      vsProf.resample(depths);
      rhoProf.resample(depths);
      QVector<double> vp=vpProf.values();
      return surfaceWaveModel(depths ,vp, vsProf.values(), rhoProf.values());
    }
  }
}

/*!
  Reads profiles and build a resistivity layered model according to available profiles.
*/
Resistivity1DModel * DCReportBlock::readElectricModel()
{
  TRACE;
  Profile resProf;
  if(res()) resProf.readReport(*_s);
  QVector<double> depths;
  if(resProf.count()>0) {
    return new Resistivity1DModel(resProf);
  } else {
    return nullptr;
  }
}

/*!
  Compatibility with reports generated by Beta release from June 2006
*/
void DCReportBlock::writeBeta(ReportWriter * outReport, ReportReader * inReport)
{
  TRACE;
  int inVersion=inReport->userBlockVersion( "DISP" );
  if(inVersion<0) {
    return;
  }
  DCReportBlock dcBlock(inReport->stream());
  dcBlock.readProfiles(inVersion);
  // Tag and block version
  outReport->userBlockHeader("DISP", DCREPORTBLOCK_CURRENT_VERSION);
  QDataStream& sOut=outReport->stream();
  qint64 tableOffset=sOut.device()->pos();
  writeOffsetTable(sOut);
  // Save profiles
  for(int i=0;i<5;i++) {
    if(dcBlock.profile(i)) {
      Profile p;
      ReportWriter::setCurrentOffset(sOut, tableOffset + i*8);
      p.readReport(inReport->stream());
      p.writeReport(sOut);
    }
  }
  if(dcBlock.profile(5)) {  // Dispersion
    ReportWriter::setCurrentOffset(sOut, tableOffset+40);
    writeFactoryBeta(sOut, dcBlock, 4);
  }
  if(dcBlock.profile(6)) {  // Autocorr
    ReportWriter::setCurrentOffset(sOut, tableOffset+48);
    int nRings;
    dcBlock.stream() >> nRings;
    writeFactory(sOut, dcBlock, 3*nRings);
  }
  if(dcBlock.profile(7)) {  // Ellipticity
    ReportWriter::setCurrentOffset(sOut, tableOffset+56);
    writeFactory(sOut, dcBlock, 1);
  }
}

void DCReportBlock::writeFactoryBeta(QDataStream& s, DCReportBlock& dcBlock, int nStorages)
{
  TRACE;
  qint64 tableOffset=s.device()->pos();
  qint64 offset=0;
  for(int iS=0;iS<nStorages;iS++) {
    s << offset;
  }
  qint64 blockOffset=dcBlock.stream().device()->pos();
  ModalStorageReader readerRayleigh(dcBlock.stream());
  readerRayleigh.setBetaReleaseOffsets(dcBlock.stream(), true); // Read number of Rayleigh modes
  if(readerRayleigh.nModes()>0) { // is there any Rayleigh mode?
    ReportWriter::setCurrentOffset(s, tableOffset);
    s << readerRayleigh.nModes();
    for(int i=0; i<readerRayleigh.nModes(); i++) {
      if(readerRayleigh.seek(dcBlock.stream(), i)) {
        readerRayleigh.toStream(dcBlock.stream(), s);
      }
    }
  }
  tableOffset+=16; // skip Group Rayleigh
  // Back to beginning of block
  dcBlock.stream().device()->seek(blockOffset);
  ModalStorageReader readerLove(dcBlock.stream());
  readerLove.setBetaReleaseOffsets(dcBlock.stream(), false); // Read number of Love modes
  if(readerLove.nModes()>0) { // is there any Love mode?
    ReportWriter::setCurrentOffset(s, tableOffset);
    s << readerLove.nModes();
    for(int i=0; i<readerLove.nModes(); i++) {
      if(readerLove.seek(dcBlock.stream(), i)) {
        readerLove.toStream(dcBlock.stream(), s);
      }
    }
  }
}

/*!
  Compatibility with reports generated by Na_viewer
*/
void DCReportBlock::writeNaViewer(ReportWriter * outReport, CompatInversionReport * inReport)
{
  TRACE;
  // Tag and block version
  outReport->userBlockHeader("DISP", DCREPORTBLOCK_CURRENT_VERSION);
  QDataStream& sOut=outReport->stream();
  qint64 tableOffset=sOut.device()->pos();
  writeOffsetTable(sOut);
  // Save profiles
  Seismic1DModel * m=inReport->currentModel();
  ReportWriter::setCurrentOffset(sOut, tableOffset);
  m->vpProfile().writeReport(sOut);
  ReportWriter::setCurrentOffset(sOut, tableOffset+8);
  m->vsProfile().writeReport(sOut);
  ReportWriter::setCurrentOffset(sOut, tableOffset+16);
  m->rhoProfile().writeReport(sOut);
  if(inReport->currentDispersion()) {
    ReportWriter::setCurrentOffset(sOut, tableOffset+40);
    writeFactoryNaViewer(sOut, inReport->currentDispersion(), 4);
  }
  // Autocorr and ellipticities not implemented
}

void DCReportBlock::writeFactoryNaViewer(QDataStream& s, CompatMultiModalCurves * curves, int nStorages)
{
  TRACE;
  static const double inv2pi=0.5/M_PI;
  qint64 tableOffset=s.device()->pos();
  qint64 offset=0;
  for(int iS=0;iS<nStorages;iS++) {
    s << offset;
  }
  int nf=curves->omegasCount();
  // Rayleigh phase
  int nR=curves->rayleighModesCount();
  if(nR<0) nR=0;
  if(nR>0) {
    ReportWriter::setCurrentOffset(s, tableOffset);
    s << nR;
    for(int i=0; i<nR; i++) {
      s << nf;
      for(int j=0; j<nf; j++) {
        s << curves->omega(j)*inv2pi;
        s << curves->value(j, i);
      }
    }
  }
  tableOffset+=16; // skip Group Rayleigh
  // Love phase
  int nL=curves->modesCount()-nR;
  if(nL>0) {
    ReportWriter::setCurrentOffset(s, tableOffset);
    s << nL;
    for(int i=0; i<nL; i++) {
      s << nf;
      for(int j=0; j<nf; j++) {
        s << curves->omega(j)*inv2pi;
        s << curves->value(j, i+nR);
      }
    }
  }
}

} // namespace DinverDCCore
