/***************************************************************************
**
**  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: 2003-11-10
**  Copyright: 2003-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <QGpCoreTools.h>
#include <mseed.h>

#include "SignalDatabase.h"
#include "SignalFile.h"
#include "SubSignalPool.h"
#include "GeopsyCoreEngine.h"
#include "GeopsyPreferences.h"
#include "GuralpSignal.h"
#include "MiniSeedVolume.h"
#include "MiniSeedTrace.h"
#include "GeopsySignalHeader.h"
#include "WaveHeader.h"
#include "SEGYTraceHeader.h"
#include "PasscalSegYHeader.h"
#include "SEGDHeader.h"
#include "SACHeader.h"
#include "SyscomSVmrxHeader.h"
#include "SyscomXmrHeader.h"
#include "AsciiSignalFormat.h"
#include "TimePick.h"
#include "StackCount.h"
#include "OriginalFileName.h"
#include "Comments.h"
#include "SharkHeader.h"

namespace GeopsyCore {

const QString SignalFile::xmlSignalFileTag="File";

SignalFile::SignalFile(SignalDatabase * db, QString fileName, const SignalFileFormat& format)
{
  TRACE;
  _name=fileName;
  _format=format;
  _crc32=0;
  _size=0;
  _isOriginalFile=true;
  _pathModified=false;
  _db=db;
  db->addFile(this);
}

/*!
  Construct a new file from an existing one \a f, store it in directory \a d.
  The format of the created file will always be of type GeopsySignal.
  The file name is automatically indexed to avoid overwriting existing files.
  isOriginalFile() is always set to false.
  \a sigs are added to the list of signal to load
*/
SignalFile::SignalFile(SignalDatabase * db, SignalFile& f, QDir& d)
{
  TRACE;
  _name=File::uniqueName (f.shortName(), d);
  _format=SignalFileFormat::GeopsySignal;
  _isOriginalFile=false;
  _pathModified=false;
  _crc32=0;
  _size=0;
  _db=db;
  db->addFile(this);
}

SignalFile::~SignalFile()
{
  TRACE;
}

SignalFile& SignalFile::operator=(const SignalFile& p)
{
  TRACE;
  _name=p._name;
  _format=p._format;
  _isOriginalFile=p._isOriginalFile;
  return *this;
}

QString SignalFile::name(int len) const
{
  TRACE;
  QDir d;
  QChar sep=d.separator();
  QString str;
  int compLen=_name.length();
  int toDel=compLen - len;
  if(toDel <= 0) {
    str=_name;
    return str;
  }
  toDel += 3;
  int posEnd=compLen - 1;
  while(posEnd && _name[ posEnd ]!=sep)
    posEnd--;
  if(compLen - posEnd + 3 >= len) {
    str="..." + _name.right(len - 3);
    return str;
  }
  int half=(len - (compLen - posEnd) - 3)/2;
  str=_name.left(half) + "..." + _name.right(compLen - posEnd + half);
  return str;
}

QString SignalFile::shortName() const
{
  TRACE;
  QFileInfo fi(_name);
  return fi.fileName();
}

bool SignalFile::load()
{
  TRACE;
  if(!setContentKey()) {
    return false;
  }
  bool ret;
  SAFE_UNINITIALIZED(ret,0);
  switch (_format.id()) {
  case SignalFileFormat::GeopsySignal:
    ret=loadGeopsySignal();
    break;
  case SignalFileFormat::Seg2:
    ret=loadSeg2();
    break;
  case SignalFileFormat::SegD:
    ret=loadSegD();
    break;
  case SignalFileFormat::SuLittleEndian:
    ret=loadSu(QDataStream::LittleEndian);
    break;
  case SignalFileFormat::SuBigEndian:
    ret=loadSu(QDataStream::BigEndian);
    break;
  case SignalFileFormat::RD3:
    ret=loadRD3();
    break;
  case SignalFileFormat::SacLittleEndian:
    ret=loadSac(QDataStream::LittleEndian);
    break;
  case SignalFileFormat::SacBigEndian:
    ret=loadSac(QDataStream::BigEndian);
    break;
  case SignalFileFormat::Radan:
    ret=loadRadan();
    break;
  case SignalFileFormat::Gse2:
    ret=loadGse2(SignalFileFormat::Single);
    break;
  case SignalFileFormat::MultiGse2:
    ret=loadGse2(SignalFileFormat::Multi);
    break;
  case SignalFileFormat::CityShark2:
    ret=loadShark(SignalFileFormat::CityShark2);
    break;
  case SignalFileFormat::MiniShark:
    ret=loadShark(SignalFileFormat::MiniShark);
    break;
  case SignalFileFormat::Saf:
    ret=loadSaf();
    break;
  case SignalFileFormat::Ascii:
    ret=loadAscii();
    break;
  case SignalFileFormat::AsciiOneColumn:
    ret=loadAsciiOneColumn();
    break;
  case SignalFileFormat::Sismalp:
    ret=loadSismalp();
    break;
  case SignalFileFormat::Wav:
    ret=loadWav();
    break;
  case SignalFileFormat::SegYLittleEndian:
    ret=loadSegY(QDataStream::LittleEndian);
    break;
  case SignalFileFormat::SegYBigEndian:
    ret=loadSegY(QDataStream::BigEndian);
    break;
  case SignalFileFormat::PasscalSegYLittleEndian:
    ret=loadPasscalSegY(QDataStream::LittleEndian);
    break;
  case SignalFileFormat::PasscalSegYBigEndian:
    ret=loadPasscalSegY(QDataStream::BigEndian);
    break;
  case SignalFileFormat::SyscomXmr:
    ret=loadSyscomXmr();
    break;
  case SignalFileFormat::SyscomSmr:
    ret=loadSyscomSmr();
    break;
  case SignalFileFormat::SyscomVmrx:
    ret=loadSyscomVmrx();
    break;
  case SignalFileFormat::GuralpGcf:
    ret=loadGuralpGcf();
    break;
  case SignalFileFormat::MiniSeed:
    ret=loadMiniSeed();
    break;
  case SignalFileFormat::Custom:
    ret=_format.customFormat()->load(this);
    break;
  case SignalFileFormat::Fourier:
    ret=loadFourier();
    break;
  case SignalFileFormat::Tomo:
  case SignalFileFormat::Unknown:
  case SignalFileFormat::Temporary:
  case SignalFileFormat::FormatCount:
    ret=false;
    break;
  }
  if(!ret) {
    App::log(tr(" ### Error while loading file %1\n").arg(_name) );
  }
  return ret;
}

/*!
  Save the file to a permanent storage (in directory \a d) and convert it to an original file.
  This function is called by SignalDatabase::save() for storing temporary signals.

  See SubSignalPool::saveGeopsySignal() for file fomat.
*/
bool SignalFile::save(const QDir& d)
{
  TRACE;
  _name=File::uniqueName(_name, d);
  SubSignalPool subPool;
  subPool.addFile(this);
  if(subPool.saveGeopsySignal(_name) ) {
    // Set members as if it was normally loaded
    _format=SignalFileFormat::GeopsySignal;
    _isOriginalFile=true;
    int nSignals=subPool.count();
    int offset=GEOPSYSIGNAL_HEADERSIZE+nSignals*4;
    for(int i=0;i < nSignals;i++ ) {
      Signal * sig=subPool.at(i);
      sig->setOffsetInFile(offset);
      offset += sizeof(double) * sig->nSamples();
    }
    return true;
  } else {
    Message::warning(MSG_ID, tr( "Saving Geopsy signals ..." ),
                          tr( "Cannot write to file: %1\n"
                              "(Permission, disk space,...)" ). arg(_name), Message::ok(), true);
    return false;
  }
}

/*!
  See SubSignalPool::saveGeopsySignal() for file fomat.

  File header version 1, still readable, contained also a table of conversion factor (???).
  With version 2, there is more control on the correctness of the integer and double float internal representation.

*/
bool SignalFile::loadGeopsySignal()
{
  TRACE;
  QFile f(_name);
  if( !f.open(QIODevice::ReadOnly) ) {
    App::log(tr("Loading Geopsy Signal: unable to open file %1\n").arg(_name));
    return false;
  }
  // Load header
  GeopsySignalHeader h;
  f.read(h.raw, GEOPSYSIGNAL_HEADERSIZE);
  if(!h.isValid()) {
    App::log(tr("Loading Geopsy Signal: wrong format for file %1\n").arg(_name));
    return false;
  }
  int * nSamples=new int[h.field.nSignals];
  switch(h.field.version) {
  case 1: // Compatibility: rewind to the position of a version header 1
    f.seek(10+3*4);
    break;
  case 2: // Compatibility: rewind to the position of a version header 2
    f.seek(16+8+4*4);
    break;
  default:
    break;
  }
  f.read((char *)nSamples, h.field.nSignals*sizeof(qint32));
  f.close();
  // To be still loadable with previous releases, offset is "faked" by traceHeaderSize
  qint64 offset=h.field.offset-h.field.traceHeaderSize;
  for(int i=0; i<h.field.nSignals; i++) {
    Signal * newSignal=new Signal(_db);
    newSignal->setFile(this);
    newSignal->setNumberInFile(i);
    newSignal->setNSamples(nSamples[i]);
    newSignal->setOffsetInFile(offset);
    offset+=sizeof(double)*nSamples[i]+h.field.traceHeaderSize;
  }
  delete [] nSamples;
  return true;
}

