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

#include <QGpCoreTools.h>

#include "CitySignal.h"
#include "SignalFile.h"
#include "GeoSignal.h"
#include "Comments.h"

namespace GeopsyCore {

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

/*!
  Description of constructor still missing
*/
CitySignal::CitySignal()
{
  TRACE;
  _frequency=0;
  _duration=0;
  _recorder=Unknown;
  _erased=false;
  _dataOffset=0;
  _currentBlock[BLOCK_SIZE]='\0';
  _fileIndex[3]='\0';
}

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

/*!
  \a version is used only to fill signal comments.
  It does not modify the way samples or header information is extracted.
  It switches automatically to CityShark 1 or 2 with DEB or DEB2 keyword
  found at the beginning of every signal.
*/
bool CitySignal::readHeader(QFile * stream, int version)
{
  TRACE;
  _softwareVersion=version;
  char * ptr;
  qint64 initialOffset=stream->pos();
  char timeBuf[15];
  timeBuf[14]='\0';

  // Signals always start and end with those tags (2 for Cityshark 2):
  //    DEB, DEB2, ERASE or ERASE2
  readBlock(stream);
  ptr=File::stripWhiteSpace(_currentBlock);
  if(strncmp(ptr, "DEB", 3)==0) {
    _erased=false;
    setRecorder(ptr+3);
  } else if(strncmp(ptr, "ERASED", 6)==0) {
    _erased=true;
    setRecorder(ptr+6);
  } else {
    // No more signal header found
    App::log(tr(" # Warning # No more file header found. Valid data occupy %1 bytes (%2 Mbytes)\n")
                     .arg(initialOffset).arg((double)initialOffset/(1024.0*1024.0)));
    return false;
  }

  // Channel number, serial number
  readBlock(stream);
  ptr=File::stripWhiteSpace(_currentBlock);
  _serialNum=atoi(getCouple(ptr));
  _channelNum=atoi(ptr);

  // FILE tag
  readBlock(stream);
  ptr=File::stripWhiteSpace(_currentBlock);
  if(strcmp(ptr, "FILE")!=0) {
    App::log(tr(" # Error # Cannot read keyword FILE, block corrupted, skipping it\n"));
    return false;
  }
  // Start time (MMJJHHmm)
  readBlock(stream);
  ptr=File::stripWhiteSpace(_currentBlock);
  strncpy(timeBuf, ptr, 8);
  // Start time (sszzz)
  readBlock(stream);
  ptr=File::stripWhiteSpace(_currentBlock);
  strncpy(timeBuf+8, ptr, 2);
  strncpy(timeBuf+10, ".", 1);
  strncpy(timeBuf+11, ptr+2, 3);
  App::freeze(true);  // Avoid message from time conversion
  _startTime.fromString(timeBuf, "MMddhhmmssz");
  App::freeze(false);
  setCurrentYear(_startTime);
  // File index
  readBlock(stream);
  ptr=File::stripWhiteSpace(_currentBlock);
  strncpy(_fileIndex, ptr, 3);

  // PARAM tag
  readBlock(stream);
  ptr=File::stripWhiteSpace(_currentBlock);
  if(strcmp(ptr, "PARAM")!=0) {
    App::log(tr(" # Error # Cannot read keyword PARAM, block corrupted, skipping it\n") );
    return false;
  }
  // Frequency, duration
  readBlock(stream);
  ptr=File::stripWhiteSpace(_currentBlock);
  _duration=atoi(getCouple(ptr));
  _frequency=atoi(ptr);
  _nSamples=_frequency*_duration*60;
  if(_nSamples <= 0) {
    App::log(tr(" # Error # Number of samples is null or negative, block corrupted, skipping it\n"));
    return false;
  }
  // Gain
  readBlock(stream);
  ptr=File::stripWhiteSpace(_currentBlock);
  _gain=atoi(ptr);
  // Saturation
  readBlock(stream);
  ptr=File::stripWhiteSpace(_currentBlock);
  _saturation=atof(ptr);
  // Maximum allowed amplitude
  readBlock(stream);
  ptr=File::stripWhiteSpace(_currentBlock);
  _maxAllowedAmpl=atoi(ptr);
  // Maximum reached amplitude
  readBlock(stream);
  ptr=File::stripWhiteSpace(_currentBlock);
  _maxReachedAmpl=atoi(ptr);
  // Test if all blocks have been read correctly
  if((stream->pos()-initialOffset)!=12*BLOCK_SIZE) {
    if(stream->atEnd()) {
      App::log(tr(" # Error # End of file reached (CityShark 1 infos), partial block skipped\n"));
      return false;
    } else {
      App::log(tr(" # Error # Block not read correctly due to an unknown error (CityShark 1 infos)\n"));
      return false;
    }
  }
  if(_recorder==CityShark2) {
    // Latitude, Longitude (degrees)
    readBlock(stream);
    ptr=File::stripWhiteSpace(_currentBlock);
    _longitudeDeg=atoi(getCouple( ptr) );
    _latitudeDeg=atoi(ptr);
    // Latitude (minutes)
    readBlock(stream);
    ptr=File::stripWhiteSpace(_currentBlock);
    _latitudeMin=atof(ptr);
    // Longitude (minutes)
    readBlock(stream);
    ptr=File::stripWhiteSpace(_currentBlock);
    _longitudeMin=atof(ptr);
    // Number of satelites, altitude
    readBlock(stream);
    ptr=File::stripWhiteSpace(_currentBlock);
    _altitude=atof(getCouple( ptr) );
    _satNum=atoi(ptr);
    if(_softwareVersion >= 413) { // since station software version 0413
      // Calculate end time from time elapsed from start in milliseconds
      readBlock(stream);
      _endTime=_startTime;
      _endTime.addSeconds(atoi( File::stripWhiteSpace(_currentBlock) )*0.001);
      readBlock(stream); // useless block
    } else {
      // End time (MMJJHHmm)
      readBlock(stream);
      ptr=File::stripWhiteSpace(_currentBlock);
      strncpy(timeBuf, ptr, 8);
      // End time (ss.mmm)
      readBlock(stream);
      ptr=File::stripWhiteSpace(_currentBlock);
      strncpy(timeBuf + 8, ptr, 2);
      strncpy(timeBuf + 10, ".", 1);
      strncpy(timeBuf + 11, ptr+2, 3);
      App::freeze(true);
      _endTime.fromString(timeBuf, "MMddhhmmssz");
      App::freeze(false);
      setCurrentYear(_endTime);
    }
    // Test if all blocks have been read correctly
    if(( stream->pos() - initialOffset)!=18 * BLOCK_SIZE) {
      if(stream->atEnd()) {
        App::log(tr(" # Error # End of file reached (CityShark 2 infos), partial block skipped\n"));
        return false;
      } else {
        App::log(tr(" # Error # Block not read correctly due to an unknown error (CityShark 2 infos)\n"));
        return false;
      }
    }
  }
  initialOffset=stream->pos();
  // DATA
  readBlock(stream);
  ptr=File::stripWhiteSpace(_currentBlock);
  if(strcmp(ptr, "DATA")!=0) {
    App::log(tr(" # Error # Cannot read keyword DATA, block corrupted, skipping it\n"));
    return false;
  }
  _dataOffset=stream->pos();
  // Skip data block
  int dataLength=_channelNum * _nSamples * 3;
  stream->seek(stream->pos() + dataLength);
  // FIN
  readBlock(stream);
  ptr=File::stripWhiteSpace(_currentBlock);
  if(strcmp( ptr, "FIN" )!=0) {
    App::log(tr(" # Warning # Cannot read keyword FIN for file %1, end of trace may be corrupted\n").arg(fileName()));
  }
  // Test if all blocks have been read correctly
  if(( stream->pos() - initialOffset)!=dataLength + BLOCK_SIZE * 2) {
    if(stream->atEnd()) {
      App::log(tr(" ### End of file reached (data part), partial block skipped\n"));
      return false;
    } else {
      App::log(tr(" ### Block not read correctly due to an unknown error (data part)\n"));
      return false;
    }
  }
  App::log(tr("     File %1 ok\n").arg(fileName()));
  return true;
}

bool CitySignal::erase(QFile * stream)
{
  TRACE;
  if(_erased)
    return true;
  switch (_recorder) {
  case CityShark1:
    stream->seek(_dataOffset - 13 * BLOCK_SIZE);
    if(stream->write( "ERASED   ", BLOCK_SIZE)!=BLOCK_SIZE) return false;
    break;
  case CityShark2:
    stream->seek(_dataOffset - 19 * BLOCK_SIZE);
    if(stream->write( "ERASED2  ", BLOCK_SIZE)!=BLOCK_SIZE) return false;;
    break;
  default:
    return false;
  }
  _erased=true;
  return true;
}

void CitySignal::setCurrentYear(DateTime& dt)
{
  TRACE;
  QDate d=QDate::currentDate();
  if(dt.date().month()>d.month()) {
    App::log(tr(" # Warning # recorded in the future, setting date to last year.\n") );
    d.setDate(d.year()-1, dt.date().month(), dt.date().day());
  } else {
    d.setDate(d.year(), dt.date().month(), dt.date().day());
  }
  dt.setDate(d);
}

/*!
  Get conversion factor from frequency and gain (count per volt)
*/
double CitySignal::conversionFactor(int frequency, int gain)
{
  /* Conversion factors initialy transcripted from Frechet
  switch(_frequency) {
  case 50:
    resp=209715; break; // 1048575 /5
  case 100:
    resp=52428.6; break; // 262143 /5
  case 125:
    resp=26214.2; break; // 131071 /5
  case 200:
    resp=13107; break; // 65535 /5
  case 250:
    resp=6553.4; break; // 32767 /5
  default:
    resp=0.0; break;
  }*/
  // Conversion factors from new version: 9/4/2004
  // First resp is set to the amplitude range for counts
  // Check that it is correct, there might be 1 to be subtracted (e.g. from -8192 to 8191 or -8191 to 8191?)
  double resp;
  switch (frequency) {
  case 10:
  case 20:
  case 25:
  case 40:
  case 50:
    resp=1048576.0;
    break;
  case 60:
    resp=524288.0;
    break;
  case 75:
  case 80:
  case 100:
    resp=262144.0;
    break;
  case 125:
  case 150:
    resp=131072.0;
    break;
  case 200:
    resp=65536.0;
    break;
  case 250:
    resp=32768.0;
    break;
  case 300:
    resp=16384.0;
    break;
  case 400:
    resp=8192.0;
    break;
  case 500:
  case 600:
    resp=4096.0;
    break;
  case 750:
    resp=2048.0;
    break;
  case 1000:
    resp=1024.0;
    break;
  default:
    resp=1.0;
    break;
  }
  // Then divided by the peak to peak voltage difference (currently 5 volts)
  resp/=5.0;
  return resp*gain;
}

/*!
  Read flash card accessed with \a stream
*/
bool CitySignal::loadSignals(SignalDatabase * db, QFile * stream) const
{
  TRACE;
  QString originalFileName=fileName();
  // Read all channels in one buffer
  int n=_nSamples*_channelNum*3;
  char * data=new char [n];
  stream->seek(_dataOffset);
  // Read buffer with 20 blocks (to display advance)
  int block20Size=n/20;
  int blockRemaining=n%20;
  GeopsyCoreEngine::instance()->setProgressMaximum(db, 20);
  for(int i=0; i<20; i++) {
    GeopsyCoreEngine::instance()->setProgressValue(db, i);
    stream->read(data+i*block20Size, block20Size);
  }
  if(blockRemaining>0) {
    stream->read(data+20*block20Size, blockRemaining);
  }
  GeopsyCoreEngine::instance()->setProgressValue(db, 20);

  GeopsyCoreEngine::instance()->showMessage(db, tr("Loading file %1").arg(originalFileName));
  double countPerVolt=conversionFactor(_frequency, _gain);

  // Create a new temporary file
  SignalFile * newFile=new SignalFile(db, originalFileName, SignalFileFormat::Temporary);

  GeopsyCoreEngine::instance()->setProgressMaximum(db, _channelNum-1);
  for(int iSig=0; iSig<_channelNum; iSig++) {
    Signal * newSignal=new Signal(db);

    newSignal->setFile(newFile);
    newSignal->setNumberInFile(iSig);
    newSignal->setStartTime(_startTime);
    newSignal->setSamplingFrequency(_frequency);
    newSignal->setType(Signal::Waveform);
    int recNum=iSig/3+1;
    if(_channelNum>3)
      SignalFile::setSignalName(newSignal, originalFileName, QString("_%1").arg(recNum), recNum, originalFileName);
    else
      SignalFile::setSignalName(newSignal, originalFileName, "", 1, originalFileName);
    if(iSig % 3==0)
      newSignal->setComponent(Signal::Vertical);
    else if(iSig % 3==1)
      newSignal->setComponent(Signal::North);
    else if(iSig % 3==2)
      newSignal->setComponent(Signal::East);
    newSignal->setNSamples(_nSamples);
    newSignal->setCountPerVolt(countPerVolt);
    QString cmt;
    cmt+="Station serial number: "+QString::number(_serialNum)+"\n";
    cmt+="Station software version: "+QString::number(_softwareVersion)+"\n";
    cmt+="Starting date: "+_startTime.toString("dd.MM.yyyy")+"\n";
    cmt+="Starting time: "+_startTime.toString("hh:mm:ssz", 3)+"\n";
    cmt+="Ending date: "+_endTime.toString("dd.MM.yyyy")+"\n";
    cmt+="Ending time: "+_endTime.toString("hh:mm:ssz", 3)+"\n";
    cmt+="Recording duration: "+QString::number(_duration)+" mn\n";
    cmt+="Latitude : "+QString::number(_latitudeDeg)+" "+QString::number(_latitudeMin)+" N\n";
    cmt+="Longitude: "+QString::number(_longitudeDeg)+" "+QString::number(_longitudeMin)+" E\n";
    cmt+="Altitude : "+QString::number(_altitude)+" m\n";
    cmt+="No. satellites: "+QString::number(_satNum)+"\n";
    cmt+="Gain: "+QString::number(_gain)+"\n";
    cmt+="Clipped samples: "+QString::number(_saturation, 'f', 2)+"%\n";
    // Max allowed amplitude looks like the max positive amplitude, the absolute value is +1
    cmt+="Maximum amplitude: "+QString::number(_maxReachedAmpl)+" / "+QString::number(_maxAllowedAmpl+1)+"\n";
    // Duration calculated between start and end time does not account for the last sample duration
    cmt+="Time drift: "+QString::number(_startTime.secondsTo(_endTime)-newSignal->duration()+newSignal->samplingPeriod())+" s";
    newSignal->setMetaData(Comments(cmt));
    // Extract samples for this signal from buffer
    LOCK_SAMPLES(double, newSamples, newSignal)
      char * ptr=data+3*iSig;
      int di=_channelNum*3;
      double unitPerCount=newSignal->unitPerCount();
      for(int i=0; i<_nSamples; i++) {
        newSamples[i]=getSample(ptr+i*di)*unitPerCount;
      }
    UNLOCK_SAMPLES(newSignal)
    GeopsyCoreEngine::instance()->setProgressValue(db, iSig);
  }
  delete [] data;
  return true;
}


} // namespace GeopsyCore