/*!
  See SubSignalPool::saveGeopsySignal() for file fomat.

  Contrary to loadGeopsySignal() no new signal are created. Read signals are just
  affected to signals of subPool.

*/
bool SignalFile::loadGeopsySignal(const SubSignalPool& subPool)
{
  TRACE;
  if(!setContentKey()) {
    return false;
  }
  QFile f(_name);
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("Loading Geopsy Signal: unable to open file %1\n").arg(_name) );
    return false;
  }
  // Load header
  GeopsySignalHeader h;
  f.read(h.raw, GEOPSYSIGNAL_HEADERSIZE);
  if(!h.isValid()) {
    App::log(tr("Loading Geopsy Signal: wrong format for file %1\n").arg(_name));
    return false;
  }
  int * nSamples=new int[h.field.nSignals];
  switch(h.field.version) {
  case 1: // Compatibility: rewind to the position of a version header 1
    f.seek(10+3*4);
    break;
  case 2: // Compatibility: rewind to the position of a version header 2
    f.seek(16+8+4*4);
    break;
  default:
    break;
  }
  f.read((char *)nSamples, h.field.nSignals*sizeof(qint32));
  f.close();
  if(h.field.nSignals!=subPool.count()) {
    App::log(tr("Loading Geopsy Signal: number of signals does not match.\n").arg(_name) );
    return false;
  }
  for(int i=0; i<h.field.nSignals; i++) {
    Signal * sig=subPool.at(i);
    if(nSamples[i]!=sig->nSamples()) {
      App::log(tr("Loading Geopsy Signal: number of samples does not match for signal id %1.\n").arg(sig->id()) );
      return false;
    }
  }
  // To be still loadable with previous releases, offset is "faked" by traceHeaderSize
  qint64 offset=h.field.offset-h.field.traceHeaderSize;
  for(int i=0; i<h.field.nSignals; i++) {
    Signal * sig=subPool.at(i);
    if(!sig->file()) {
      sig->addReference(); // Temporary signals have one reference difference
                           // with usual signals to provide automatic deletion.
                           // With this load, it becomes a usual signal.
    }
    sig->setFile(this);
    sig->setNumberInFile(i);
    sig->setOffsetInFile(offset);
    offset+=sizeof(double)*nSamples[i]+h.field.traceHeaderSize;
  }
  delete [] nSamples;
  return true;
}

bool SignalFile::loadSeg2()
{
  TRACE;
  int i;
  QString str;
  QVector<qint32> offsets;

  QFile f(_name);
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("Loading SEG-2: unable to open file\n"));
    return false;
  }
  QFileInfo fi(_name);
  int maxOffset=static_cast<int>(fi.size());
  int bufLen=256;
  char * buf=new char[bufLen];
  // Header of 32 bytes:
  // 2 bytes  file format code (3A55 for SFF_SEG2)
  // 4 bytes  ????
  // 1 byte   number of channels
  // 25 bytes ???? (set to 0)
  if(f.read(buf, 32)!=32) {
    App::log(tr("Loading SEG2 : error reading first 32 bytes\n"));
    return false;
  }
  qint16 id=*reinterpret_cast<qint16 *>(buf);
  if(id!=0x3A55) {
    App::log(tr("Loading SEG2 : Wrong file format (id=0x%1)\n").arg(id, 4, 16, QChar('0')));
    delete [] buf;
    return false;
  }
  int nChannels=*reinterpret_cast<qint16 *>(buf+6);
  if(nChannels<0) {
    App::log(tr("Loading SEG2 : negative number of channels\n"));
    delete [] buf;
    return false;
  }
  int pos;
  offsets.resize(nChannels);
  // OffsetTable NumChannel*4bytes
  for(i=0; i<nChannels; i++) {
    f.read(reinterpret_cast<char *>(&pos), 4);
    if(pos>maxOffset) {
      App::log(tr("Loading SEG2 : corrupted file, signal offset %1 > file length\n").arg(i));
      delete [] buf;
      return false;
    }
    offsets[i]=pos;
    //printf("Channel %i, offset=%x\n",i , pos);
  }

  DateTime acquisitionTime;
  double conversionFactor=1.0;
  static const QRegExp sep("[ \\t]");
  // Client and instrument specification (between current pos and first offset)
  while(f.pos()<offsets[0]) {
    f.read(buf, 2);
    // buf contains the lenght of the whole string (2 bytes for the lenght, the string itself terminated by a null char)
    int l=*reinterpret_cast<qint16 *>(buf)-2;
    if(l<0) {
      // If there's string in this section, it's impossible that l is less than 0
      // At the end of the strings, it's possible to have some 0. In this case this function is mandatory
      //pos=offsets[ 0 ];
      //fseek(f, pos, SEEK_SET);
      continue;
    }
    if(l>bufLen) {
      delete [] buf;
      bufLen=l;
      buf=new char[bufLen];
    }
    f.read(buf, l);
    buf[l-1]=0; // Make sure that it's a null terminated string
    str=buf;
    APP_LOG(2, "file header: "+str+"\n")
    QString key=str.section(sep, 0, 0, QString::SectionSkipEmpty);
    if(key=="INSTRUMENT") {
      if(str.section(" ", 1, 1)=="Bison") {
        conversionFactor*=3.308e-6;
      }
    } else if(key=="ACQUISITION_DATE") {
      App::freeze(true);
      QString val=str.section(sep, 1, 1, QString::SectionSkipEmpty);
      DateTime d;
#define SEG2_DATE_FORMAT_COUNT 5
      const char * formats[]={"Cd/MMM/yyyy", "Cd/M/yyyy", "Cd/M/yy", "CM/d/yyyy", "CM/d/yy"};
      int i;
      for(i=0; i<SEG2_DATE_FORMAT_COUNT; i++) {
        d.fromString(val, formats[i]);
        if(d.isValid()) {
          acquisitionTime.setDate(d.date());
          break;
        }
      }
      if(i==SEG2_DATE_FORMAT_COUNT) {
        App::log(tr("ACQUISITION_DATE='%1' is invalid, send a request to fix it.\n").arg(val));
      }
      App::freeze(false);
    } else if(key=="ACQUISITION_TIME") {
      QString val=str.section(sep, 1, 1, QString::SectionSkipEmpty);
      DateTime t;
      if(t.fromString(val, "h:m:sz")) {
        acquisitionTime.setTime(t.time());
      } else {
        App::log(tr("ACQUISITION_TIME='%1' is invalid\n").arg(val));
      }
    } else {
      App::log(1, tr("Unprocessed SEG2 keyword in file %1: %2=\"%3\"\n")
                       .arg(shortName()).arg(key).arg(str.section(sep, 1, -1, QString::SectionSkipEmpty)));
    }
  }
  if(!acquisitionTime.isValid()) { // No acquisition time nor date found in header, back to file modification date
    acquisitionTime=fi.lastModified();
    QList<const SeismicEvent *> events=_db->seismicEvents()->eventsAt(acquisitionTime);
    while(!events.isEmpty()) {
      acquisitionTime.addSeconds(60.0);
      events=_db->seismicEvents()->eventsAt(acquisitionTime);
    }
  }
  SeismicEvent e;
  e.setType(SeismicEvent::Hammer);
  e.setForce(Point(0.0, 0.0, -1.0));
  e.setTime(acquisitionTime);

  // File Ptr is normally equal to offsets[0], the next statement make it sure.
  // Read all channels according to their offsets in the offsets table
  Signal * lastSignal=nullptr;
  for(i=0; i<nChannels; i++) {
    Signal * newSignal=new Signal(_db);
    newSignal->setFile(this);
    newSignal->setType(Signal::Waveform);

    pos=offsets[i];
    f.seek(pos);
    f.read(buf, 32);
    qint16 id=*reinterpret_cast<qint16 *>(buf);
    // Header of signal: 32 bytes
    // 2 bytes should contain the flag 0x4422
    // 2 bytes (short) blocksize=header size in bytes (including flag)
    // 4 bytes (long) ????
    // 4 bytes (long) number of samples
    // 1 bytes (long) format (1 to 5)
    if(id!=0x4422) {
      App::log(tr("Loading SEG2 : Invalid signal (loading header ID=0x%1)\n").arg(id, 4, 16, QChar('0')));
      delete [] buf;
      return false;
    }
    newSignal->setOffsetInFile(offsets[i]);
    newSignal->setNSamples(*reinterpret_cast<qint32 *>(buf+8));
    int format=*reinterpret_cast<qint32 *>(buf+12);
    int nsamp2=*reinterpret_cast<qint32 *>(buf+4);
    switch (format) {
    case 1:
      nsamp2/=2;
      break;
    case 2:
      nsamp2/=4;
      break;
    case 3:
      nsamp2*=2;
      nsamp2/=5;
      break;
    case 4:
      nsamp2/=4;
      break;
    case 5:
      nsamp2/=8;
    }
    if(newSignal->nSamples()>nsamp2) {
      newSignal->setNSamples(nsamp2);
    }
    newSignal->setNumberInFile(i);
    newSignal->setCountPerVolt(1.0/conversionFactor);
    // set default name
    setSignalName(newSignal, shortName(), "", i + 1, shortName());
    int blocksize=*reinterpret_cast<qint16 *>(buf+2);
    int sizeRead=32;
    bool ok;
    // Strings that contain the delay, the sample interval,...
    while(sizeRead<blocksize) {
      f.read(buf, 2);
      int l=*reinterpret_cast<qint16 *>(buf)-2;
      if(l<0) {
        if(sizeRead==32 && lastSignal) {
          /* Null block header for this signal which is not the first one
             This has been observed in signals exported from segread. */
          newSignal->setStartTime(lastSignal->startTime());
          newSignal->setSamplingPeriod(lastSignal->samplingPeriod());
          newSignal->setCountPerVolt(lastSignal->countPerVolt());
          newSignal->setReceiver(lastSignal->receiver());
          setSignalName(newSignal, lastSignal->name(), "", i + 1, shortName());
        }
        sizeRead=blocksize;
        continue;
      }
      if(l>bufLen) {
        delete [] buf;
        bufLen=l;
        buf=new char[bufLen];
      }
      f.read(buf, l);
      sizeRead += l+2;
      buf[l-1]=0;
      str=buf;
      QString key=str.section(sep, 0, 0, QString::SectionSkipEmpty);
      APP_LOG(2, "signal header: "+str+"\n")
      if(key=="DELAY") {
        double dt=str.section(sep, 1, 1, QString::SectionSkipEmpty).toDouble();
        newSignal->setStartTime(e.time().shifted(dt));
      } else if(key=="SAMPLE_INTERVAL") {
        QString s=str.section(sep, 1, 1, QString::SectionSkipEmpty);
        if(s.count(",")==1 && !s.contains(".")) {
          s.replace(",", ".");
        }
        newSignal->setSamplingPeriod(s.toDouble());
      } else if(key=="RECEIVER_LOCATION") {
        double x=str.section(sep, 1, 1, QString::SectionSkipEmpty).toDouble();
        double y=str.section(sep, 2, 2, QString::SectionSkipEmpty).toDouble();
        double z=str.section(sep, 3, 3, QString::SectionSkipEmpty).toDouble();
        newSignal->setReceiver(Point(x, y, z));
      } else if(key=="SOURCE_LOCATION") {
        e.setPosition(Point(str.section(sep, 1, 1, QString::SectionSkipEmpty).toDouble(),
                            str.section(sep, 2, 2, QString::SectionSkipEmpty).toDouble(),
                            str.section(sep, 3, 3, QString::SectionSkipEmpty).toDouble()));
        _db->seismicEvents()->add(e);
      } else if(key=="ARRIVAL_TIME") {  // Sardine extension to SEG2
        TimePick& tp=newSignal->beginModifyMetaData<TimePick>();
        double p=str.section(sep, 2, 2, QString::SectionSkipEmpty).toDouble();
        tp.setValue(str.section(sep, 1, 1, QString::SectionSkipEmpty), e.time().shifted(p));
        newSignal->endModifyMetaData(tp);
      } else if(key=="NAME") {
        setSignalName(newSignal, str.section( "\n", 1, 1, QString::SectionSkipEmpty), "", i + 1, shortName());
      } else if(key=="DESCALING_FACTOR") {
        double c=str.section(sep, 1, 1, QString::SectionSkipEmpty).toDouble(&ok);
        // DESCALING_FACTOR does not include STACK and converts to millivolts => factor 1e3
        if(ok && c>0.0) {
          newSignal->setCountPerVolt(1e3*newSignal->countPerVolt()/c);
        }
      } else if(key=="STACK") {
        double c=str.section(sep, 1, 1, QString::SectionSkipEmpty).toInt(&ok);
        if(ok && c>0) {
          newSignal->setCountPerVolt(newSignal->countPerVolt()*c);
          newSignal->setMetaData(StackCount(c));
        }
      } else if(key=="RAW_RECORD") {
        QString c=str.section(sep, 1, 1, QString::SectionSkipEmpty);
        newSignal->setMetaData(OriginalFileName(c));
      } else if(key=="CHANNEL_NUMBER") {
        int ch=str.section(sep, 1, 1, QString::SectionSkipEmpty).toInt(&ok);
        if(ok && ch!=i+1) {
          App::log(tr("CHANNEL_NUMBER keyword in file %1 (sig %2) not corresponding to index in file (%3 instead of %4)\n")
            .arg(shortName()).arg(i).arg(str).arg(i+1));
        }
      } else {
        /* Other encountered fields:
          ALIAS_FILTER              1666.66 0
          AMPLITUDE_RECOVERY        NONE
          DIGITAL_HIGH_CUT_FILTER   0 0
          DIGITAL_LOW_CUT_FILTER    0 0
          LINE_ID                   0
          LOW_CUT_FILTER            0 0
          NOTCH_FREQUENCY           0
          RAW_RECORD                D:\Bastille\1039.dat
          SKEW                      -0.000125748
          NOTE                      Free text with multiple lines
          FIXED_GAIN                1
          SHOT_SEQUENCE_NUMBER      101
          TRACE_TYPE                SEISMIC_DATA
        */
        App::log(3, tr("Unprocessed SEG2 keyword in file %1 (sig %2): %3=\"%4\"\n")
                          .arg(shortName()).arg(i).arg(key).arg(str.section(sep, 1, -1, QString::SectionSkipEmpty)));
      }
    }
    if(newSignal->name().isEmpty()) setSignalName(newSignal, "", "", i + 1, shortName());
    lastSignal=newSignal;
  }
  delete [] buf;
  return true;
}

/*!

*/
bool SignalFile::loadSegD()
{
  TRACE;
  QString str;
  QVector<int> offsets;

  QFile f(_name);
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("Loading SEG-D: unable to open file\n") );
    return false;
  }
  QFileInfo fi(_name);
  SEGDHeader h;
  return false;
  // Not yet implemented
}

bool SignalFile::loadWav()
{
  TRACE;
  static const QString title(tr("Loading WAVE PCM soundfile:"));
  QFile f(_name);
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("%1 unable to open file\n").arg(title) );
    return false;
  }
  // Load Canonical header of 44 bytes
  WaveHeader wavinfo;
  f.read(reinterpret_cast<char *>(&wavinfo), sizeof(WaveHeader));
  // Check canonical flags
  if(strncmp( wavinfo.chunkID, "RIFF", 4)!=0) {
    App::log(tr("%1 bad ChunkID at offset 0 (should be 'RIFF').\n").arg(title) );
    return false;
  }
  if(strncmp( wavinfo.format, "WAVE", 4)!=0) {
    App::log(tr("%1 bad Format at offset 8 (should be 'WAVE').\n").arg(title) );
    return false;
  }
  if(strncmp( wavinfo.subchunk1ID, "fmt ", 4)!=0) {
    App::log(tr("%1 bad Subchunk1ID at offset 12 (should be 'fmt ').\n").arg(title) );
    return false;
  }
  if(wavinfo.subchunk1Size!=16) {
    App::log(tr("%1 bad Subchunk1Size at offset 16 (should be 16).\n").arg(title) );
    return false;
  }
  if(wavinfo.audioFormat!=1) {
    App::log(tr("%1 bad AudioFormat at offset 20 (PCM should be 1, "
                "no compression is currently supported)\n").arg(title));
    return false;
  }
  short curBlockAlign=wavinfo.numChannels * wavinfo.bitsPerSample/8;
  if(wavinfo.blockAlign!=curBlockAlign) {
    App::log(tr("%1 bad BlockAlign at offset 32\n"
                "  NumChannels=%2\n"
                "  BitsPerSample=%3\n"
                "  BlockAlign should be %4\n")
                     .arg(title)
                     .arg(wavinfo.numChannels)
                     .arg(wavinfo.bitsPerSample)
                     .arg(curBlockAlign));
    return false;
  }
  int curByteRate=wavinfo.sampleRate * curBlockAlign;
  if(wavinfo.byteRate!=curByteRate) {
    App::log(tr("%1 bad ByteRate at offset 28\n"
                "  SampleRate=%2\n"
                "  NumChannels=%3\n"
                "  BitsPerSample=%4\n"
                "  ByteRate should be %5\n")
                     .arg(title)
                     .arg(wavinfo.sampleRate)
                     .arg(wavinfo.numChannels)
                     .arg(wavinfo.bitsPerSample)
                     .arg(curByteRate));
    return false;
  }
  if(strncmp( wavinfo.subchunk2ID, "data", 4)!=0) {
    App::log(tr("%1 bad Subchunk2ID at offset 36 (should be 'data').\n").arg(title) );
    return false;
  }
  if(wavinfo.subchunk2Size + 36!=wavinfo.chunkSize) {
    App::log(tr("%1 subchunk2Size+36!=ChunkSize at offset 40\n").arg(title) );
    return false;
  }
  int nSamples=wavinfo.subchunk2Size/wavinfo.blockAlign;
  for(int i=0; i<wavinfo.numChannels; i++) {
    Signal * newSignal=new Signal(_db);
    newSignal->setFile(this);
    newSignal->setNumberInFile(i);
    newSignal->setNSamples(nSamples);
    newSignal->setSamplingPeriod(1.0/(double) wavinfo.sampleRate);
    newSignal->setByteIncrement(wavinfo.bitsPerSample/8);
    newSignal->setOffsetInFile(wavinfo.blockAlign);
    newSignal->setType(Signal::Waveform);
    setSignalName(newSignal, "", "", i + 1, shortName());
  }
  return true;
}

bool SignalFile::loadSegY(QDataStream::ByteOrder bo)
{
  TRACE;
  QFile f(_name);
  if( !f.open(QIODevice::ReadOnly) ) {
    App::log(tr("Loading SEGY : Unable to open file\n"));
    return false;
  }
  QDataStream s(&f);
  s.setByteOrder(bo);
  int nChannels=0;
  qint64 binHeaderOffset=3200;
  // Skip file header: need the number of extended textual file headers and sample coding
  qint16 sampleCoding, nExtendedTextualHeaders;
  f.seek(binHeaderOffset+24);
  s >> sampleCoding;
  if(!SEGYTraceHeader::isSampleCodingValid(sampleCoding)) {
    App::log(tr("Data sample format %1 not supported\n").arg(sampleCoding));
    return false;
  }
  f.seek(binHeaderOffset+304);
  s >> nExtendedTextualHeaders;
  if(nExtendedTextualHeaders==-1) { // Variable number
    App::log(tr("Variable number of Extended Textual File Header currently not supported\n") );
    return false;
  } else if(nExtendedTextualHeaders>0) { // fixed number
    f.seek(binHeaderOffset+400+3200*nExtendedTextualHeaders);
  } else { // fixed Extended Textual, conforming rev 0 (1975)
    f.seek(binHeaderOffset+400);
  }
  while(!s.atEnd()) {
    qint64 sigBeginOffset=f.pos();
    SEGYTraceHeader h;
    h.read(s);
    if(h.field.sampleNumber <= 0) {
      App::log(tr("Negative or null number of samples, try little and big endian\n"));
      return false;
    }
    if(h.field.sampleInterval <= 0) {
      App::log(tr("Negative or null sampling frequency, try little and big endian\n"));
      return false;
    }
    f.seek(sigBeginOffset+sizeof(SEGYTraceHeader)+4*(qint64)h.field.sampleNumber);
    if(s.status()==QDataStream::Ok) {
      Signal * newSignal=new Signal(_db);
      newSignal->setFile(this);
      newSignal->setNSamples(h.field.sampleNumber);
      newSignal->setNumberInFile(nChannels);
      newSignal->setOffsetInFile(sigBeginOffset + 240);
      newSignal->setByteIncrement(sampleCoding);
      newSignal->setType(Signal::Waveform);
      newSignal->setStartTime(h.startTime(SEGYTraceHeader::SEGYDelay));
      newSignal->setSamplingPeriod(( double) h.field.sampleInterval * 1.e-6);
      SeismicEvent e;
      e.setPosition(h.source());
      e.setTime(h.startTime(SEGYTraceHeader::NoDelay));
      e.setType(SeismicEvent::Hammer);
      e.setForce(Point(0.0, 0.0, -1.0));
      _db->seismicEvents()->add(e);
      newSignal->setReceiver(h.receiver());
      setSignalName(newSignal, "", "", nChannels + 1, shortName());
      nChannels++;
    } else {
      App::log(tr("Loading SEGY : Error reading SEGY file\n"));
      return false;
    }
  }
  return true;
}

/*!
  Loads SU format
*/
bool SignalFile::loadSu(QDataStream::ByteOrder bo)
{
  TRACE;
  QFile f(_name);
  if( !f.open(QIODevice::ReadOnly) ) {
    App::log(tr("Loading SU: Unable to open file %1\n").arg(_name) );
    return false;
  }
  QDataStream s(&f);
  s.setByteOrder(bo);
  int nChannels=0;
  while(!s.atEnd()) {
    qint64 sigBeginOffset=f.pos();
    SEGYTraceHeader h;
    h.read(s);
    f.seek(sigBeginOffset+sizeof(SEGYTraceHeader)+4*(qint64)h.field.sampleNumber);
    if(s.status()==QDataStream::Ok) {
      Signal * newSignal=new Signal(_db);
      newSignal->setFile(this);
      newSignal->setNSamples(h.field.sampleNumber);
      newSignal->setNumberInFile(nChannels);
      newSignal->setOffsetInFile(sigBeginOffset + 240);
      newSignal->setType(Signal::Waveform);
      newSignal->setStartTime(h.startTime(SEGYTraceHeader::SUDelay));
      newSignal->setSamplingPeriod((double) h.field.sampleInterval * 1.e-6);
      SeismicEvent e;
      e.setPosition(h.source());
      e.setTime(h.startTime(SEGYTraceHeader::NoDelay));
      e.setType(SeismicEvent::Hammer);
      e.setForce(Point(0.0, 0.0, -1.0));
      _db->seismicEvents()->add(e);
      newSignal->setReceiver(h.receiver());
      newSignal->setMetaData<StackCount>(h.field.numberSumVertical);
      newSignal->setMetaData<OriginalFileName>(QString::number(h.field.originalRecordNumber));
      setSignalName(newSignal, "", "", nChannels + 1, shortName());
      nChannels++;
    } else {
      App::log(tr("Loading SU : Error reading file %1\n").arg(_name) );
      return false;
    }
  }
  return true;
}

/*!
  Loads PASSCAL SEGY format
*/
bool SignalFile::loadPasscalSegY(QDataStream::ByteOrder bo)
{
  TRACE;
  QFile f(_name);
  if( !f.open(QIODevice::ReadOnly) ) {
    App::log(tr("Loading PASSCAL SEGY: Unable to open file %1\n").arg(_name) );
    return false;
  }
  QDataStream s(&f);
#if(QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
  s.setFloatingPointPrecision(QDataStream::SinglePrecision);
#endif
  s.setByteOrder(bo);
  int nChannels=0;
  while( !s.atEnd()) {
    qint64 sigBeginOffset=f.pos();
    PasscalSegYHeader h;
    h.read(s);
    f.seek(sigBeginOffset+240+h.sampleSize()*(qint64)h.nSamples());
    if(s.status()==QDataStream::Ok) {
      Signal * newSignal=new Signal(_db);
      newSignal->setFile(this);
      newSignal->setNSamples(h.nSamples());
      newSignal->setNumberInFile(nChannels);
      newSignal->setOffsetInFile(sigBeginOffset + 240);
      newSignal->setType(Signal::Waveform);
      newSignal->setStartTime(h.startTime());
      newSignal->setSamplingPeriod(h.samplingPeriod());
      newSignal->setComponent(h.component());
      newSignal->setByteIncrement(h.sampleSize());
      newSignal->setVoltPerCount(h.field.scaleFactor/h.field.gainConstant);
      TimePick& tp=newSignal->beginModifyMetaData<TimePick>();
      tp.setValue("P0", h.timePick());
      newSignal->endModifyMetaData(tp);
      setSignalName(newSignal, QString(h.field.stationName).left(5), "", nChannels + 1, shortName());
      nChannels++;
    } else {
      App::log(tr("Loading PASSCAL SEGY : Error reading file %1").arg(_name));
      return false;
    }
  }
  return true;
}

bool SignalFile::loadGse2(SignalFileFormat::Storage sto)
{
  TRACE;
  QFile f(_name);
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("Loading GSE 2.0: unable to open file\n") );
    return false;
  }
  int signalCount=0;
  QByteArray buffer;
  while(true) {
    // Look for the first WID2 (lines have always 80 char)
    while(!f.atEnd()) {
      buffer=f.readLine();
      if(buffer.startsWith("WID2")) {
        break;
      }
    }
    if(f.atEnd()) {
      if(signalCount==0) {
        App::log(tr("Loading GSE 2.0: no signal data found\n") );
        return false;
      } else if(signalCount==1 && sto==SignalFileFormat::Multi) {
        // Change file format from MULTIGSE2 to GSE2
        setFormat(SignalFileFormat::Gse2);
      }
      return true;
    }
    QByteArray header(buffer);
    AsciiLineParser parser(&header);
    while(!f.atEnd()) {
      buffer=f.readLine();
      if(buffer.startsWith("DAT2")) {
        break;
      }
    }
    if(f.atEnd()) {
      App::log(tr("Loading GSE 2.0: wrong file format, cannot find DAT2\n") );
      return false;
    }

    // Read time and date, set time datum within a dialog box, _t0 is then calculated
    DateTime startTime(QDate(parser.toInt(5, 4), parser.toInt(10, 2), parser.toInt(13, 2)), 0, 0.0);
    startTime.addHours(parser.toInt(16, 2));
    startTime.addMinutes(parser.toInt(19, 2));
    startTime.addSeconds(parser.toInt(22, 2)+parser.toDouble(24, 4));

    Signal * newSignal=new Signal(_db);
    newSignal->setFile(this);
    newSignal->setNumberInFile(signalCount);
    newSignal->setNSamples(parser.toInt(48, 8));
    newSignal->setOffsetInFile(f.pos());
    newSignal->setStartTime(startTime);
    double fsamp=parser.toDouble(57, 11);
    if(fsamp<=0) {
       App::log(tr("Invalid sampling frequency (%1)\n").arg(fsamp) );
       return false;
    }
    newSignal->setSamplingPeriod(1.0 /fsamp);
    newSignal->setType(Signal::Waveform);
    setSignalName(newSignal, parser.toString(29, 5).trimmed(), "", signalCount + 1, shortName());
    QString compString=parser.toString(35, 3);
    if(compString.endsWith("Z")) {
      newSignal->setComponent(Signal::Vertical);
    } else if(compString.endsWith("E")) {
      newSignal->setComponent(Signal::East);
    } else if(compString.endsWith("N")) {
      newSignal->setComponent(Signal::North);
    }
    double fac=parser.toDouble(69, 10);
    if(fac>0.0 && fac!=1.0) {
      // GSE specifies the callibration factor in nanometer per count
      // geopsy cannot set a unique UnitPerCount. Instead:
      newSignal->setVoltPerCount(1.0);
      newSignal->setUnitPerVolt(1e-9*fac);
    }
    QString sampleFormat=parser.toString(44, 3);
    if(sampleFormat.startsWith("CM")) {
      newSignal->setByteIncrement(parser.toInt(46, 1));
    } else if (sampleFormat=="INT") {
      newSignal->setByteIncrement(4);
    } else {
      App::log(tr("Loading GSE 2.0: unrecognized sample encoding '%1'\n").arg(sampleFormat) );
      return false;
    }
    signalCount++;
    if(sto==SignalFileFormat::Single) {
      break;
    }
  }
  return true;
}

bool SignalFile::loadShark(SignalFileFormat format)
{
  TRACE;
  QFile f(_name);
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("Loading %1: unable to open file '%1'\n").arg(format.name()).arg(_name) );
    return false;
  }
  QTextStream s(&f);
  SharkHeader h(format.id());
  h.fileName=shortName();
  switch(format.id()) {
  case SignalFileFormat::CityShark2:
    while(!s.atEnd()) {
      QString line=s.readLine();
      if(!h.parseLine(line)) {
        break;
      }
    }
    break;
  case SignalFileFormat::MiniShark:
    while(!s.atEnd()) {
      qint64 offset=s.pos();
      QString line=s.readLine();
      if(line.isEmpty() || line[0]!='#') {
        s.seek(offset);
        break;
      } else {
        h.parseLine(line.mid(1));
      }
    }
    break;
  default:
    ASSERT(false);
  }
  if(!h.isValid(f)) {
    return false;
  }

  qint64 offset=s.pos();
  for(int iSig=0; iSig<h.channelCount; iSig++) {
    Signal * newSignal=new Signal(_db);
    newSignal->setFile(this);
    newSignal->setNumberInFile(iSig);
    newSignal->setStartTime(h.startTime);
    newSignal->setSamplingPeriod(1.0/h.samplingFrequency);
    newSignal->setType(Signal::Waveform);
    int recNum=iSig/3+1;
    if(h.channelCount>3) {
      setSignalName(newSignal, h.fileName,
                    QString( "_%1" ).arg(recNum), recNum, shortName());
    } else {
      setSignalName(newSignal, h.fileName, "", 1, shortName());
    }
    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->setMetaData(OriginalFileName(h.originalFileName));
    newSignal->setNSamples(h.sampleCount);
    newSignal->setOffsetInFile(offset);
    newSignal->setReceiver(h.utmPosition);
    newSignal->setUtmZone(h.utmZone);
    newSignal->setCountPerVolt(h.conversionFactor*h.gain);
    Comments& c=newSignal->beginModifyMetaData<Comments>();
    c=h.comments;
    newSignal->endModifyMetaData(c);
  }
  return true;
}

bool SignalFile::loadSaf()
{
  TRACE;
  QFile f(_name);
  if( !f.open(QIODevice::ReadOnly) ) {
    App::log(tr("Loading SAF file: unable to open file\n"));
    return false;
  }
  QTextStream s(&f);
  // Read header lines
  int sampNum=0;
  double sampFreq=0, convFact=1;
  DateTime startTime;
  QString statName, str;
  Point p;
  Signal::Components comp[3];
  comp[0]=Signal::UndefinedComponent;
  comp[1]=Signal::UndefinedComponent;
  comp[2]=Signal::UndefinedComponent;
  while(!s.atEnd()) {
    QString line=s.readLine();
    StringSection str(line);
    str.trimmed();
    const QChar * ptr=nullptr;
    StringSection value=str.nextField(ptr, "=");
    value.trimmed();
    QString keyword=value.toString().toUpper();
    value=str.nextField(ptr, "=");
    value.trimmed();
    // List of accepted keywords:
    if(keyword=="STA_CODE")
      statName=value.toString();
    else if(keyword=="START_TIME") {
      ptr=nullptr;
      int year=value.nextField(ptr).toInt();
      int month=value.nextField(ptr).toInt();
      int day=value.nextField(ptr).toInt();
      startTime.setDate(QDate(year, month, day));
      startTime.addHours(value.nextField(ptr).toInt());
      startTime.addMinutes(value.nextField(ptr).toInt());
      startTime.addSeconds(value.nextField(ptr).toDouble());
    } else if(keyword=="SAMP_FREQ")
      sampFreq=value.toDouble();
    else if(keyword=="NDAT")
      sampNum=value.toInt();
    else if(keyword=="CH0_ID" ||
            keyword=="CH1_ID" ||
            keyword=="CH2_ID") {
      int index=keyword.mid(2, 1).toInt();
      QString valueStr=value.toString();
      if(valueStr.contains('N', Qt::CaseInsensitive)) {
        comp[index]=Signal::North;
      } else if(valueStr.contains('E', Qt::CaseInsensitive)) {
        comp[index]=Signal::East;
      } else if(valueStr.contains('Z', Qt::CaseInsensitive) ||
                valueStr.contains('V', Qt::CaseInsensitive)) {
        comp[index]=Signal::Vertical;
      } else {
        App::log(tr("Unrecognized component name for SAF format: \"%1\"\n").arg(value.toString()));
      }
    } else if(keyword=="# DATA CONVERSION FACTOR")
      convFact=value.toDouble();
    else if(keyword=="# GEOGRAPHICAL COORDINATES")
      p.fromString(value);
    else if(line.left(6)=="####--")
      break;
  }
  if(sampFreq==0.0) {
    App::log(tr("Loading SAF file: bad sampling frequency\n"));
    return false;
  }
  if(sampNum==0) {
    App::log(tr("Loading SAF file: bad number of samples\n"));
    return false;
  }
  qint64 offset=s.pos();
  // Read time and date, set time datum within a dialog box, _t0 is then calculated
  for(int iSig=0; iSig<3; iSig++) {
    Signal * newSignal=new Signal(_db);
    newSignal->setFile(this);
    newSignal->setNumberInFile(iSig);
    newSignal->setNSamples(sampNum);
    newSignal->setOffsetInFile(offset);
    newSignal->setCountPerVolt(convFact);
    newSignal->setStartTime(startTime);
    newSignal->setSamplingPeriod(1.0/sampFreq);
    newSignal->setType(Signal::Waveform);
    setSignalName(newSignal, statName, "", 1, shortName());
    newSignal->setComponent(comp[ iSig ] );
  }
  return true;
}

bool SignalFile::loadAsciiOneColumn()
{
  TRACE;
  FILE * fpin;
  if((fpin=fopen(_name.toLatin1().data(), "r"))==nullptr) {
    Message::warning(MSG_ID, tr("Loading ASCII one columns"), tr("Unable to open file"), true);
    return false;
  }
  int lineLen=1024;
  char * line=new char[ lineLen ];
  int chanNum=0;
  while(!feof(fpin)) {
    // Skip unrecognized header lines, after we must count the line to obtain the number of samples
    while(File::readLine(line, lineLen, fpin)!=0 && line[0]=='#' ) {
      char * ptr=File::stripWhiteSpace(line);
      if(strlen(ptr)>0) break;
    }
    int sampNum=1;
    while(File::readLine(line, lineLen, fpin)!=0 && line[0]!='#' && line[0]!='>' ) sampNum++;
    if(sampNum>0) {
      QString signalNameBase(shortName());
      Signal * newSignal=new Signal(_db);
      newSignal->setFile(this);
      newSignal->setNumberInFile(chanNum);
      newSignal->setNSamples(sampNum);
      newSignal->setOffsetInFile(0);
      newSignal->setSamplingPeriod(0);
      newSignal->setType(Signal::Waveform);
      setSignalName(newSignal, "", "", 1, shortName());
      newSignal->setComponent(Signal::UndefinedComponent);
      chanNum++;
    }
  }
  fclose(fpin);
  warnAsciiHeader();
  delete [] line;
  return true;
}

bool SignalFile::loadAscii()
{
  TRACE;
  QFile f(_name);
  if(!f.open(QIODevice::ReadOnly)) {
    Message::warning(MSG_ID, tr( "Loading ASCII Columns" ), tr("Unable to open file %1").arg(_name), true);
    return false;
  }
  // Parse header if it exists
  QTextStream s(&f);
  int headerLineNumber=0;
  AsciiSignalFormat format;
  if(_format.customFormat() && _format.id()==SignalFileFormat::Ascii) {
    format=*static_cast<const AsciiSignalFormat *>(_format.customFormat());
    QString hdr=format.header(s, headerLineNumber);
    if(!hdr.isEmpty()) {
      format.parseHeader(hdr);
    }
  }
  // The rest is data, check consitency for column number
  qint64 offset=s.pos();
  QByteArray buffer=f.readAll();
  AsciiLineParser p(&buffer);
  p.setSeparators(format.separators());
  p.setEmptyValues(format.emptyValues());
  p.setQuotes(format.quotes());
  int lineNumber=headerLineNumber;
  lineNumber++;
  p.countColumns();
  int chanNum=p.count();
  if(chanNum==0) {
    Message::warning(MSG_ID, tr("Loading ASCII Columns"), tr("Empty file ('%1')").arg(_name), true);
    return false;
  }
  int sampNum=1; // Already one is read to get chanNum

  while(!p.atEnd()) {
    lineNumber++;
    p.countColumns();
    if(chanNum!=p.count()) {
      Message::warning(MSG_ID, tr("Loading ASCII Columns"),
                       tr("The number of columns is not constant (%2 instead of %3), skipped from line %1. "
                          "Check number of samples in file '%4'.").arg(lineNumber).arg(p.count()).arg(chanNum).arg(_name), true);
      break;
    }
    sampNum++;
  }

  // For some custom formats, time information may be contained in data columns
  if(!format.readTimeColumn(buffer, chanNum, headerLineNumber)) {
    return false;
  }
  // Read time and date, set time reference within a dialog box, t0 is then calculated
  for(int iSig=0; iSig<chanNum; iSig++) {
    if(!format.isIgnored(iSig)) {
      Signal * newSignal=new Signal(_db);
      newSignal->setFile(this);
      newSignal->setNumberInFile(iSig);
      newSignal->setNSamples(sampNum);
      newSignal->setOffsetInFile(offset);
      newSignal->setSamplingFrequency(100.0);
      newSignal->setType(Signal::Waveform);
      setSignalName(newSignal, "", "", 1, shortName());
      newSignal->setComponent(Signal::UndefinedComponent);
      newSignal->setStartTime(format.startTime());
      format.assign(newSignal);
    }
  }
  if(!_format.customFormat()) warnAsciiHeader();
  return true;
}

void SignalFile::warnAsciiHeader()
{
  TRACE;
  Message::information(MSG_ID, tr("Loading ASCII Columns"),
                       tr("The ASCII format without header imply that you fill in "
                          "the correct information yourself in a table. Choose menu "
                          "\"view/new table\" and change the SamplingPeriod (or "
                          "SamplingFrequency) to allow correct plotting in a graphic"), true);
}

/*!
  Loads a text file with 2*n+1 columns, n being the number of channels.
  The first column is the frequency. Only the last line is considered and the
  samples are supposed to be linearly distributed.
*/
bool SignalFile::loadFourier()
{
  TRACE;
  static const QString title(tr( "Loading Fourier spectrum"));
  QFile f(_name);
  if(!f.open(QIODevice::ReadOnly)) {
    Message::warning(MSG_ID, title, tr("Unable to open file %1").arg(_name), true);
    return false;
  }
  // Check consitency for column number
  QByteArray buffer=f.readAll();
  AsciiLineParser pc(&buffer);
  pc.countColumns();
  int colNum=pc.count();
  if(colNum<3) {
    Message::warning(MSG_ID, title, tr("Empty file"), true);
    return false;
  }
  if((colNum-1)%2!=0) {
    Message::warning(MSG_ID, title, tr("Number of columns must be 2*n+1: frequency, Re(Channel1), Im(Channel1),..."), true);
    return false;
  }
  AsciiLineParser p(&buffer, colNum);
  int lineNumber=1;
  QVector<double> frequencies;
  p.readLine();
  frequencies.append(p.toDouble(0));

  // Check column consistency and frequency sampling
  while(!p.atEnd()) {
    lineNumber++;
    p.readLine();
    if(colNum!=p.count()) {
      Message::warning(MSG_ID, title,
                       tr("The number of columns is not constant, skipped from line %1. "
                          "Check number of samples").arg(lineNumber), true);
      break;
    }
    frequencies.append(p.toDouble(0));
  }

  std::sort(frequencies.begin(), frequencies.end());
  double df=(frequencies.last()-frequencies.first())/(frequencies.count()-1);
  for(int i=1; i<frequencies.count(); i++) {
    if(fabs(frequencies.at(i)-frequencies.at(i-1)-df)>0.001*df) {
      Message::warning(MSG_ID, title,
                       tr("Not a regular sampling in frequency at index %1 (df0=%2, df=%3, err=%4)")
                       .arg(i).arg(df).arg(frequencies.at(i)-frequencies.at(i-1))
                       .arg(fabs(frequencies.at(i)-frequencies.at(i-1)-df)), true);
      return false;
    }
  }

  int sampNum=2*qRound(frequencies.last()/df)+2;
  int chanNum=(colNum-1)/2;
  for(int iSig=0; iSig<chanNum; iSig++) {
    Signal * newSignal=new Signal(_db);
    newSignal->setFile(this);
    newSignal->setNumberInFile(iSig);
    newSignal->setNSamples(sampNum);
    newSignal->setOffsetInFile(0);
    newSignal->setSamplingPeriod(1.0/(sampNum*df));
    newSignal->setType(Signal::Spectrum);
    setSignalName(newSignal, "", "", 1, shortName());
    newSignal->setComponent(Signal::UndefinedComponent);
  }
  return true;
}

bool SignalFile::loadRD3()
{
  TRACE;
  // test for existence of rd3 file
  FILE * f=fopen(_name.toLatin1().data(), "rb");
  if(!f) {
    App::log(tr("Loading RD3 : Unable to open file\n"));
    return false;
  }
  fseek(f, 0L, SEEK_END);
  long sizeData=ftell(f);
  fclose(f);

  // open log file
  QFileInfo finfo(_name);
  QDir d(finfo.absolutePath());
  QString logname=d.absoluteFilePath( "radarm.log" );
  QFile flog(logname);
  bool IgnoreLog=false;
  while(!(flog.open(QIODevice::ReadOnly) || IgnoreLog)) {
    if(Message::warning(MSG_ID, "Loading RD3 ...",
                                "Unable to open log file.  Ignore ?", Message::yes(), Message::no())
        ==Message::Answer0)
      IgnoreLog=true;
    else {
      QString fileName=Message::getOpenFileName("Open RD3 log file", "Log file (*.log)");
      flog.setFileName(fileName);
    }
  }
  //
  // no log file
  //
  if(IgnoreLog) {
    int nSamples;
    double sampFreq, startTime;
    if(!GeopsyCoreEngine::instance()->askRD3LogParameter(nSamples, sampFreq, startTime)) {
      return false;
    }
    int nChannels=sizeData/nSamples/2;
    for(int i=0; i<nChannels; i++) {
      Signal * newSignal=new Signal(_db);
      newSignal->setFile(this);
      newSignal->setOffsetInFile(i * 2 * nSamples);
      newSignal->setNSamples(nSamples);
      newSignal->setNumberInFile(i + 1);
      newSignal->setType(Signal::Waveform);
      newSignal->setStartTime(DateTime::null.shifted(startTime*1e-6));
      newSignal->setSamplingPeriod(1./sampFreq * 1e-6);
    }
  } else { // read log file
    int sigNum0=_db->count();
    QString str;
    QString substr;
    QTextStream slog(&flog);
    while(!slog.atEnd()) {
      str=slog.readLine();
      if(str.length()<=0) {
        break;
      }
      substr=str.section(" ", 2, 2);
      int ich=substr.section("=", 1, 1).toInt()-1;
      Signal * newSignal=new Signal(_db);
      newSignal->setFile(this);
      substr=str.section( " ", 6, 6);
      newSignal->setNSamples(substr.section("=", 1, 1).toInt());
      newSignal->setNumberInFile(ich);
      newSignal->setType(Signal::Waveform);
      substr=str.section(" ", 4, 4);
      newSignal->setStartTime(DateTime::null.shifted(substr.section("=", 1, 1).toDouble()*1.e-6));
      substr=str.section(" ", 5, 5);
      newSignal->setSamplingPeriod(1.0/substr.section("=", 1, 1).toDouble() * 1e-6);
    }
    flog.close();
    int offset=0;
    for(int i=sigNum0; i<_db->count(); i++) {
      Signal * sig=_db->at(i);
      sig->setOffsetInFile(offset);
      offset += sig->nSamples() * 2;
    }
  }
  return true;
}

bool SignalFile::loadSac(QDataStream::ByteOrder bo)
{
  TRACE;
  QFile f(_name);
  if( !f.open(QIODevice::ReadOnly) ) {
    App::log(tr("Loading SAC: unable to open file\n"));
    return false;
  }
  QDataStream s(&f);
#if(QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
  s.setFloatingPointPrecision(QDataStream::SinglePrecision);
#endif
  s.setByteOrder(bo);
  SACHeader h;
  h.read(s);

  if(h.ints.field.NVHDR!=6) {
    App::log(tr("Loading SAC : Geopsy can open SAC files with version 6\n"
                "              File version is %1. It may lead to instabilities.\n").
                  arg(h.ints.field.NVHDR));
  }
  if(h.ints.field.IFTYPE!=1 || ! h.bools.field.LEVEN) {
    App::log(tr("Loading SAC: this SAC file does not contains time series evenly spaced\n"));
    return false;
  }

  Signal * newSignal=new Signal(_db);
  newSignal->setFile(this);
  newSignal->setOffsetInFile(f.pos());
  newSignal->setNSamples(h.ints.field.NPTS);
  newSignal->setNumberInFile(0);
  newSignal->setCountPerVolt(h.floats.field.SCALE!=-12345.0 ? Number::toDouble(h.floats.field.SCALE) : 1.0);
  newSignal->setType(Signal::Waveform);
  newSignal->setStartTime(h.startTime());
  if(h.floats.field.ODELTA!=-12345.0) {
    if(h.floats.field.ODELTA!=h.floats.field.DELTA) {
      Message::information(MSG_ID, tr("Loading SAC"), tr("Observed (%1) and nominal (%2) sampling "
                              "frequencies are different").arg(Number::toDouble(h.floats.field.ODELTA))
                              .arg(Number::toDouble(h.floats.field.DELTA) ), Message::ok(), true);
    }
  }
  newSignal->setSamplingPeriod(Number::toDouble(h.floats.field.DELTA) );
  QByteArray name_str(h.chars.field.KSTNM, 8); 
  name_str=name_str.trimmed();
  setSignalName(newSignal, name_str, "", 1, shortName());
  QByteArray cmp_str(h.chars.field.KCMPNM, 8);
  cmp_str=cmp_str.trimmed().toUpper();
  Signal::Components cmp=Signal::globalSeismographicNetworkComponent(cmp_str.data());
  if(cmp==Signal::UndefinedComponent) { // not recognized as a standard name, revert to
    if(cmp_str.contains("Z"))          // a more basic technique.
      cmp=Signal::Vertical;
    else if(cmp_str.contains("E"))
      cmp=Signal::East;
    else if(cmp_str.contains("N"))
      cmp=Signal::North;
  }
  newSignal->setComponent(cmp);
  switch (h.ints.field.IDEP) {
  case 6:
    newSignal->setAmplitudeUnit(Signal::Displacement);
    break;
  case 50:

  case 7:
    newSignal->setAmplitudeUnit(Signal::Velocity);
    break;
  case 8:
    newSignal->setAmplitudeUnit(Signal::Acceleration);
    break;
  default:
    break;
  }
  // User USER7, USER8 and USER9 (as BBFK command from SAC)
  newSignal->setReceiver(Point(Number::toDouble(h.floats.field.USER7),
                               Number::toDouble(h.floats.field.USER8),
                               Number::toDouble(h.floats.field.USER9)));
  // Set time picks
  for(int i=0; i<10; i++) {
    if(h.floats.field.T[i]!=-12345) {
      TimePick& tp=newSignal->beginModifyMetaData<TimePick>();
      tp.setValue("SAC_"+QString::number(i),
                  newSignal->startTime().shifted(Number::toDouble(h.floats.field.T[i])));
      newSignal->endModifyMetaData(tp);
    }
  }
  return true;
}

bool SignalFile::loadRadan()
{
  TRACE;
  char * header=new char[ 1024 ];

  FILE * f=fopen(_name.toLatin1().data(), "rb" );
  if( !f) {
    App::log(tr("Loading Radan: unable to open file\n"));
    return false;
  }
  // Header:
  // Offset
  // 0      2 bytes  file format code (00FF)
  // 2      2 bytes  header length (normally 1024 bytes)
  // 4      2 bytes  number of samples/scan
  // 6      2 bytes  number of bit/sample
  // 8      2 bytes  Size of fixed header ??? (128)
  // 10     4 bytes  Number of scan/second (float)
  // 14     3*4 bytes (float) Position, Top, Depth ???
  // 26     4 bytes  Scan Length (ns, float)
  // 28     2 bytes  Number of channels ??
  // 30     2*4 bytes Unsigned Integer, date created and modified
  // 38     14 bytes ??
  // 52     4 bytes  Dielectrical constant
  // 56     40 bytes 0 ??
  // 96     6 bytes  Antenna type
  // 102    9 bytes  0 ??
  // 111    1 byte   08 ??
  // 112    6 bytes  file name
  // 118    6 bytes  0 ??
  // 124    2 bytes  ??
  // 126    2 bytes  Number of values to define the gains=ng
  // 128    ng*4 bytes (float) Gains values
  // Description of filters???
  // zeros as trailer

  if(fread(header, 1, 10, f)!=10) {
    App::log(tr("Loading Radan: error reading first 10 bytes\n") );
    fclose(f);
    return false;
  }
  short id=*((short *)header);
  if(id!=0x00FF) {
    App::log(tr("Loading Radan: wrong file format\n") );
    fclose(f);
    return false;
  }
  unsigned int headerSize=*(( short * ) (header + 2) );
  int nSamples=*(( short * ) (header + 4) );
  int traceSize=nSamples * *(( short * ) (header + 6) )/8;

  if(headerSize!=1024) {
    delete [] header;
    header=new char[headerSize];
    fseek(f, 0, SEEK_SET);
    if(fread(header, 1, headerSize, f)!=headerSize) {
      App::log(tr("Loading Radan: error reading header\n") );
      fclose(f);
      return false;
    }
  } else {
    if(fread(header + 10, 1, 1014, f)!=1014) {
      App::log(tr("Loading Radan: error reading header (1024)\n") );
      fclose(f);
      return false;
    }
  }

  // Looking for the total size of the file and number of channels
  fseek(f, 0, SEEK_END);
  int sizeData=ftell(f);
  int nChannels=sizeData % traceSize;
  int byteInc=*(( short * ) (header + 6) )/8;
  double samplingPeriod=Number::toDouble(*((float *)(header + 26)))/(double)nSamples*0.000000001;

  // create signals
  long pos=headerSize;
  for(int i=0; i < nChannels; i++) {
    Signal * newSignal=new Signal(_db);
    newSignal->setFile(this);
    newSignal->setNSamples(nSamples);
    newSignal->setByteIncrement(byteInc);
    newSignal->setOffsetInFile(pos);
    newSignal->setNumberInFile(i);
    newSignal->setType(Signal::Waveform);
    newSignal->setSamplingPeriod(samplingPeriod);
    pos += traceSize;
  }

  fclose(f);
  delete [] header;
  return true;
}

bool SignalFile::loadSismalp()
{
  TRACE;
  QFileInfo fi(_name);
  QString ext=fi.suffix().toLower();
  QString title="Loading Sismalp " + shortName();
  QDir d(fi.absolutePath());
  QFileInfo fi_ndx(d.absoluteFilePath(fi.baseName() + ".NDX" ));
  if( !fi_ndx.exists())
    fi_ndx.setFile(d.absoluteFilePath(fi.baseName() + ".ndx" ));
  QFileInfo fi_sis(d.absoluteFilePath(fi.baseName() + ".SIS" ));
  if( !fi_sis.exists())
    fi_sis.setFile(d.absoluteFilePath(fi.baseName() + ".sis" ));
  if( !fi_sis.exists()) {
    App::log(tr( "Loading Sismalp : File %1.sis is missing.\n").arg(fi.baseName()));
    return false;
  }
  QFile f(fi_ndx.filePath());
  if( !f.open(QIODevice::ReadOnly) ) {
    App::log(tr("Loading Sismalp: unable to open file\n"));
    return false;
  }
  // Correct file name if ndx was selected
  if(ext=="ndx" )
    _name=fi_sis.filePath();
  QTextStream s(&f);
  QString header1, header2;
  uint nSignals=0;
  while( !s.atEnd()) {
    header1=s.readLine();
    header2=s.readLine();
    int nSamples=header1.mid(8, 6).toInt();
    int nbrBlockSignal=header1.mid(20, 6).toInt();

    if(nSamples > nbrBlockSignal * 512) {
      App::log(tr("Loading Sismalp: this file contains too much samples. Header is corrupted\n"));
      return false;
    }
    bool ok;
    double version=header2.mid(19, 5).toDouble(&ok);
    if( !ok) version=14.00;

    QTime tt(header1.mid(47, 2).toInt(), header1.mid(50, 2).toInt(), header1.mid(53, 2).toInt(), header1.mid(56, 3).toInt());
    DateTime startTime(QDate(header1.mid(42, 4).toInt(), header1.mid(39, 2).toInt(), header1.mid(36, 2).toInt()), 0, 0.0);
    startTime.addHours(header1.mid(47, 2).toInt());
    startTime.addMinutes(header1.mid(50, 2).toInt());
    startTime.addSeconds(header1.mid(53, 6).toInt());

    Signal * newSignal=new Signal(_db);
    newSignal->setFile(this);
    newSignal->setNumberInFile(0);
    newSignal->setNSamples(nSamples);
    uint coding;
    if(version > 14.00) {
      uint nrBits=header2.mid(12, 2).toUInt(&ok);
      if( !ok)
        nrBits=10;
      coding=nrBits; // nrBits is less than 100, hence it occupy less than 8 bits
      uint nrGainBits=header2.mid(15, 1).toUInt(&ok);
      if( !ok)
        nrGainBits=0;
      coding += nrGainBits << 8;  /* nrGainBits is less than 10,
                                               hence it occupy less than 4 bits */
    } else
      coding=10;
    newSignal->setByteIncrement(coding);
    newSignal->setOffsetInFile(( (header1.mid(14, 6).toInt() - 1) * 512) );
    newSignal->setStartTime(startTime);
    newSignal->setSamplingPeriod(1.0/header1.mid(26, 8).toDouble());
    newSignal->setType(Signal::Waveform);
    newSignal->setName(header1.mid(4, 4) );
    if(version > 14.00) {
      if(header2.mid(10, 1)=="Z" )
        newSignal->setComponent(Signal::Vertical);
      else if(header2.mid(10, 1)=="E" )
        newSignal->setComponent(Signal::East);
      else if(header2.mid(10, 1)=="N" )
        newSignal->setComponent(Signal::North);
      else
        newSignal->setComponent(Signal::Vertical);
    } else
      newSignal->setComponent(Signal::Vertical);
    //newSignal->setRecorderFactor(2pow(header2.mid(16,1)).toDouble());     //  lire dans le fichier gain .dat
    //if(header.mid(44,3)=="INT") newSignal->setByteIncrement(4);
    //else newSignal->byteIncrement(header.mid(46,1).toInt());
    //if(version >= 15.50)
    //if(15.41 => version > 15.50)
    //if(15.02 => version > 15.41)
    //if(15.02 < version || version!=long)
    nSignals++;
  }
  f.close();
  return true;
}

bool SignalFile::loadSyscomXmr()
{
  TRACE;
  FILE * f=fopen(_name.toLatin1().data(), "rb" );
  if( !f) {
    App::log(tr("Loading Syscom Xmr: unable to open file\n"));
    return false;
  }

  SyscomXmrHeader h;
  int nRead=fread(h.raw, 1, 256, f);
  fclose(f);
  if(nRead!=256) {
    App::log(tr("Loading Syscom Xmr: unable to read the header\n"));
    return false;
  }
  if(h.field.NB_CHANNEL!=3) {
    App::log(tr("Loading Syscom Xmr: more or less than 3 channels, file header corrupted\n"));
    return false;
  }

  for(int iSig=0; iSig<3; iSig++) {
    Signal * newSignal=new Signal(_db);
    newSignal->setFile(this);
    newSignal->setNumberInFile(iSig);
    newSignal->setNSamples(h.field.NB_SAMPLES);
    newSignal->setOffsetInFile(256+iSig*3);
    newSignal->setStartTime(h.startTime().shifted(static_cast<double>(h.field.INT_OFFSET)/static_cast<double>(h.field.SAMPLING)));
    newSignal->setSamplingPeriod(1.0/h.field.SAMPLING);
    newSignal->setType(Signal::Waveform);
    setSignalName(newSignal, shortName(), "", 1, shortName());
    newSignal->setCountPerVolt(h.countPerVolt(iSig));
    newSignal->setComponent(h.component(iSig));
  }
  return true;
}

bool SignalFile::loadSyscomSmr()
{
  TRACE;
  FILE * f=fopen(_name.toLatin1().data(), "rb" );
  if( !f) {
    App::log(tr("Loading Syscom Smr: unable to open file\n"));
    return false;
  }
  SyscomSVmrxHeader h;
  int nRead=fread(h.raw, 1, 256, f);
  fclose(f);
  if(nRead!=256) {
    App::log(tr( "Loading Syscom Smr: unable to read the header\n"));
    return false;
  }
  if(h.field.NB_CHANNEL!=3) {
    App::log(tr("Loading Syscom Smr: more or less than 3 channels, file header corrupted\n"));
    return false;
  }
  for(int iSig=0; iSig<3; iSig++) {
    Signal * newSignal=new Signal(_db);
    newSignal->setFile(this);
    newSignal->setNumberInFile(iSig);
    newSignal->setNSamples(h.field.NB_SAMPLES);
    newSignal->setOffsetInFile(256+iSig*2);
    newSignal->setStartTime(h.startTime().shifted(static_cast<double>(h.field.INT_OFFSET)/static_cast<double>(h.field.SAMPLING)));
    newSignal->setSamplingPeriod(1.0/h.field.SAMPLING);
    newSignal->setType(Signal::Waveform);
    setSignalName(newSignal, shortName(), "", 1, shortName());
    newSignal->setCountPerVolt(h.countPerVolt(iSig));
    newSignal->setComponent(h.component(iSig));
  }
  return true;
}

bool SignalFile::loadSyscomVmrx()
{
  TRACE;
  FILE * f=fopen(_name.toLatin1().data(), "rb" );
  if( !f) {
    App::log(tr("Loading Syscom Vmr/Vmx : Unable to open e"));
    return false;
  }

  SyscomSVmrxHeader h;
  int nRead=fread(h.raw, 1, 256, f);
  if(nRead!=256) {
    App::log(tr("Loading Syscom Vmr/Vmx : Unable to read the r"));
    fclose(f);
    return false;
  }
  int headerSize;
  switch(h.field.ENGG_MODE) {
  case 4:
  case 6:
    // Second part of the header is ignored here
    App::log(tr("File type %1: not yet supported\n").arg((int)h.field.ENGG_MODE) );
    return false;
  case 5:
  case 2:
  case 0:
    headerSize=256;
    break;
  default:
    App::log(tr("Loading Syscom Vmr/Vmx : bad file e"));
    fclose(f);
    return false;
  }
  fclose(f);
  if(h.field.NB_CHANNEL!=3) {
    App::log(tr( "Loading Syscom Vmr/Vmx : more or less than 3 channels, file header d"));
    return false;
  }

  for(int iSig=0; iSig<3; iSig++) {
    Signal * newSignal=new Signal(_db);
    newSignal->setFile(this);
    newSignal->setNumberInFile(iSig);
    newSignal->setNSamples(h.field.NB_SAMPLES);
    newSignal->setOffsetInFile(headerSize+iSig*2);
    newSignal->setStartTime(h.startTime().shifted(static_cast<double>(h.field.INT_OFFSET)/static_cast<double>(h.field.SAMPLING)));
    newSignal->setSamplingPeriod(1.0/h.field.SAMPLING);
    newSignal->setType(Signal::Waveform);
    setSignalName(newSignal, shortName(), "", 1, shortName());
    newSignal->setCountPerVolt(h.countPerVolt(iSig));
    newSignal->setComponent(h.component(iSig));
  }
  return true;
}

bool SignalFile::loadGuralpGcf()
{
  TRACE;
  QFile f(name());
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("Loading Guralp GCF values: unable to open file %1\n").arg(name()));
    return false;
  }
  QList<GuralpSignal *> sigList;
  QHash<GuralpCompressedBlock, GuralpSignal *> sigHash;
  GuralpCompressedBlock b;
  while(!f.atEnd()) {
    quint64 offset=f.pos();
    if(!b.readHeader(f)) {
      App::log(tr("Loading Guralp: error at offset %1\n").arg(f.pos(), 0, 16, QChar(' ')));
      qDeleteAll(sigList);
      return false;
    }
    b.skipBody(f);
    if(b.isData()) {
      QHash<GuralpCompressedBlock, GuralpSignal *>::iterator it=sigHash.find(b);
      if(it==sigHash.end()) {
        GuralpSignal * gsig=new GuralpSignal(offset, b);
        sigList.append(gsig);
        sigHash.insert(b, gsig);
      } else {
        it.value()->addRecord(offset, b);
      }
    }
  }

  // Create true Geopsy signals
  int n=sigList.count();
  for(int i=0; i<n; i++) {
    GuralpSignal * gsig=sigList.at(i);
    Signal * newSignal=new Signal(_db);
    newSignal->setFile(this);
    newSignal->setNumberInFile(i);
    newSignal->setNSamples(gsig->nSamples());
    newSignal->setStartTime(gsig->startTime());
    newSignal->setSamplingPeriod(1.0/gsig->samplingFrequency());
    newSignal->setTimeRange(gsig->timeRange());
    newSignal->setType(Signal::Waveform);
    setSignalName(newSignal, gsig->name(), "", i+1, shortName());
    newSignal->setComponent(gsig->component());
    newSignal->setMetaData<GuralpRecords>(gsig->records());
  }
  qDeleteAll(sigList);
  return true;
}

/*!
  Parse mini seed records using libmseed (from Iris). Blocks are read in any order and might be interlaced
  if various channels are recorded in the same mini seed volume.
*/
bool SignalFile::loadMiniSeed()
{
  TRACE;
  MiniSeedVolume vol;
  if(!vol.read(_name)) {
    return false;
  }

  for(int i=0; i<vol.count(); i++) {
    const MiniSeedTrace& trace=*vol.at(i);

    int nSamples=trace.nSamples();
    if(nSamples>0) {
      Signal * newSignal=new Signal(_db);
      newSignal->setFile(this);
      newSignal->setNumberInFile(i);
      newSignal->setNSamples(nSamples);
      newSignal->setOffsetInFile(0);
      newSignal->setStartTime(trace.startTime());
      newSignal->setSamplingPeriod(1.0/trace.samplingFrequency());
      newSignal->setType(Signal::Waveform);
      QString name;
      if(trace.network().isEmpty()) {
        name=trace.station();
      } else {
        name=trace.network()+"_"+trace.station();
      }
      setSignalName(newSignal, name, "", 1, shortName());
      if(trace.channel().endsWith("Z")) newSignal->setComponent(Signal::Vertical);
      else if(trace.channel().endsWith("N")) newSignal->setComponent(Signal::North);
      else if(trace.channel().endsWith("E")) newSignal->setComponent(Signal::East);
      newSignal->setMetaData(trace.records());
    }
  }
  return true;
}

void SignalFile::setSignalName(Signal * sig, QString stationName,
                                   QString sufix, int recNum, QString fileName)
{
  TRACE;
  if(GeopsyCoreEngine::instance()->preferences() ->stationNameFile())
    sig->setName(stationName + sufix);
  else if(GeopsyCoreEngine::instance()->preferences() ->stationNameRxxx()) {
    sig->setName(QString::asprintf("R%03i", recNum));
  } else
    sig->setName(fileName + sufix);
}

void SignalFile::removeFile()
{
  TRACE;
  if(!_isOriginalFile) {
    QFileInfo fi(_name);
    QDir d(fi.dir());
    d.remove(fi.fileName());
  }
}

void SignalFile::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  Q_UNUSED(context)
  writeProperty(s, "name", _name);
  writeProperty(s, "format", _format.name());
  writeProperty(s, "original", _isOriginalFile);
  writeProperty(s, "size", _size);
  writeProperty(s, "crc32", _crc32);
}

void SignalFile::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  SubSignalPool subPool;
  subPool.addFile(this);
  for(SubSignalPool::iterator it=subPool.begin(); it!=subPool.end(); ++it) {
    (*it)->xml_save(s, context);
  }
}

XMLMember SignalFile::xml_member(XML_MEMBER_ARGS)
{
  Q_UNUSED(attributes)
  if(tag=="Signal") {
    ASSERT(_db==static_cast<SignalDatabase *>(context));
    Signal * sig=new Signal(_db);
    sig->setFile(this);
    return XMLMember(sig);
  }
  if(tag=="name") return XMLMember(0);
  else if(tag=="format") return XMLMember(1);
  else if(tag=="original") return XMLMember(2);
  else if(tag=="size") return XMLMember(3);
  else if(tag=="crc32") return XMLMember(4);
  return XMLMember(XMLMember::Unknown);
}

bool SignalFile::xml_setProperty(XML_SETPROPERTY_ARGS)
{
  Q_UNUSED(tag)
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  bool ok=true;
  switch (memberID) {
  case 0:
    setName(content.toString());
    return true;
  case 1:
    setFormat(content.toString());
    if(format()==SignalFileFormat(SignalFileFormat::Unknown)) {
      App::log(tr("Unkown format '%1' for file '%2'\n").arg(content.toString()).arg(name()) );
      return false;
    } else {
      return true;
    }
  case 2:
    _isOriginalFile=content.toBool(&ok);
    return ok;
  case 3:
    _size=content.toLongLong(&ok);
    return ok;
  case 4:
    _crc32=content.toUInt(&ok);
    return ok;
  default:
    return false;
  }
}

#define CRC32_BUFFER_SIZE 512*1024

bool SignalFile::setContentKey()
{
  TRACE;
  QFileInfo fi(_name);
  _size=fi.size();
  bool ok=true;
  _crc32=File::crc32(_name, CRC32_BUFFER_SIZE, &ok);
  return ok;
}

bool SignalFile::hasValidContent(const QString& fileName) const
{
  TRACE;
  QFileInfo fi(fileName);
  // Check if exists
  if(!fi.exists()) {
    App::log(tr("File '%1' does not exist.\n")
                     .arg(fileName));
    return false;
  }
  // Check size
  qint64 fileSize=fi.size();
  if(_size>0 && fileSize!=_size) { // _size can be null for old databases
    App::log(tr("File size is not correct for file '%1' (%2 instead of %3)\n")
                     .arg(fileName).arg(fileSize).arg(_size));
    return false;
  }
  // Check hash on the first 512 kb
  bool ok=true;
  quint32 fileCrc32=File::crc32(fileName, CRC32_BUFFER_SIZE, &ok);
  if(ok) {
    if(fileCrc32==_crc32 || _crc32==0) {  // Compatibility: _crc32 can be null for old databases
      return true;
    } else {
      App::log(tr("File crc is not correct for file '%1' (%2 instead of %3)\n")
                       .arg(fileName).arg(fileCrc32).arg(_crc32));
    }
  }
  return false;
}

bool SignalFile::xml_polish(XML_POLISH_ARGS)
{
  TRACE;
  Q_UNUSED(context)
  // Bug in all release before 20070511, NumberInFile not initialized in Signal properties list in xml file
  // -1 is the uninitialized value of the NumberInFile
  SubSignalPool subPool;
  subPool.addFile(this);
  int n=subPool.count();
  for(int i=0; i<n;i++) {
    Signal& sig=*subPool.at(i);
    if(sig.numberInFile()==-1) {
      sig.setNumberInFile(i);
    }
  }
  // In desktop mode, check that file really exists and is the right file.
  // In server mode, the wrong file paths, sizes or hashes are not resolved
  // They are checked afterwards.
  QString fileName=name();
  if(CoreApplication::instance()->hasGui() &&
     !CoreApplication::instance()->isBatch()) {
    QString log;
    StreamRedirection sr(new StringStream(&log));
    if(!hasValidContent(fileName)) {
      PathTranslatorOptions options;
      options.setTitle(tr("Opening Database..."));
      options.setFileType(tr("Signal file (%1)"));
      options.setForceAsk(false);
      do {
        options.setErrorLog(log);
        fileName=CoreApplication::instance()->translatePath(name(), options);
        if(fileName.isEmpty()) {
          return true;
        }
        options.setForceAsk(true); // If the file is rejected because of size or hash, automatic translation must be avoided.
        log.clear();
      } while(!hasValidContent(fileName));
      setName(fileName);
      setPathModified(true);
    }
    if(_size==0 || _crc32==0) { // compatibility with version < 7
      setContentKey();
    }
  } else if(GeopsyCoreEngine::instance()->isFileContentKey() && (_size==0 || _crc32==0)) {  // Server mode requires size and hash,
                                                                                            // version must be >= 7
    App::log(tr("File content key is not available, save the database file with a more recent geopsy release (>20170301, file version>=7)\n") );
    return false;
  }
  return true;
}

QString SignalFile::keyName() const
{
  TRACE;
  quint64 val=_size;
  val*=_crc32;
  return QString("%1").arg(val, 16, 16, QChar('0'));
}

} // namespace GeopsyCore
