/***************************************************************************
**
**  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 <math.h>

#include <QGpCoreTools.h>

#include "SubSignalPool.h"
#include "SubSignalPoolProcess.h"
#include "SignalDatabase.h"
#include "GeopsyCoreEngine.h"
#include "AbstractSignalGroup.h"
#include "GeopsySignalHeader.h"
#include "WaveHeader.h"
#include "AbstractFileFormat.h"
#include "DynamicSignal.h"
#include "SEGYHeader.h"
#include "TimePick.h"
#include "Comments.h"
#include "CitySignal.h"
#include "GeopsyPreferences.h"

namespace GeopsyCore {

/*!
  Copy constuctor: do not forget to increase the reference count
*/
SubSignalPool::SubSignalPool(const SubSignalPool& subPool)
    : QList<Signal *>(subPool), _name(subPool.name())
{
  TRACE;
  for(iterator it=begin(); it!=end(); ++it) {
    ( *it) ->addReference();
  }
}

SubSignalPool& SubSignalPool::operator=(const SubSignalPool& o)
{
  TRACE;
  if( !isEmpty())
    removeAll();
  QList<Signal *>::operator=(o);
  _name=o._name;
  for(iterator it=begin(); it!=end(); ++it) {
    ( *it) ->addReference();
  }
  return *this;
}

bool SubSignalPool::operator==(const SubSignalPool& o) const
{
  TRACE;
  if(_name!=o._name) return false;
  int n=count();
  if(n!=o.count()) return false;
  for(int i=0; i<n; i++ ) {
    if(at(i)!=o.at(i)) return false;
  }
  return true;
}

/*!
  Create a hard copy of the current subpool, dupplicating signals.
*/
SubSignalPool SubSignalPool::copy() const
{
  TRACE;
    SubSignalPool c;
    c._name=_name;
    for(const_iterator it=begin(); it!=end(); ++it) {
      Signal * sig=new Signal(**it);
      sig->copySamplesFrom( *it);
      c.addSignal(sig);
    }
    return c;
}

int SubSignalPool::samplesCount ()
{
  TRACE;
  int nSamples=0;
  for(iterator it=begin(); it!=end(); ++it) {
    Signal * sig=*it;
    nSamples += sig->nSamples();
  }
  return nSamples;
}

void SubSignalPool::addAll(SignalDatabase * db)
{
  TRACE;
  addSubPool(db->subPool());
  _name=tr("All signals");
}

void SubSignalPool::addSignal(SignalDatabase * db, int id)
{
  TRACE;
  Signal * sig=db->signal(id);
  if(sig) {
    addSignal(sig);
  }
}

void SubSignalPool::addSubPool(const SubSignalPool& subPool)
{
  TRACE;
  for(const_iterator it=subPool.constBegin(); it!=subPool.constEnd(); ++it) {
    append( * it);
  }
  if(_name.isEmpty())
    _name=subPool.name();
  else if( !subPool.name().isEmpty())
    _name += "+" + subPool.name();
}

void SubSignalPool::addReceiver(Point point)
{
  TRACE;
  for(iterator it=begin(); it!=end(); ++it) {
    Signal * sig=* it;
    if(point==sig->receiver())
      append(sig);
  }
}

void SubSignalPool::addFile(SignalDatabase * db, int fileN)
{
  TRACE;
  SignalFile * sFile=db->filePool().at(fileN);
  addFile(sFile);
}

void SubSignalPool::addFile(const SignalFile * sFile)
{
  TRACE;
  const SubSignalPool & dbList=sFile->database()->subPool();
  for(const_iterator it=dbList.begin(); it!=dbList.end(); ++it) {
    Signal * sig=*it;
    if(sig->file()==sFile) {
      //qDebug( "SubSignalPool::addFile signal id %i %s", sig->id(), sig->name().toLatin1().data());
      append(sig);
    }
  }
  if(_name.isEmpty())
    _name=tr( "File %1" ).arg(sFile->shortName());
  else
    _name += tr( "+File %1" ).arg(sFile->shortName());
}

void SubSignalPool::insertSignal(Signal * sig, Signal * refSig, bool before)
{
  TRACE;
  if( !refSig)
    append(sig);
  else {
    int index=indexOf(refSig);
    if(index!=-1) {
      sig->addReference();
      if(before)
        insert(index, sig);
      else
        insert(index + 1, sig);
    }
  }
}

void SubSignalPool::addGroup(const AbstractSignalGroup * g)
{
  TRACE;
  addSubPool(g->subPool());
}

/*!
  Removes signal at index i in subpool
*/
void SubSignalPool::removeAt(int i)
{
  TRACE;
  Signal * sig=at(i);
  QList<Signal *>::removeAt(i);
  Signal::removeReference(sig);
}

/*!
  Removes signal pointed by sig
*/
void SubSignalPool::remove(Signal * sig)
{
  int index=indexOf(sig);
  if(index>-1) {
    QList<Signal *>::removeAt(index);
    Signal::removeReference(sig);
  }
}

/*!
  Removes all signals included in \a subPool
*/
void SubSignalPool::remove(const SubSignalPool& subPool)
{
  for(const_iterator it=subPool.begin(); it!=subPool.end(); ++it) {
    remove(*it);
  }
}

/*!
  Removes all signals
*/
void SubSignalPool::removeAll()
{
  TRACE;
  while(!isEmpty()) {
    Signal * sig=last();
    removeLast();
    Signal::removeReference(sig);
  }
}

void SubSignalPool::remove(Signal::Components comp)
{
  int i=0;
  while(i<count()) {
    Signal * sig=at(i);
    if(sig->component()==comp) {
      QList<Signal *>::removeAt(i);
      Signal::removeReference(sig);
    } else
      i++;
  }
}

/*!
  Remove all signals with a name not contained in \a keepSignalNames.
*/
void SubSignalPool::remove(const QStringList& keepSignalNames)
{
  TRACE;
  QMutableListIterator<Signal *> it(*this);
  while (it.hasNext()) {
    Signal * sig=it.next();
    if(!keepSignalNames.contains(sig->name())) {
      it.remove();
      Signal::removeReference(sig);
    }
  }
}

void SubSignalPool::subtractValue(double val)
{
  TRACE;
  for(iterator it=begin(); it!=end(); ++it) {
    Signal * sig=* it;
    if(!sig->warnReadOnlySamples()) {
      sig->subtractValue(val);
    }
  }
}

void SubSignalPool::removeTrend()
{
  TRACE;
  for(iterator it=begin(); it!=end(); ++it) {
    Signal * sig=* it;
    if(!sig->warnReadOnlySamples()) {
      App::log(1, tr("Signal '%1':\n").arg(sig->name()));
      sig->removeTrend();
    }
  }
}

void SubSignalPool::subtractSignal(Signal * bysig)
{
  TRACE;
  // Make sure all signals are in time series
  fastFourierTransform(Signal::Waveform);
  bool bySigIncludedInSubpool=false;
  SubSignalPoolProcess cp(*this);
  bysig->constLockSamples();
  for(iterator it=begin(); it!=end(); ++it) {
    Signal * sig=* it;
    if(sig!=bysig) { // avoid bysig to zeroed, postponed to the end
      if( !sig->warnReadOnlySamples()) {
        sig->subtractSignal(bysig);
      }
    } else {
      bySigIncludedInSubpool=true;
    }
    cp.next();
  }
  bysig->unlockSamples();
  if(bySigIncludedInSubpool) { // Set it to 0
    if(!bysig->warnReadOnlySamples()) {
      bysig->setValue(0.0);
    }
  }
}

void SubSignalPool::subtractSignals(SubSignalPool * bySubPool)
{
  TRACE;
  if(count()!=bySubPool->count())
    return ;
  // Make sure all signals are in time series
  fastFourierTransform(Signal::Waveform);
  for(int i=0; i < count(); ++i) {
    Signal * sig=at(i);
    Signal * bysig=bySubPool->at(i);
    if(!sig->warnReadOnlySamples()) {
      sig->subtractSignal(bysig);
    }
  }
}

void SubSignalPool::filter(const FilterParameters& param)
{
  TRACE;
  SignalDatabase * db=database();
  GeopsyCoreEngine::instance()->setProgressMaximum(db, count()-1);
  for(int i=0; i<count(); ++i) {
    Signal * sig=at(i);
    GeopsyCoreEngine::instance()->setProgressValue(db, i);
    if(!sig->warnReadOnlySamples()) {
      sig->filter(param);
    }
  }
}

void SubSignalPool::waveletTransform(const MorletParameters& param)
{
  TRACE;
  SignalDatabase * db=database();
  GeopsyCoreEngine::instance()->setProgressMaximum(db, count()-1);
  for(int i=0; i<count(); ++i) {
    Signal * sig=at(i);
    GeopsyCoreEngine::instance()->setProgressValue(db, i);
    if(!sig->warnReadOnlySamples()) {
      ComplexSignal * sigComp=sig->morletWavelet(param);
      Curve<Point2D> cabs=sigComp->abs(1.0/sig->duration());
      int n=sig->nSamples();
      LOCK_SAMPLES(double, sigSamples, sig)
        for(int i=0; i<n; i++) {
          sigSamples[i]=cabs.at(i).y();
        }
      UNLOCK_SAMPLES(sig)
      sig->setType(Signal::Waveform);
      delete sigComp;
    }
  }
}

void SubSignalPool::shift(double dt)
{
  TRACE;
  SignalDatabase * db=database();
  GeopsyCoreEngine::instance()->setProgressMaximum(db, count()-1);
  for(int i=0; i<count(); ++i) {
    Signal * sig=at(i);
    GeopsyCoreEngine::instance()->setProgressValue(db, i);
    if(!sig->warnReadOnlySamples()) {
      sig->shift(dt);
    }
  }
}

void SubSignalPool::overSample(double factor)
{
  TRACE;
  SignalDatabase * db=database();
  GeopsyCoreEngine::instance()->setProgressMaximum(db, count()-1);
  SubSignalPool sampledSignals;
  sampledSignals.setName(_name);
  for(int i=0; i<count(); ++i) {
    Signal * sig=at(i);
    GeopsyCoreEngine::instance()->setProgressValue(db, i);
    Signal * newsig=new Signal(*sig);
    newsig->overSample(factor, sig);
    sampledSignals.addSignal(newsig);
  }
  *this=sampledSignals;
}

void SubSignalPool::agc(double width)
{
  TRACE;
  SignalDatabase * db=database();
  GeopsyCoreEngine::instance()->setProgressMaximum(db, count()-1);
  for(int i=0; i<count(); ++i) {
    Signal * sig=at(i);
    GeopsyCoreEngine::instance()->setProgressValue(db, i);
    if(!sig->warnReadOnlySamples()) {
      sig->agc(width);
    }
  }
}

void SubSignalPool::whiten()
{
  TRACE;
  SignalDatabase * db=database();
  GeopsyCoreEngine::instance()->setProgressMaximum(db, count()-1);
  for(int i=0; i<count(); ++i) {
    Signal * sig=at(i);
    GeopsyCoreEngine::instance()->setProgressValue(db, i);
    if(!sig->warnReadOnlySamples()) {
      sig->whiten();
    }
  }
}

void SubSignalPool::stddevClip(double factor)
{
  TRACE;
  SignalDatabase * db=database();
  GeopsyCoreEngine::instance()->setProgressMaximum(db, count()-1);
  for(int i=0; i<count(); ++i) {
    Signal * sig=at(i);
    GeopsyCoreEngine::instance()->setProgressValue(db, i);
    if(!sig->warnReadOnlySamples()) {
      sig->stddevClip(factor);
    }
  }
}

void SubSignalPool::multiply(double value)
{
  TRACE;
  SignalDatabase * db=database();
  GeopsyCoreEngine::instance()->setProgressMaximum(db, count()-1);
  for(int i=0; i<count(); ++i) {
    Signal * sig=at(i);
    GeopsyCoreEngine::instance()->setProgressValue(db, i);
    if(!sig->warnReadOnlySamples()) {
      sig->multiply(value);
    }
  }
}

/*!
  Merge only signals that have common names, components and that have compatible T0.
*/
bool SubSignalPool::mergeStations()
{
  TRACE;
  // Sorting
  SortKey::clear();
  SortKey::add(MetaDataFactory::Name);
  SortKey::add(MetaDataFactory::Component);
  SortKey::add(MetaDataFactory::StartTime);
  sort();
  SubSignalPool toMerge;
  SubSignalPool merged;
  merged.setName(_name);
  double samplingPeriod=0;
  Signal::Components comp;
  SAFE_UNINITIALIZED(comp, Signal::Vertical);
  QString stat;
  Point rec;
  for(iterator it=begin(); it!=end(); ) {
    Signal * sig=* it;
    if(samplingPeriod==0) {
      samplingPeriod=sig->samplingPeriod();
      stat=sig->name();
      rec=sig->receiver();
      comp=sig->component();
      toMerge.addSignal(sig);
      ++it;
      continue;
    }
    if(sig->name()==stat && sig->receiver()==rec && sig->component()==comp) {
      if(sig->samplingPeriod()!=samplingPeriod) {
        Message::warning(MSG_ID, tr( "Merging station components" ),
                             tr( "For station %1, the sampling rate is not the "
                                 "same for all parts, skiping %2 part." )
                             .arg(stat).arg(sig->startTime().toString()), Message::cancel(), true);
        return false;
      } else {
        toMerge.addSignal(sig);
      }
      ++it;
    } else {
      if( ! toMerge.merge()) return false;
      merged.addSignal(toMerge.first());
      toMerge.removeAll();
      samplingPeriod=0;
    }
  }
  if( !toMerge.isEmpty()) {
    if( ! toMerge.merge()) return false;
    merged.addSignal(toMerge.first());
  }
  *this=merged;
  return true;
}

/*!
   Merge all traces of the subpool into a single trace
 */
bool SubSignalPool::merge()
{
  TRACE;
  if(isEmpty()) return false;
  // Make sure all signals are in time series
  fastFourierTransform(Signal::Waveform);
  iterator it;
  Signal * sig;
  for(it=begin(); it!=end(); ++it) {
    sig=* it;
    if(sig->type()!=Signal::Waveform) {
      Message::warning(MSG_ID, tr( "Merging signals ..." ),
                           tr( "Some signals are not waveforms" ),
                           Message::cancel(), true);
      return false;
    }
  }
  // Get the min and max time
  TimeRange r=timeRange();
  // Check if they have the same sampling frequency
  it=begin();
  sig=* it;
  double dT=sig->samplingPeriod();
  double fSamp=1.0/dT;
  for( ; it!=end(); ++it) {
    double d_jump=(r.start().secondsTo(sig->startTime()))*fSamp;
    int i_jump=qRound(d_jump);
    if(dT!=sig->samplingPeriod() && fabs(d_jump-static_cast<double>(i_jump))<0.001) {
      Message::warning(MSG_ID, tr( "Merging signals ..." ),
                           tr( "All signals must have the same sampling frequency "
                               "and differences between t0 must be a multiple of dT." ),
                               Message::cancel(), true);
      return false;
    }
  }
  // Allocate the new signal
  DynamicSignal * mergedsig=new DynamicSignal(first()->database());
  mergedsig->setSamplingPeriod(first()->samplingPeriod());
  mergedsig->setName(first()->name());
  mergedsig->setComponent(first()->component());
  mergedsig->setStartTime(first()->startTime());
  // Copy samples in it
  for(it=begin(); it!=end(); ++it) {
    sig=*it;
    CONST_LOCK_SAMPLES(double, samples, sig)
      mergedsig->set(sig->startTime(), samples, sig->nSamples());
    UNLOCK_SAMPLES(sig)
  }
  removeAll();
  addSignal(mergedsig);
  return true;
}

/*!
   Find minimum and maximum time over all signals of subPool and returns it as a TimeRange.
   Return a null range for spectra.
*/
TimeRange SubSignalPool::timeRange() const
{
  TRACE;
  DateTime tmin=DateTime::maximumTime;
  DateTime tmax=DateTime::minimumTime;
  double fmin=std::numeric_limits<double>::infinity();
  double fmax=-std::numeric_limits<double>::infinity();
  for(const_iterator it=begin(); it!=end(); ++it) {
    Signal * sig=* it;
    if(sig->isSpectrum()) {
      double minsig, maxsig;
      if(sig->isComplex()) {
        maxsig=0.5/sig->samplingPeriod();
      } else {
        maxsig=sig->samplingPeriod()*sig->nSamples();
      }
      minsig=0.1;
      if(minsig<fmin) {
        fmin=minsig;
      }
      if(maxsig>fmax) {
        fmax=maxsig;
      }
    } else {
      const DateTime& minsig=sig->startTime();
      DateTime maxsig=minsig.shifted(sig->samplingPeriod()*sig->nSamples());
      if(minsig<tmin) {
        tmin=minsig;
      }
      if(maxsig>tmax) {
        tmax=maxsig;
      }
    }
  }
  if(tmin==DateTime::maximumTime) {
    return TimeRange(DateTime::null.shifted(fmin), DateTime::null.shifted(fmax));
  } else {
    return TimeRange(tmin,tmax);
  }
}
/*!
   Find maximum frequency over all signals of subPool.
   Return an infinite range for waveforms.
*/
double SubSignalPool::maximumFrequency() const
{
  TRACE;
  double fmax=0.0;
  for(const_iterator it=begin(); it!=end(); ++it) {
    Signal * sig=* it;
    if(sig->isSpectrum()) {
      double maxsig;
      if(sig->isComplex()) {
        maxsig=0.5/sig->samplingPeriod();
      } else {
        maxsig=sig->samplingPeriod()*sig->nSamples();
      }
      if(maxsig>fmax) {
        fmax=maxsig;
      }
    }
  }
  return fmax;
}

/*!
  Return the number of spectra
*/
int SubSignalPool::spectraCount() const
{
  TRACE;
  int n=0;
  for(const_iterator it=begin(); it!=end(); ++it) {
    if((*it)->isSpectrum()) {
      n++;
    }
  }
  return n;
}

/*!
  Multiplies signal in in time domain by a window function.
*/
void SubSignalPool::taper(const TimeRangeParameters& range, const WindowFunctionParameters& param)
{
  TRACE;
  for(iterator it=begin(); it!=end(); ++it) {
    Signal * sig=* it;
    if(!sig->warnReadOnlySamples()) {
      TimeRange tw=range.absoluteRange(sig);
      sig->taper(tw, param);
    }
  }
}

/*!
  Cut signals according to \a param time limits.
*/
void SubSignalPool::cut(const TimeRangeParameters& param)
{
  TRACE;
  SubSignalPool cuttedSignals;
  cuttedSignals.setName(_name);
  // Make sure all signals are in time series
  fastFourierTransform(Signal::Waveform);
  for(iterator it=begin(); it!=end(); ++it) {
    Signal * sig=*it;
    Signal * newSig=sig->cut(param.absoluteRange(sig));
    if(newSig) {
      cuttedSignals.addSignal(newSig);
    }
  }
  *this=cuttedSignals;
}

/*!
  Computes STA/LTA ratios
*/
void SubSignalPool::stalta(double tsta, double tlta)
{
  TRACE;
  SubSignalPool staltaSignals;
  staltaSignals.setName(_name);
  // Make sure all signals are in time series
  fastFourierTransform(Signal::Waveform);
  for(iterator it=begin(); it!=end(); ++it) {
    Signal * sig=* it;
    DoubleSignal * cpsig=new DoubleSignal( *sig);
    cpsig->copySamplesFrom(sig);
    cpsig->abs();
    Signal * ratiosig=new Signal( *sig);
    cpsig->stalta(tsta, tlta, ratiosig);
    delete cpsig;
    staltaSignals.addSignal(ratiosig);
  }
  *this=staltaSignals;
}

/*!
  Decimate signals along amplitude scale. \a maxCount is the maximum number of discretization steps.
  \a maxRef is the amplitude value corresponding to \a maxCount. If \a maxRef is negative, the maximum
  of the signal is taken as a reference.
*/
void SubSignalPool::decimateAmplitude(int maxCount, double maxRef)
{
  TRACE;
  if(maxRef < 0.0) {
    double invMaxCount=1.0/(double) maxCount;
    for(iterator it=begin(); it!=end(); ++it) {
      Signal& sig=** it;
      if(!sig.warnReadOnlySamples()) {
        sig.decimateAmplitude(sig.maximumAmplitude() * invMaxCount);
      }
    }
  } else {
    double delta=maxRef/maxCount;
    for(iterator it=begin(); it!=end(); ++it) {
      Signal * sig=*it;
      if(!sig->warnReadOnlySamples()) {
        sig->decimateAmplitude(delta);
      }
    }
  }
}

void SubSignalPool::discreteFourierTransform()
{
  TRACE;
  SubSignalPool fftSignals;
  fftSignals.setName(_name);
  // Make sure all signals are in time series
  fastFourierTransform(Signal::Waveform);

  SignalDatabase * db=database();
  GeopsyCoreEngine::instance()->setProgressMaximum(db, count()-1);
  for(int i=0; i<count(); ++i) {
    Signal * sig=at(i);
    GeopsyCoreEngine::instance()->setProgressValue(db, i);
    sig->discreteFourierTransform();
  }
}

/*!
  Decimate signals along time scale.
*/
void SubSignalPool::decimateTime(int factor)
{
  TRACE;
  SubSignalPool decimatedSignals;
  decimatedSignals.setName(_name);
  // Make sure all signals are in time series
  fastFourierTransform(Signal::Waveform);

  SignalDatabase * db=database();
  GeopsyCoreEngine::instance()->setProgressMaximum(db, count()-1);
  for(int i=0; i<count(); ++i) {
    Signal * sig=at(i);
    GeopsyCoreEngine::instance()->setProgressValue(db, i);
    Signal * newsig=new Signal(*sig);
    newsig->decimateTime(factor, sig);
    decimatedSignals.addSignal(newsig);
  }
  *this=decimatedSignals;
}

void SubSignalPool::fastFourierTransform(DoubleSignal::SignalType st)
{
  TRACE;
  SignalDatabase * db=database();
  GeopsyCoreEngine::instance()->setProgressMaximum(db, count()-1);
  for(int i=0; i<count(); ++i) {
    Signal * sig=at(i);
    GeopsyCoreEngine::instance()->setProgressValue(db, i);
    sig->fastFourierTransform(st);
    sig->resetAmplitudes();
    sig->setHeaderModified(true);
  }
}

void SubSignalPool::importTable(QString fileName)
{
  TRACE;
  QFile f(fileName);
  if( !f.open(QIODevice::ReadOnly) ) {
    Message::warning(MSG_ID, tr( "Importing Table ..." ),
                         tr("Unable to open file %1").arg(fileName), Message::cancel());
    return ;
  }
  QString str;
  // Get the list of variable (IDs)
  QTextStream s(&f);
  str=s.readLine().trimmed();
  while(str[ 0 ]=='#' ) {
    str=s.readLine().trimmed(); // comment line
  }
  MetaDataFieldList varList;
  MetaDataIndex varItem;
  StringSection strItem;
  StringSection strLine(str);
  const QChar * ptr=nullptr;
  strItem=strLine.nextField(ptr, "\t" );
  bool doSamplingPeriod=false;
  while(strItem.isValid()) {
    varItem=MetaDataFactory::instance()->index(strItem.toString());
    if(!varItem.isValid()) {
      Message::warning(MSG_ID, tr("Importing Table ..."),
                       tr("Unrecognized meta data: '%1'").arg(strItem.toString()),
                       Message::cancel());
      return;
    }
    if(varItem.id()==MetaDataFactory::SamplingPeriod) {
      switch (Message::warning(MSG_ID, tr( "Importing table ..." ),
                                    tr( "You are about to modify the SamplingPeriod of your signals. "
                                        "This action may alter their time scale. "
                                        "Do you want to proceed?\n\n"
                                        "- If you answer 'No'', all the other variables will be imported as usual.\n"
                                        "- If you answer 'Cancel', nothing will be imported." ),
                                    Message::yes(), Message::no(),
                                    Message::cancel()) ) {
      case Message::Answer0:
        doSamplingPeriod=true;
        break;
      case Message::Answer1:
        doSamplingPeriod=false;
        break;
      default:
        return ;
      }
    }
    varList.add(varItem);
    strItem=strLine.nextField(ptr, "\t" );
  }
  // Get the values
  for(iterator it=begin(); it!=end() && !s.atEnd(); ++it) {
    Signal * sig=* it;
    str=s.readLine().trimmed();
    while(str[ 0 ]=='#' ) {
      str=s.readLine().trimmed(); // comment line
    }
    ptr=nullptr;
    strLine.set(str.data(), str.size());
    strItem=strLine.nextField(ptr, "\t" );
    for(MetaDataFieldList::iterator itvar=varList.begin();itvar!=varList.end();++itvar) {
      if(itvar->id()!=MetaDataFactory::SamplingPeriod || doSamplingPeriod) {
        sig->setHeader(*itvar, strItem.toString());
      }
      strItem=strLine.nextField(ptr, "\t" );
    }
    sig->setHeaderModified(true);
  }
}

void SubSignalPool::exportTable(QString fileName, const MetaDataFieldList& param)
{
  TRACE;
  QFile f(fileName);
  if(!f.open(QIODevice::WriteOnly)) {
    Message::warning(MSG_ID, tr( "Exporting table ..." ),
                         tr( "Unable to save file" ), Message::cancel());
    return;
  }
  QString str;
  QTextStream s(&f);
  s << tr("# This file contains all the variables that were listed in Geopsy's Table\n"
          "# You can edit this file as you want.\n"
          "# All columns must be separated by only one tabulation.\n"
          "# Lines starting with '#' are always ignored when importing.\n"
          "# The first line starting without '#' must contains the name of each variable.\n"
          "#\n");
  MetaDataFieldList::const_iterator itvar;
  for(itvar=param.begin();itvar!=param.end();++itvar) {
    s << MetaDataFactory::instance()->name(*itvar) << "\t";
  }
  s << endl;
  for(iterator it=begin(); it!=end(); ++it) {
    Signal * sig=*it;
    for (itvar=param.begin(); itvar!=param.end(); ++itvar) {
      switch (itvar->id()) {
      case MetaDataFactory::Duration:
        str=Number::secondsToDuration(sig->header(*itvar).toDouble());
        break;
      default:
        str=sig->header(*itvar).toString();
        break;
      }
      s << str << "\t";
    }
    s << endl;
  }
}

void SubSignalPool::unglitch(double threshold)
{
  TRACE;
  for(iterator it=begin(); it!=end(); ++it) {
    Signal * sig=* it;
    if( !sig->warnReadOnlySamples()) {
      sig->unglitch(threshold);
    }
  }
}

int SubSignalPool::checkDuplicateRays()
{
  TRACE;
  int ndrays=0;
  /* TODO: not usefull if tomography is not included in geopsy
           to be revised in a more object oriented form (e.g. DupRay with all necessary search properties
  PointList sources,receivers;
  SubSignalPool sigs(_pool,(bool)false);
  int index;
  for(iterator it=begin(); it!=end(); ++it) {
    Signal * sig=* it;
    delete sig->duplicateRaysPool();
    sig->duplicateRaysPool(NULL);
    sig->duplicateRaysID(-1);
    // Look for a source corresponding to this one
    index=sources.indexOf(sig->source());
    // For this source, look for a matching receiver
    while(index>-1 && receivers.at(index)!=sig->receiver())
      index=sources.findNext(&sig->source());
    if(index==-1) {
      // Look for a source corresponding to this receiver
      index=sources.find (&sig->receiver());
      // For this source, look for a matching receiver
      while(index >-1 && *(receivers.at(index))!=sig->source())
        index=sources.findNext(&sig->receiver());
    }
    if(index==-1) {
      sources.append(&sig->source());
      receivers.append(&sig->receiver());
      sigs.append(sig);
    }
    else {
      Signal * otherSig=sigs.at(index);
      SubSignalPool* dupPool=otherSig->duplicateRaysPool();
      if(dupPool==NULL) {
        ndrays++;
        // Set duplicate for the other signal
        dupPool=new SubSignalPool(_pool,(bool)false);
        otherSig->duplicateRaysPool(dupPool);
        dupPool->append(sig);
        otherSig->duplicateRaysID(ndrays);
        // Set Duplicate for signal i
        SubSignalPool* tmpPool=new SubSignalPool(_pool,(bool)false);
        sig->duplicateRaysPool(tmpPool);
        tmpPool->append(otherSig);
        sig->duplicateRaysID(ndrays);
      }
      else {
        SubSignalPool* tmpPool=new SubSignalPool(_pool,(bool)false);
        tmpPool->append(otherSig);
        SubSignalPoolIterator itDup(*dupPool);
        for(Signal * dupSig=itDup.current();dupSig;dupSig=++itDup) {
          dupSig->duplicateRaysPool()->append(sig);
          tmpPool->append(dupSig);
        }
        dupPool->append(sig);
        sig->duplicateRaysID(otherSig->duplicateRaysID());
        sig->duplicateRaysPool(tmpPool);
      }
    }
  }*/ 
  return ndrays;
}

/*!
  Returns a vector of all receivers.
*/
Curve<Point> SubSignalPool::receivers() const
{
  TRACE;
  Curve<Point> l;
  for(const_iterator it=begin(); it!=end(); ++it) {
    Signal * sig=*it;
    l.append(sig->receiver());
  }
  return l;
}

/*!
  Returns a vector of all sources. One source per signal is allowed
*/
Curve<Point> SubSignalPool::sources() const
{
  TRACE;
  Curve<Point> l;
  for(const_iterator it=begin(); it!=end(); ++it) {
    Signal * sig=*it;
    l.append(sig->source());
  }
  return l;
}

void SubSignalPool::setHeaderModified(bool b) const
{
  TRACE;
  for(const_iterator it=begin(); it!=end(); ++it) {
    (*it)->setHeaderModified(b);
  }
}

bool SubSignalPool::isHeaderModified() const
{
  TRACE;
  for(const_iterator it=begin(); it!=end(); ++it) {
    if((*it)->isHeaderModified())
      return true;
  }
  return false;
}

bool SubSignalPool::isReadOnlySamples() const
{
  TRACE;
  for(const_iterator it=begin(); it!=end(); ++it) {
    if(( *it) ->isReadOnlySamples())
      return true;
  }
  return false;
}

SubSignalPool SubSignalPool::horizontalComponents() const
{
  TRACE;
  // Sorting
  SortKey::clear();
  SortKey::add(MetaDataFactory::Component);
  SortKey::add(MetaDataFactory::Name);
  SortKey::add(MetaDataFactory::StartTime);
  SubSignalPool s3c=*this;
  s3c.sort();
  int n=s3c.count();
  SubSignalPool sh;
  for(int i=0; i<n; i++) {
    Signal * sig=at(i);
    switch(sig->component()) {
    case Signal::North:
    case Signal::East:
      sh.addSignal(sig);
      break;
    case Signal::Vertical:
    case Signal::UndefinedComponent:
    case Signal::All:
    case Signal::Horizontal:
    case Signal::Time:
    case Signal::Ignore:
      break;
    }
  }
  return sh;
}

SubSignalPool SubSignalPool::verticalComponent() const
{
  TRACE;
  // Sorting
  SortKey::clear();
  SortKey::add(MetaDataFactory::Component);
  SortKey::add(MetaDataFactory::Name);
  SortKey::add(MetaDataFactory::StartTime);
  SubSignalPool s3c=*this;
  s3c.sort();
  int n=s3c.count();
  SubSignalPool sz;
  for(int i=0; i<n; i++) {
    Signal * sig=at(i);
    switch(sig->component()) {
    case Signal::Vertical:
      sz.addSignal(sig);
      break;
    case Signal::North:
    case Signal::East:
    case Signal::UndefinedComponent:
    case Signal::All:
    case Signal::Horizontal:
    case Signal::Time:
    case Signal::Ignore:
      break;
    }
  }
  return sz;
}

/*!
  Make sure the the sub pool is organized in terms of a list of 3 component
  stations, listed by Z N E
  Returns false if some components are missing or if some error were detected.
  The signals must be associated in triplets (Vertical, North and East component)
    \li sort the subpool by receiver name, t0 and component
    \li test if sampling rate and number of samples is the same for all
*/
bool SubSignalPool::associate3Components()
{
  TRACE;
  // Sorting
  SortKey::clear();
  SortKey::add(MetaDataFactory::Name);
  SortKey::add(MetaDataFactory::StartTime);
  SortKey::add(MetaDataFactory::Component);
  sort();
  // Testing if all 3 components are present (warning only)
  // Testing if nsamples and sampling rate are constant
  SubSignalPool triplet;
  double samplingPeriod=0;
  TimeRange range;
  QString stat;
  Point rec;
  int n=count();
  for(int i=0; i<n; ) {
    Signal * sig=at(i);
    if(samplingPeriod==0) {
      samplingPeriod=sig->samplingPeriod();
      range=sig->timeRange().range();
      stat=sig->name();
      rec=sig->receiver();
      triplet.addSignal(sig);
      i++;
      continue;
    }
    if(sig->name()==stat &&
       sig->receiver()==rec &&
       sig->timeRange().range().intersection(range).lengthSeconds()>0.9*range.lengthSeconds()) {
      if(sig->samplingPeriod()!=samplingPeriod) {
        App::log(tr("Associating 3 components for station %1: "
                    "the sampling rate is not the same for all components.")
                 .arg(stat).arg(sig->component()));
        return false;
      } else {
        triplet.addSignal(sig);
      }
      i++;
    } else {
      if(!check3ComponentTriplet(triplet)) return false;
      triplet.removeAll();
      samplingPeriod=0;
    }
  }
  if(!triplet.isEmpty()) {
    if(!check3ComponentTriplet(triplet)) return false;
  }
  return true;
}

/*!
  Check if subpool is only madeof 3 signals corresponding to the
  3 spatial components (ZNE), return true if OK
*/
bool SubSignalPool::check3ComponentTriplet(SubSignalPool & triplet)
{
  TRACE;
  if(triplet.count()!=3) {
    QString stat=triplet.at(0)->name();
    App::log(tr("Associating 3 components for station %1: "
                "at least one component is  missing.")
             .arg(stat));
    return false;
  }
  if(triplet.at(0)->component()!=Signal::Vertical ||
     triplet.at(1)->component()!=Signal::North ||
     triplet.at(2)->component()!=Signal::East) {
    QString stat=triplet.at(0) ->name();
    App::log(tr("Associating 3 components for station %1: "
                "cannot find the 3 components Z, N and E.")
             .arg(stat));
    return false;
  }
  return true;
}

/*!
  Gather all sources and signals that record them.
  Allocated subpools must be deleted.
*/
QHash<const SeismicEvent *, SubSignalPool *> SubSignalPool::associateSources()
{
  TRACE;
  QHash<const SeismicEvent *, SubSignalPool *> sourceSignals;
  QHash<const SeismicEvent *, SubSignalPool *>::iterator it;
  SubSignalPool * sourceSubPool=nullptr;
  QList<const SeismicEvent *> sources;
  for(int i=count()-1; i>=0; i--) {
    Signal * sig=at(i);
    sources=database()->seismicEvents()->events(sig);
    for(int i=sources.count()-1; i>=0; i--) {
      const SeismicEvent * source=sources.at(i);
      it=sourceSignals.find(source);
      if(it==sourceSignals.end()) {
        sourceSubPool=new SubSignalPool;
        sourceSubPool->setName(source->name());
        sourceSubPool->addSignal(sig);
        sourceSignals.insert(source, sourceSubPool);
      } else {
        sourceSubPool=it.value();
        sourceSubPool->addSignal(sig);
      }
    }
  }
  // Sort subPools by increasing distance to source
  for(it=sourceSignals.begin(); it!=sourceSignals.end(); it++) {
    QMap<double, Signal *> distances;
    const Point& src=it.key()->position();
    SubSignalPool * subPool=it.value();
    for(int i=subPool->count()-1; i>=0; i--) {
      distances.insert(src.distanceTo(subPool->at(i)->receiver()),
                       subPool->at(i));
    }
    subPool->clear();
    QMap<double, Signal *>::iterator it;
    for(it=distances.begin(); it!=distances.end(); it++) {
      subPool->addSignal(it.value());
    }
  }
  return sourceSignals;
}

void SubSignalPool::rotateComponents(Matrix3x3 rotMatrix)
{
  TRACE;
  if(!associate3Components()) return;
  int nSamples;
  Point p;
  Signal * sigV, * sigN, * sigE;
  SubSignalPoolProcess cp(*this);
  for(iterator it=begin(); it!=end(); ++it) {
    sigV=*it;
    nSamples=sigV->nSamples();
    LOCK_SAMPLES(double, sampV, sigV)
      ++it;
      if(it!=end()) {
        sigN=*it;
       LOCK_SAMPLES(double, sampN, sigN)
          ++it;
          if(it!=end()) {
            sigE=*it;
            if(sigV->nSamples()!=sigN->nSamples() ||
               sigV->nSamples()!=sigE->nSamples() ||
               sigV->startTime()!=sigN->startTime() ||
               sigV->startTime()!=sigE->startTime()) {
              Message::warning(MSG_ID, tr("Associating 3 components"),
                               tr("For station %1, the components are not available on exactly "
                                  "the same time range, skipping this stations.").arg(sigV->name()),
                               Message::cancel(), true);
            } else {
              LOCK_SAMPLES(double, sampE, sigE)
                if(!sigV->warnReadOnlySamples() &&
                   !sigN->warnReadOnlySamples() &&
                   !sigE->warnReadOnlySamples()) {
                  for(int i=0; i<nSamples; i++) {
                    p.set(sampE[i], sampN[i], sampV[i]);
                    p=rotMatrix*p;
                    sampE[i]=p.x();
                    sampN[i]=p.y();
                    sampV[i]=p.z();
                  }
                }
              UNLOCK_SAMPLES(sigE)
            }
          }
        UNLOCK_SAMPLES(sigN)
      }
    UNLOCK_SAMPLES(sigV)
    cp.next(3);
  }
}

void SubSignalPool::debugSignalSharing()
{
  TRACE;
  for(iterator it=begin(); it!=end(); ++it) {
    Signal * sig=*it;
    qDebug("Signal %i named %s ptr %llx -> refCount=%i", sig->id(), sig->name().toLatin1().data(),
           reinterpret_cast<qint64>(sig), sig->referenceCount());
  }
}

int SubSignalPool::maximumTimePickCount() const
{
  TRACE;
  int n=0;
  for(const_iterator it=begin(); it!=end(); ++it) {
    int t=(*it)->metaData<TimePick>().count();
    if(t>n) n=t;
  }
  return n;
}

/*!
  Computes all possible correlations from this subpool if \a referenceSig is null.

  The correlation is computed with a maximum delay of \a delay.
*/
void SubSignalPool::correlations(double maxDelay, const Signal * referenceSig)
{
  TRACE;
  SubSignalPool corrSubPool;
  corrSubPool.setName(_name);
  int n=count();
  if(referenceSig) {
    for(int i=0; i<n; i++) {
      Signal * sig=at(i);
      if(sig!=referenceSig) {
        Signal * corrSig=new Signal(sig->database());
        corrSig->setTemporary();
        corrSig->correlation(referenceSig, sig, maxDelay);
        corrSubPool.addSignal(corrSig);
      }
    }
  } else {
    for(int i=0; i<n-1; i++) {
      Signal * sig=at(i);
      for(int j=i+1; j< n; j++) {
        Signal * corrSig=new Signal(sig->database());
        corrSig->setTemporary();
        corrSig->correlation(sig, at(j), maxDelay);
        corrSubPool.addSignal(corrSig);
      }
    }
  }
  *this=corrSubPool;
}

/*!
  Computes all possible normalized correlations from this subpool if \a referenceSig is null.

  The correlation is computed with a maximum delay of \a delay.
*/
void SubSignalPool::normalizedCorrelations(double maxDelay, const Signal * referenceSig)
{
  TRACE;
  SubSignalPool corrSubPool;
  corrSubPool.setName(_name);
  int n=count();
  if(referenceSig) {
    for(int i=0; i<n; i++) {
      Signal * sig=at(i);
      if(sig!=referenceSig) {
        Signal * corrSig=new Signal(sig->database());
        corrSig->setTemporary();
        corrSig->normalizedCorrelation(referenceSig, sig, maxDelay);
        corrSubPool.addSignal(corrSig);
      }
    }
  } else {
    for(int i=0; i<n-1; i++) {
      Signal * sig=at(i);
      for(int j=i+1; j< n; j++) {
        Signal * corrSig=new Signal(sig->database());
        corrSig->setTemporary();
        corrSig->normalizedCorrelation(sig, at(j), maxDelay);
        corrSubPool.addSignal(corrSig);
      }
    }
  }
  *this=corrSubPool;
}

/*!
  Computes convolution product with respect to \a referenceSig
*/
void SubSignalPool::convolution(const Signal * referenceSig)
{
  TRACE;
  SubSignalPool convSubPool;
  convSubPool.setName(_name);
  int n=count();
  for(int i=0; i< n; i++) {
    Signal * sig=at(i);
    if(sig!=referenceSig) {
      Signal * convSig=new Signal(sig->database());
      convSig->setTemporary();
      convSig->convolution(referenceSig, sig);
      convSubPool.addSignal(convSig);
    }
  }
  *this=convSubPool;
}

bool SubSignalPool::removeInstrumentalResponse(const InstrumentalResponse& sensor)
{
  int n=count();
  for(int i=0; i<n; i++) {
    Signal * sig=at(i);
    if(!sig->removeInstrumentalResponse(sensor)) {
      return false;
    }
    sig->setVoltPerUnit(sensor.transductionFactor());
  }
  return true;
}

/*!
  Export subPool signals to file \a filePAth with format \a f. If format is SignalFile::Unknown,
  The format is guessed from the extension of \a filePath. If \a useOriginalBaseName is true,
  \a filePath is considered as a directory (\a f is mandatory).
*/
bool SubSignalPool::save(QString filePath, bool useOriginalBaseName, SignalFileFormat format,
                         int maximumSignalsPerFile, const QString& pickName) const
{
  TRACE;
  QFileInfo fi(filePath);
  // Split eventually the subpool into various subsubpool of maximumSignalsPerFile signals and save them.
  if(maximumSignalsPerFile>0) {
    SubSignalPool subPool;
    for(const_iterator it=begin();it!=end();it++) {
      subPool.addSignal(*it);
      if(subPool.count()==maximumSignalsPerFile) {
        QString newFilePath;
        if(useOriginalBaseName) {
          newFilePath=filePath;
        } else {
          QFileInfo fi(filePath);
          QString suffix;
          if(fi.isDir()) {
            newFilePath+="/"+subPool.first()->name();
          } else {
            newFilePath=fi.baseName()+subPool.first()->name();
            suffix=fi.suffix();
          }
          if(maximumSignalsPerFile==1) {
            newFilePath+="_"+Signal::componentLetter(subPool.first()->component());
          }
          if(suffix.isEmpty()) {
            suffix=format.suffix();
          }
          newFilePath+="."+suffix;
          newFilePath=File::uniqueName(newFilePath, fi.path());
          GeopsyCoreEngine::instance()->showMessage(subPool.database(), tr("Exporting file '%1'...").arg(newFilePath));
        }
        if(!subPool.save(newFilePath, useOriginalBaseName, format, 0, pickName)) {
          return false;
        }
        subPool.removeAll();
      }
    }
    return true;
  }
  if(format.id()==SignalFileFormat::Unknown) {
    if(useOriginalBaseName) {
      Message::warning(MSG_ID, tr("Exporting signals"), tr("No format given with option 'use original base name'. Cannot guess "
                                                           "ouput file format from extension."), true);
      return false;
    }
    format=SignalFileFormat::fromSuffix(fi.suffix());
    if(format.id()==SignalFileFormat::Unknown) {
      Message::warning(MSG_ID, tr("Exporting signals"), tr("Cannot determine export format from file extension"), true);
      return false;
    }
  }
  if(useOriginalBaseName) {
    if(!fi.exists() || !fi.isDir()) {
      Message::warning(MSG_ID, tr("Exporting signals"),
                       tr("%1 does not exists or is not a directory (option 'original base name' is on).").arg(filePath),
                       true);
      return false;
    }
  }
  switch (format.id()) {
  case SignalFileFormat::Custom:
    if(!format.customFormat() || format.customFormat()->isReadOnly()) {
      App::log(tr("Format %1 is currently not supported for export.\n").arg(format.name()) );
      return false;
    }
    if(format.customFormat()->storage()==SignalFileFormat::Single) {
      break;
    }
    // Else custom format with export capabilities of multiple signal in one file
  case SignalFileFormat::Seg2:
  case SignalFileFormat::SuLittleEndian:
  case SignalFileFormat::SuBigEndian:
  case SignalFileFormat::SegYLittleEndian:
  case SignalFileFormat::SegYBigEndian:
  case SignalFileFormat::Gse2:
  case SignalFileFormat::MultiGse2:
  case SignalFileFormat::GeopsySignal:
  case SignalFileFormat::Tomo:
  case SignalFileFormat::Ascii:
  case SignalFileFormat::AsciiOneColumn:
  case SignalFileFormat::Saf:
  case SignalFileFormat::Wav:
  case SignalFileFormat::CityShark2:
  case SignalFileFormat::MiniSeed:
    // For multi signal formats
    if(!exportFilePath(filePath, useOriginalBaseName, format)) return false;
    if(useOriginalBaseName){
      QFileInfo fi(filePath);
      QString newFilePath=File::uniqueName(fi.fileName(), fi.path());
      if(newFilePath!=filePath) {
        if(Message::warning(MSG_ID, tr("Exporting signals"), tr("File %1 already exists, saving to %2?").arg(filePath).arg(newFilePath),
                            Message::yes(), Message::no())==Message::Answer1) {
          return false;
        }
        App::log(tr("File %1 already exists, saving to %2\n").arg(filePath).arg(newFilePath) );
        filePath=newFilePath;
      }
    }
    break;
  case SignalFileFormat::SacLittleEndian:
  case SignalFileFormat::SacBigEndian:
    break;
  SIGNALFILEFORMAT_READONLY
    App::log(tr("Format %1 is currently not supported for export.\n").arg(format.name()) );
    return false;
  }
  switch (format.id()) {
  case SignalFileFormat::Seg2:
    return saveSeg2(filePath);
  case SignalFileFormat::SuLittleEndian:
    return saveSu(filePath, QDataStream::LittleEndian);
  case SignalFileFormat::SuBigEndian:
    return saveSu(filePath, QDataStream::BigEndian);
  case SignalFileFormat::SegYLittleEndian:
    return saveSegY(filePath, QDataStream::LittleEndian);
  case SignalFileFormat::SegYBigEndian:
    return saveSegY(filePath, QDataStream::BigEndian);
  case SignalFileFormat::SacLittleEndian:
    return saveSac(filePath, useOriginalBaseName, QDataStream::LittleEndian);
  case SignalFileFormat::SacBigEndian:
    return saveSac(filePath, useOriginalBaseName, QDataStream::BigEndian);
  case SignalFileFormat::Gse2:
  case SignalFileFormat::MultiGse2:
    return saveGse2(filePath);
  case SignalFileFormat::GeopsySignal:
    return saveGeopsySignal(filePath);
  case SignalFileFormat::Tomo:
    return saveArrivalTimes(filePath, pickName);
  case SignalFileFormat::Ascii:
    if(!format.customFormat()) {
      return saveAsciiMultiColumns(filePath);
    }
    break;
  case SignalFileFormat::Custom:
    if(format.customFormat()) {
      return format.customFormat()->save(*this, filePath);
    }
    break;
  case SignalFileFormat::AsciiOneColumn:
    return saveAsciiOneColumn(filePath);
  case SignalFileFormat::Saf:
    return saveSaf(filePath);
  case SignalFileFormat::CityShark2:
    return saveCityShark2(filePath);
  case SignalFileFormat::Wav:
    return saveWav(filePath);
  case SignalFileFormat::MiniSeed:
    return saveMiniSeed(filePath);
  SIGNALFILEFORMAT_READONLY
    break;
  }
  return false;
}

/*!
  Adjust file path to export. Returns false if \a useOriginalBaseName is true and no common base name can be deduced
  from original file names. This is called for all Multi Storage file format export.
*/
bool SubSignalPool::exportFilePath(QString& filePath, bool useOriginalBaseName, const SignalFileFormat& format) const
{
  TRACE;
  if(!useOriginalBaseName) return true;
  if(isEmpty()) {
    Message::warning(MSG_ID, tr("Export %1").arg(format.caption()),
                     tr("No signal to deduce a common file name, cannot use option 'original base name'." ), Message::cancel());
    return false;
  }
  const_iterator it=begin();
  SignalFile * file=(*it)->file();
  if(!file) {
    Message::warning(MSG_ID, tr("Export %1").arg(format.caption()),
                     tr("Cannot deduce file name from temporary signals, cannot use option 'original base name'." ), Message::cancel());
    return false;
  }
  QString fileName=file->shortName();
  for(it++; it!=end(); it++) {
    file=(*it)->file();
    if(fileName!=file->shortName()) {
      Message::warning(MSG_ID, tr("Export %1").arg(format.caption()),
                           tr("No common file name, cannot use option 'original base name'.\n\n"
                              "A solution is probably to split the list of signals to export by setting "
                              "'maximum number of signals per file'."), Message::cancel());
      return false;
    }
  }
  if(format.suffix().isEmpty()) {
    filePath+="/"+fileName;
  } else {
    QFileInfo fi(fileName);
    filePath+="/"+fi.baseName()+format.suffix();
  }
  return true;
}

/*!
  Save samples in a binary format (double) for quick access. This format is machine dependent. Hence, a magic int and
  double number is stored at the beginning of the file. If the correct value can be retrieved there very little chance
  than the internal representation of number is different on this machine and on the machine that generated the file.

  Format for current version (2)
  ------------------------------

 Header:
   char*16 (16)                            "GeopsySignal   " (tag ending with 0)
   int (4)                                 Magic integer number
   double (8)                              Magic double number
   int (4)                                 version (currently=2)
   int (4)                                 offset to first data (currently=22+n*4)
   int (4)                                 nSignals
   int*nSignals (4*nSignals)               nSamples

 Format for each signal:
  double*nSamples (8*...)      samples

  N samples is stored even if it is not necessary for database reloading.
  It makes this format a minimum self-contained. This binary file format is generally with a database file (.gpy)
  which contains the header information with an XML format.

  Format for version 1
  --------------------
 Header:
   char*10 (10)                "DBSignals" (tag)
   int (4)                     version (=1)
   int (4)                     offset to first data (currently=22+n*12)
   int (4)                     nSignals
   int*nSignals (4*...)        nSamples
   int*nSignals (4*...)        conversionFactors

 Format for each signal:
  double*nSamples (8*...)      samples

*/
bool SubSignalPool::saveGeopsySignal (QString filePath) const
{
  TRACE;
  QFile f(filePath);
  if(!f.open(QIODevice::WriteOnly)) return false;
  GeopsySignalHeader h;
  memcpy(h.field.tag, GEOPSYSIGNAL_TAG, 16);
  h.field.magicInt=GEOPSYSIGNAL_MAGICINT;
  h.field.magicFloat=GEOPSYSIGNAL_MAGICFLOAT;
  h.field.version=GEOPSYSIGNAL_VERSION;
  h.field.nSignals=count();
  h.field.traceHeaderSize=GEOPSYSIGNAL_TRACEHEADERSIZE;
  // TraceHeader introduced in version 3 20170209
  // To be still compatible with previous releases, offset is "faked" by TRACEHEADERSIZE
  h.field.offset=GEOPSYSIGNAL_HEADERSIZE+h.field.nSignals*4+GEOPSYSIGNAL_TRACEHEADERSIZE;
  if(f.write(h.raw, GEOPSYSIGNAL_HEADERSIZE)!=GEOPSYSIGNAL_HEADERSIZE) return false;
  for(const_iterator it=begin(); it!=end(); ++it) {
    qint32 nSamples=(*it)->nSamples();
    if(f.write(reinterpret_cast<const char *>(&nSamples), 4)!=4) return false;
  }
  GeopsySignalTraceHeader th;
  th.field.magicFloat=GEOPSYSIGNAL_MAGICFLOAT;
  th.field.version=GEOPSYSIGNAL_VERSION;
  for(const_iterator it=begin(); it!=end(); ++it) {
    th.field.unitPerCount=(*it)->unitPerCount();
    if(f.write(th.raw, GEOPSYSIGNAL_TRACEHEADERSIZE)!=GEOPSYSIGNAL_TRACEHEADERSIZE) return false;
    qint64 nWritten=0;
    const DoubleSignal * sig=const_cast<const Signal *>(*it)->saveType(Signal::Waveform);
    qint64 nToWrite=sizeof(double)*sig->nSamples();
    CONST_LOCK_SAMPLES(double, sigSamples, sig)
      nWritten=f.write(reinterpret_cast<const char *>(sigSamples), nToWrite);
    UNLOCK_SAMPLES(sig)
    (*it)->restoreType(sig);
    if(!sigSamples || nWritten!=nToWrite) {
      return false;
    }
  }
  return true;
}

bool SubSignalPool::saveSeg2Return(bool ret, qint64 * offset) const
{
  delete [] offset;
  return ret;
}

bool SubSignalPool::saveSeg2 (QString filePath) const
{
  TRACE;
  QFile f(filePath);
  if(!f.open(QIODevice::WriteOnly)) {
    Message::warning(MSG_ID, tr("Saving SEG-2 ..."),
                     tr("Cannot write to a signal file"), Message::cancel());
    return false;
  }
  int n=count();
  qint64 * offset=new qint64[n];
  double delay=0.0;
  if(!writeSeg2Header(f) || !writeSeg2TextHeader(f, delay)) {
    Message::warning(MSG_ID, tr( "Saving SEG-2 ..." ),
                     tr( "Error while writing SEG2 header (%1)" ).arg(filePath), Message::cancel());
    return saveSeg2Return(false, offset);
  }
  for(int i=0; i<count(); ++i) {
    Signal * sig=at(i);
    offset[i]=f.pos();
    if(!sig->writeSeg2(f, delay)) {
      Message::warning(MSG_ID, tr( "Saving SEG-2 ..." ),
                       tr( "Error while writing SEG2 signal index %1, see log for details (%2)" ).arg(i).arg(filePath), Message::cancel());
      return saveSeg2Return(false, offset);
    }
  }
  if(!writeSeg2Offsets(f, offset)) {
    Message::warning(MSG_ID, tr( "Saving SEG-2 ..." ),
                     tr( "Error while writing SEG2 signal offsets (%1)" ).arg(filePath), Message::cancel());
    return saveSeg2Return(false, offset);
  }
  return saveSeg2Return(true, offset);
}

bool SubSignalPool::writeSeg2Header(QFile& f) const
{
  TRACE;
  int n=count();
  char * buf=new char[n*4+32];
  *reinterpret_cast<qint16 *>(buf)=0x3A55;
  *reinterpret_cast<qint16 *>(buf+2)=1;
  *reinterpret_cast<qint16 *>(buf+6)=static_cast<short>(n);
  *reinterpret_cast<qint16 *>(buf+8)=1;
  *reinterpret_cast<qint16 *>(buf+9)='\n';
  *reinterpret_cast<qint16 *>(buf+11)=1;
  *reinterpret_cast<qint16 *>(buf+12)='\n';
  if(f.write(buf, 32)!= 32) goto error;
  if(f.write(buf, 4*n)!=4*n) goto error;
  delete [] buf;
  return true;
error:
  delete [] buf;
  return false;
}

bool SubSignalPool::writeSeg2Offsets(QFile& f, qint64 *offset) const
{
  TRACE;
  int n=count();
  f.seek(32);
  return f.write(reinterpret_cast<const char *>(offset), 4*n)==4*n;
}

bool SubSignalPool::writeSeg2TextHeader(QFile& f, const QString& keyword,
                                        const QString& sep, const QString& value,
                                        qint16& blocksize) const
{
  QString str=keyword+sep+value+"\n";
  qint16 length=static_cast<qint16>(str.length()+2);
  if(f.write(reinterpret_cast<const char *>(&length), 2)!=2 ||
     f.write(str.toLatin1().data(), length-2)!=length-2) {
    return false;
  }
  blocksize+=length;
  return true;
}

bool SubSignalPool::writeSeg2AlignTextHeader(QFile& f, qint16 blocksize) const
{
  int rem4=blocksize % 4;
  if(rem4>0) {
    if(f.write(reinterpret_cast<const char *>(&rem4), rem4)!=rem4) {
      return false;
    }
  }
  return true;
}

bool SubSignalPool::writeSeg2TextHeader(QFile& f, double& delay) const
{
  int n=count();
  qint16 blocksize=0;
  if(!writeSeg2TextHeader(f, "SOFTWARE", " ", "Geopsy", blocksize)) {
    return false;
  }
  // Check if a common start time exists.
  DateTime commonStartTime=first()->startTime();
  for(int i=0; i<n; i++) {
    if(at(i)->startTime()!=commonStartTime) {
      return writeSeg2AlignTextHeader(f, blocksize);
    }
  }
  // Try to get pretrigger delay
  QList<const SeismicEvent *> l=database()->seismicEvents()->events(first());
  if(l.count()==1) {
    const SeismicEvent * e=l.first();
    delay=e->time().secondsTo(commonStartTime);
    qint16 blocksize=0;
    if(!writeSeg2TextHeader(f, "ACQUISITION_DATE", " ", e->time().toString("d/MMM/yyyy"), blocksize)) {
      return false;
    }
    if(!writeSeg2TextHeader(f, "ACQUISITION_TIME", " ", e->time().toString("hh:hm:ssz"), blocksize)) {
      return false;
    }
  }
  return writeSeg2AlignTextHeader(f, blocksize);
}

bool SubSignalPool::saveArrivalTimes(QString filePath, const QString& pickName) const
{
  TRACE;
  QFile f(filePath);
  if( !f.open(QIODevice::WriteOnly) ) {
    Message::warning(MSG_ID, tr("Saving Arrival Times ..."),
                     tr("Cannot write to file %1.").arg(filePath), Message::cancel());
    return false;
  }
  QTextStream s(&f);
  Curve<Point> srcList=sources();
  Curve<Point> uniqSrcList=srcList;
  uniqSrcList.unique();
  Curve<Point> recList=receivers();
  ASSERT(srcList.count()==recList.count());
  ASSERT(srcList.count()==count());
  recList.unique();
  s << "Arrival_times_file v1.1\n";
  s << "name " << pickName << "\n";
  s << uniqSrcList.count() << " sources\n";
  s << recList.count() << " receivers\n";
  s << "sources positions\n";
  s << "n  x  y  z\n";
  int n=uniqSrcList.count();
  for(int i=0; i<n; i++)
    s << i + 1 << " " << uniqSrcList.at(i) << "\n";
  s << "receivers positions\n";
  s << "n  x  y  z\n";
  n=recList.count();
  for(int i=0; i<n; i++) {
    s << i + 1 << " " << recList.at(i) << "\n";
  }
  s << "arrival times\n";
  n=uniqSrcList.count();
  int* ir=new int[count()];
  double* tr=new double[count()];
  for(int i=0; i<n; i++) {
    s << i+1 << " th source\n";
    int nr=0;
    Point src=uniqSrcList.at(i);
    for(int j=count()-1; j>=0; j--) {
      if(srcList.at(j)==src) {
        Signal * sig=at(j);
        ir[nr]=recList.indexOf(sig->receiver())+1;
        tr[nr]=sig->startTime().secondsTo(sig->metaData<TimePick>().value(pickName));
        nr++;
      }
    }
    s << nr << " receivers\n";
    for(int j=0; j<nr; j++)
      s << ir[ j ] << " " << tr[ j ] << "\n";
  }
  f.close();
  delete [] ir;
  delete [] tr;
  return true;
}

bool SubSignalPool::saveSu(QString filePath, QDataStream::ByteOrder bo) const
{
  TRACE;
  const_iterator it=begin();
  Signal * sig=*it;
  int nsamples=sig->nSamples();
  for(++it; it!=end(); ++it) {
    sig=* it;
    if(nsamples!=sig->nSamples()) {
      Message::warning(MSG_ID, tr("Saving SU ..."),
                       tr("The signals must contain the same number of samples"), Message::cancel());
      return false;
    }
  }
  QFile f(filePath);
  if(!f.open(QIODevice::WriteOnly) ) {
    Message::warning(MSG_ID, tr("Saving SU ..."),
                     tr("Cannot write to file %1").arg(filePath), Message::cancel());
    return false;
  }
  QDataStream s(&f);
#if(QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
  s.setFloatingPointPrecision(QDataStream::SinglePrecision);
#endif
  s.setByteOrder(bo);
  for(int i=0; i<count(); i++) {
    if(!at(i)->writeSegySu(s, i, true)) {
      return false;
    }
  }
  f.close();
  return true;
}

bool SubSignalPool::saveSegY(QString filePath, QDataStream::ByteOrder bo) const
{
  TRACE;
  QFile f(filePath);
  if(!f.open(QIODevice::WriteOnly) ) {
    Message::warning(MSG_ID, tr("Saving SEGY ..."),
                     tr("Cannot write to file %1").arg(filePath), Message::cancel());
    return false;
  }
  QDataStream s(&f);
  s.setFloatingPointPrecision(QDataStream::SinglePrecision);
  s.setByteOrder(bo);
  SEGYHeader hdr;
  hdr.write(s);
  for(int i=0; i<count(); i++) {
    if(!at(i)->writeSegySu(s, i, false)) {
      return false;
    }
  }
  f.close();
  return true;
}

bool SubSignalPool::saveSac(QString filePath, bool useOriginalBaseName, QDataStream::ByteOrder bo) const
{
  TRACE;
  int n=count();
  if(n==0) {
    return false;
  }
  SignalDatabase * db=database();
  GeopsyCoreEngine::instance()->setProgressMaximum(db, n-1);
  for(int i=0; i<n; i++) {
    GeopsyCoreEngine::instance()->setProgressValue(db, i);
    Signal * sig=at(i);
    QString completefilePath;
    if(useOriginalBaseName && sig->file()) {
      QString tmp=sig->file()->shortName();
      // Removes extension but not numbers like .009 found in Cityshark file names.
      if(tmp.contains(QRegExp("\\.[a-zA-Z]+[0-9]*$"))) {
        QFileInfo fi(tmp);
        tmp=fi.completeBaseName();
      }
      QString compSuffix="_"+Signal::componentLetter(sig->component());
      if(!tmp.endsWith(compSuffix)) {
        tmp+=compSuffix;
      }
      tmp+=".sac";
      completefilePath=File::uniqueName(tmp,filePath);
    } else {
      if(n>1) {
        QFileInfo fi(filePath);
        completefilePath=File::uniqueName(fi.fileName(),fi.path());
      } else
        completefilePath=filePath;
    }
    QFile f(completefilePath);
    if(!f.open(QIODevice::WriteOnly)) {
      Message::warning(MSG_ID, tr("Saving SAC ..."),
                       tr("Cannot open file %1 for writing").arg(completefilePath), Message::cancel());
      return false;
    }
    QDataStream s(&f);
#if(QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
    s.setFloatingPointPrecision(QDataStream::SinglePrecision);
#endif
    s.setByteOrder(bo);
    if(!sig->writeSac(s)) {
      Message::warning(MSG_ID, tr("Saving SAC ..."),
                       tr("Cannot write to file %1").arg(completefilePath), Message::cancel());
      return false;
    }
  }

  return true;
}

bool SubSignalPool::saveMiniSeed(QString filePath) const
{
  TRACE;
  int n=count();
  if(n==0) {
    return false;
  }
  SignalDatabase * db=database();

  QFile f(filePath);
  if(!f.open(QIODevice::WriteOnly) ) {
    Message::warning(MSG_ID, tr("Saving mini seed ..."),
                        tr("Cannot write to file %1").arg(filePath), Message::cancel());
    return false;
  }
  GeopsyCoreEngine::instance()->setProgressMaximum(db, n-1);
  for(int i=0; i<n; i++) {
    GeopsyCoreEngine::instance()->setProgressValue(db, i);
    Signal * sig=at(i);
    if(!sig->writeMiniSeed(f)) {
      Message::warning(MSG_ID, tr("Saving mini seed ..."),
                          tr("Error saving signal %1 to file %2. See log for details.")
                          .arg(sig->nameComponent()).arg(filePath), Message::cancel());
      return false;
    }
  }
  return true;
}

/*!
  Make sure that all signals have the same number of samples and same samplingPeriod
*/
bool SubSignalPool::haveSameSampling(const QString& caption) const
{
  TRACE;
  const_iterator it=begin();
  Signal * sig=*it;
  double samplingPeriod=sig->samplingPeriod();
  int nSamples=sig->nSamples();
  DoubleSignal::SignalType type=sig->type();
  for(++it; it!=end(); ++it) {
    sig=* it;
    if(samplingPeriod!=sig->samplingPeriod() || nSamples!=sig->nSamples() ||
         type!=sig->type()) {
      Message::warning(MSG_ID, caption,
                       tr("The number of samples, the sampling frequency and the "
                          "type must be the same for all exported signals."),
                       Message::cancel());
      return false;
    }
  }
  return true;
}

/*!
  Lock all signals
*/
bool SubSignalPool::lockSamples(const QString& caption, const double ** samples) const
{
  TRACE;
  for(int i=0; i<count(); i++) {
    samples[i]=at(i)->constLockSamples();
    if(!samples[i]) {
      for(i--; i>=0; i--) {
        at(i)->unlockSamples();
      }
      Message::warning(MSG_ID, caption,
                       tr("Cannot use all signals at the same time due to insuficient "
                          "memory buffer for signal samples (see preferences to increase it)"),
                       Message::cancel());
      return false;
    }
  }
  return true;
}

/*!
  Unlock all signals
*/
void SubSignalPool::unlockSamples() const
{
  TRACE;
  // Unlock signals
  for(const_iterator it=begin(); it!=end(); ++it) {
    (*it) ->unlockSamples();
  }
}

bool SubSignalPool::saveWav(QString filePath) const
{
  TRACE;
  static const char * caption=QT_TR_NOOP("Saving WAVE PCM sound file ...");
  if(!haveSameSampling(caption)) {
    return false;
  }
  FILE * f;
  if(!(f=fopen(filePath.toLatin1().data(), "w"))) {
    Message::warning(MSG_ID, tr(caption),
                     tr("Cannot write to signal file"), Message::cancel());
    return false;
  }
  const double ** samples=new const double *[count()];
  if(!lockSamples(caption, samples)) {
    delete [] samples;
    return false;
  }
  const_iterator it=begin();
  Signal * sig=* it;
  double maxAll=0;
  for( ++it; it!=end(); ++it) {
    sig=* it;
    if(sig->maximumAmplitude() > maxAll)
    maxAll=sig->maximumAmplitude();
  }
  double satFac=32768.0/maxAll; // avoid saturation and non integer values
  // Fill in header information
  WaveHeader wavinfo;
  strncpy(wavinfo.chunkID, "RIFF", 4);
  strncpy(wavinfo.format, "WAVE", 4);
  strncpy(wavinfo.subchunk1ID, "fmt ", 4);
  wavinfo.subchunk1Size=16;
  wavinfo.audioFormat=1;
  strncpy(wavinfo.subchunk2ID, "data", 4);
  wavinfo.bitsPerSample=16;
  wavinfo.numChannels=static_cast<qint16>(count());
  wavinfo.sampleRate=qRound(first()->samplingFrequency());
  wavinfo.blockAlign=wavinfo.bitsPerSample * wavinfo.numChannels/8;
  wavinfo.byteRate=wavinfo.blockAlign * wavinfo.sampleRate;
  wavinfo.subchunk2Size=sig->nSamples() * wavinfo.blockAlign;
  wavinfo.chunkSize=wavinfo.subchunk2Size + 36;
  short val;
  int is=-1, i;
  int nSamples=first()->nSamples();
  if(fwrite(&wavinfo, sizeof(WaveHeader), 1, f)==1) {
    for(is=0; is<nSamples; is++) {
      for(i=0; i<count(); i++ ) {
        val=static_cast<qint16>(qRound(samples[i][is]*satFac));
        if(fwrite(&val, sizeof(short), 1, f)!=1) break;
      }
      if(i<count()) break;
    }
  }
  fclose(f);
  unlockSamples();
  delete [] samples;
  return is==nSamples;
}

bool SubSignalPool::saveAsciiOneColumn(QString filePath) const
{
  TRACE;
  QFile f(filePath);
  if(!f.open(QIODevice::WriteOnly) ) {
    Message::warning(MSG_ID, tr("Saving ASCII 1 Column ..."),
                     tr("Cannot write to signal file"), Message::cancel());
    return false;
  }
  for(int i=0; i<count(); i++) {
    at(i)->writeAscii(f, i);
    f.write("\n", 1);
  }
  return true;
}

/*!
  Save samples to a text file. Signals are taken in their current type.

  (per signal)
  \li If time series:     1 column    amplitude 
  \li If fourier spectra: 3 columns   frequency,amplitude, phase
*/
bool SubSignalPool::saveAsciiMultiColumns(QString filePath) const
{
  TRACE;
  static const QString caption(tr("Saving Ascii Multi Columns ..."));
  if(!haveSameSampling(caption)) {
    return false;
  }
  QFile f(filePath);
  if(!f.open(QIODevice::WriteOnly)) {
    Message::warning(MSG_ID, caption,
                     tr("Cannot write to file %1").arg(filePath),
                     Message::cancel());
    return false;
  }
  const double * samples[count()];
  if(!lockSamples(caption, samples)) {
    return false;
  }
  QTextStream s(&f);
  s.setRealNumberPrecision(20);
  s.setRealNumberNotation(QTextStream::SmartNotation);
  if(first()->isReal()) {
    int n=first()->nSamples();
    for(int is=0; is<n; is++) {
      int iSig=0;
      s << samples[iSig][is];
      for(iSig++; iSig<count(); iSig++) {
        s << "\t" << samples[iSig][is];
      }
      s << "\n";
    }
  } else if(first()->isComplex()) {
    Signal * sig;
    //double fNyquist=0.5/sig->samplingPeriod();
    int nSamples2=first()->nSamples() >> 1;
    //double df=fNyquist/(double) nSamples2;
    double df=1.0/first()->duration();
    for(int is=0; is<=nSamples2; is++) {
      int iSig=0;
      sig=at(iSig);
      /*Complex c=sig->complex(samples[iSig], is);
      s << df * (double) is
        << "\t" << c.re()
        << "\t" << c.im();
      for(iSig++; iSig<count(); iSig++ ) {
        sig=at(iSig);
        c=sig->complex(samples[iSig], is);
        s << "\t" << c.re()
          << "\t" << c.im();
      }*/
      s << df * (double) is
        << "\t" << sig->amplitude(samples[iSig], is)
        << "\t" << sig->phase(samples[iSig], is);
      for(iSig++; iSig<count(); iSig++ ) {
        sig=at(iSig);
        s << "\t" << sig->amplitude(samples[iSig], is)
          << "\t" << sig->phase(samples[iSig], is);
      }
      s << "\n";
    }
  }
  unlockSamples();
  return true;
}

/*!
  Save samples to a Cityshark2 file. Signals must all have the same sampling frequency
  and the same number of samples.
*/
bool SubSignalPool::saveCityShark2(QString filePath) const
{
  TRACE;
  static const QString caption(tr("Saving Cityshark2 ..."));
  if(!haveSameSampling(caption)) {
    return false;
  }
  QFile f(filePath);
  if(!f.open(QIODevice::WriteOnly)) {
    Message::warning(MSG_ID, caption,
                     tr("Cannot write to file %1").arg(filePath),
                     Message::cancel());
    return false;
  }
  // Lock all signals
  const double * samples[count()];
  if(!lockSamples(caption, samples)) {
    return false;
  }
  QTextStream s(&f);
  // Some header information can be available in comments
  QString cmt=first()->metaData<Comments>().value();
  QRegExp cmtGrep;
  cmtGrep.setPattern("Station serial number *: *([0-9]+)");
  cmtGrep.indexIn(cmt);
  QString stationSerialNumber=cmtGrep.cap(1);
  cmtGrep.setPattern("Station software version *: *([0-9]+)");
  cmtGrep.indexIn(cmt);
  QString stationSoftwareVersion=cmtGrep.cap(1);
  cmtGrep.setPattern("Ending date *: *([0-9\\.]+)");
  cmtGrep.indexIn(cmt);
  QString endingDate=cmtGrep.cap(1);
  cmtGrep.setPattern("Ending time *: *([0-9:\\.]+)");
  cmtGrep.indexIn(cmt);
  QString endingTime=cmtGrep.cap(1);
  cmtGrep.setPattern("Recording duration *: *([0-9]+) *mn");
  cmtGrep.indexIn(cmt);
  QString duration=cmtGrep.cap(1);
  cmtGrep.setPattern("Clipped samples *: *([0-9\\.]+) *%");
  cmtGrep.indexIn(cmt);
  double clippedSamples=cmtGrep.cap(1).toDouble();
  cmtGrep.setPattern("Latitude *: *([0-9\\. NS]+)");
  cmtGrep.indexIn(cmt);
  QString latitude=cmtGrep.cap(1);
  cmtGrep.setPattern("Longitude *: *([0-9\\. EW]+)");
  cmtGrep.indexIn(cmt);
  QString longitude=cmtGrep.cap(1);
  cmtGrep.setPattern("Altitude *: *([0-9]+) *m");
  cmtGrep.indexIn(cmt);
  QString altitude=cmtGrep.cap(1);
  cmtGrep.setPattern("No. satellites *: *([0-9]+)");
  cmtGrep.indexIn(cmt);
  QString numSatellites=cmtGrep.cap(1);
  cmtGrep.setPattern("Maximum amplitude *: *([0-9 /]+)");
  cmtGrep.indexIn(cmt);
  QString maxAmplitude=cmtGrep.cap(1);
  cmtGrep.setPattern("Gain *: *([0-9]+)");
  cmtGrep.indexIn(cmt);
  int gain=cmtGrep.cap(1).toInt();
  DateTime startTime=first()->startTime();
  double countPervolt=CitySignal::conversionFactor(qRound(first()->samplingFrequency()), gain);
  // Note that conversion factor stored in the text header does not contain
  // the gain factor.
  s << "Original file name: " << first()->name() << "\n"
    << "Geopsy version: " << CoreApplication::instance()->version("GeopsyCore") << "\n"
    << "Station serial number: " << stationSerialNumber << "\n"
    << "Station software version: " << stationSoftwareVersion << "\n"
    << "Channel number: " << count() << "\n"
    << "Starting date: " << startTime.toString("dd.MM.yyyy") << "\n"
    << "Starting time: " << startTime.toString("hh:mm:ss.zzz") << "\n"
    << "Ending date: " << endingDate << "\n"
    << "Ending time: " << endingTime << "\n"
    << "Sample rate: " << first()->samplingFrequency() << " Hz\n"
    << "Sample number: " << first()->nSamples() << "\n"
    << "Recording duration: " << duration << " mn\n"
    << "Conversion factor: " << countPervolt/gain << "\n"
    << "Gain: " << gain << "\n"
    << "Dynamic range: 5 V\n"
    << "Clipped samples: " << QString::number(clippedSamples, 'f', 2) << "%\n"
    << "Latitude : " << latitude << "\n"
    << "Longitude: " << longitude << "\n"
    << "Altitude : " << altitude << " m\n"
    << "No. satellites: " << numSatellites << "\n"
    << "Maximum amplitude: " << maxAmplitude << "\n";
  if(first()->isReal()) {
    int n=first()->nSamples();
    for(int is=0; is<n; is++) {
      int iSig=0;
      s << qRound(samples[iSig][is]*countPervolt);
      for(iSig++; iSig<count(); iSig++) {
        s << "\t" << qRound(samples[iSig][is]*countPervolt);
      }
      s << "\n";
    }
  }
  unlockSamples();
  return true;
}

/*!
  The signals must be saved in triplets (Vertical, North and East component).
  First we sort the subpool by receiver name, t0, and component.
  One file will be created by receiver name and t0.
  We test if sampling rate and number of samples is the same for all components.
  Divide the subpool into triplet before calling SaveOneSaf().
*/
bool SubSignalPool::saveSaf(QString filePath) const
{
  TRACE;
  // Sorting
  SubSignalPool tmp_subPool( *this);
  SortKey::clear();
  SortKey::add(MetaDataFactory::Name);
  SortKey::add(MetaDataFactory::Component);
  SortKey::add(MetaDataFactory::StartTime);
  tmp_subPool.sort();
  // Testing if all 3 components are present (warning only, order=Vert,East, North))
  // Testing if nsamples and sampling rate are constant
  SubSignalPool triplet;
  double samplingPeriod=0;
  int nSamp=0;
  DateTime startTime;
  QString stat;
  Point rec;
  // counter to name files '_000' if more than 3 channels
  int iFile=0;
  if(count()>3) {
    if(filePath.endsWith(".saf")) {
      filePath.chop(4);
      filePath+="_%1.saf";
    } else {
      filePath+="_%1";
    }
  }
  for(const_iterator it=begin(); it!=end(); ++it) {
    Signal * sig=*it;
    if(samplingPeriod==0.0) {
      samplingPeriod=sig->samplingPeriod();
      nSamp=sig->nSamples();
      startTime=sig->startTime();
      stat=sig->name();
      rec=sig->receiver();
    }
    if(sig->name()==stat && sig->receiver()==rec && sig->startTime()==startTime) {
      if(sig->samplingPeriod()!=samplingPeriod || sig->nSamples()!=nSamp) {
        Message::warning(MSG_ID, tr("Saving ASCII Saf ..."),
                         tr("For station %1, the sampling rate or the number of samples "
                            "is not the same for all component, skiping %2 component." ).arg(stat).arg(sig->component()),
                         Message::ok(), true);
      } else
        triplet.append(sig);
    } else {
      if(count()>3) {
        if(!triplet.saveOneSaf(filePath.arg(iFile, 3, 10, QChar('0')))) return false;
      } else {
        if(!triplet.saveOneSaf(filePath)) return false;
      }
      triplet.clear();
      samplingPeriod=sig->samplingPeriod();
      nSamp=sig->nSamples();
      startTime=sig->startTime();
      stat=sig->name();
      rec=sig->receiver();
      triplet.append(sig);
      iFile++;
    }
  }
  if(!triplet.isEmpty()) {
    if(count()>3) {
      if(!triplet.saveOneSaf(filePath.arg(iFile, 3, 10, QChar('0')))) return false;
    } else {
      if(!triplet.saveOneSaf(filePath)) return false;
    }
  }
  return true;
}

/*!
  - First line :  "SESAME ASCII data format (saf) v. 1  "
  - Header information
  - Empty lines are allowed
  - Comments can be added provided that the first character in the row
    is "#"
  - Keywords are not case sensitive
  - Separator between keywords and their values is "="
  - The order of the keywords is irrelevant
  - Blanks can be added everywhere
  - Data begin after this separator  line : "####--------------------"
  - Data are written in  three columns free format
  - Column 1 must correspond to the Vertical channel, columns 2 and 3 to
    the horizontal ones

  Mandatory keywords are :
  - STA_CODE : station/site code (In the GSE2.0 format this parameter is
               at most 5 characters long)
  - START_TIME : start date and time - year month day hour minute second
                 (e.g. 1999 4 23 0 3 44.78)
  - SAMP_FREQ : sampling frequency in Hertz
  - NDAT : number of samples
  - CH0_ID : component 1 definition - It should be the vertical channel.
             It is a label. (In the GSE2.0 format this parameter is  3
             characters long)
  - CH1_ID : component 2 definition - horizontal 
  - CH2_ID : component 3 definition - horizontal
  - UNITS : label (e.g. m/s)

  optional keyword:
  -  NORTH_ROT : is the orientation of the first horizontal component
                 (channel 1 - column 2) from North clockwise (degrees)
*/
bool SubSignalPool::saveOneSaf(QString filePath) const
{
  TRACE;
  if(count()!=3) {
    ASSERT(count()>0);
    Message::warning(MSG_ID, tr("Saving ASCII Saf ..."),
                     tr("Missing components for station %1").arg(first()->name()), Message::cancel());
    return false;
  }
  TRACE_BUG;
  const Signal * sig=at(0);
  TRACE_BUG_N(1);
  int nsamp=sig->nSamples();
  QFile f(filePath);
  if(!f.open(QIODevice::WriteOnly)) {
    Message::warning(MSG_ID, tr("Saving ASCII Saf ..."),
                     tr( "Cannot open signal file %1" ).arg(filePath), Message::cancel());
    return false;
  }
  QTextStream s(&f);
  TRACE_BUG_N(2);
  s << "SESAME ASCII data format (saf) v. 1\n"
       "# Noise recording\n"
       "# Original file name : geopsy Database\n"
       "STA_CODE=" << sig->name() << "s\n"
       "# Geographical Coordinates=" << sig->receiver().toString() << "\n"
       "START_TIME=" << sig->startTime().toString("yyyy MM dd hh mm ssz") << "\n"
       "SAMP_FREQ=" << 1.0/(sig->samplingPeriod()) << "\n"
       "NDAT=" << nsamp << "\n"
       "CH0_ID=Z\nCH1_ID=N\nCH2_ID=E\n"
       "# DIGITIZER TYPE:         \n"
       "# SENSOR TYPE:      \n"
       "UNITS=         \n"
       "NORTH_ROT=0\n"
       "# DATA CONVERSION FACTOR=" << sig->countPerVolt() << "\n"
       "#        from UNITS to SI units\n"
       "####-------------------------------------------\n";
  const double * data[3];
  const DoubleSignal * sigs[3];
  TRACE_BUG_N(3);
  if(sig->component()==Signal::Vertical) {
    sigs[0]=sig->saveType(Signal::Waveform);
    data[0]=sigs[0]->constLockSamples();
  } else {
    sigs[0]=nullptr;
  }
  sig=at(1);
  if(sig->component()==Signal::North) {
    sigs[1]=sig->saveType(Signal::Waveform);
    data[1]=sigs[1]->constLockSamples();
  } else {
    sigs[1]=nullptr;
  }
  sig=at(2);
  if(sig->component()==Signal::East) {
    sigs[2]=sig->saveType(Signal::Waveform);
    data[2]=sigs[2]->constLockSamples();
  } else {
    sigs[2]=nullptr;
  }
  TRACE_BUG_N(4);
  if(data[0]==nullptr || data[1]==nullptr || data[2]==nullptr) {
    Message::warning(MSG_ID, tr("Saving ASCII Saf ..."),
                         tr("For station %1, at least one component is missing or not available")
                         .arg(first()->name()), Message::ok(), true);
  }
  for(int i=0; i<nsamp; i++) {
    for(int iComp=0; iComp<3; iComp++) {
      if(data[iComp]) {
        s << data[iComp][i] << " ";
      } else {
        s << "0 ";
      }
    }
    s << "\n";
  }
  TRACE_BUG_N(5);
  for(int iComp=0; iComp<3; iComp++) {
    if(sigs[iComp] ) {
      sigs[iComp]->unlockSamples();
      at(iComp)->restoreType(sigs[iComp]);
    }
  }
  TRACE_BUG_N(6);
  return true;
}

bool SubSignalPool::saveGse2(QString filePath) const
{
  TRACE;
  QFile f(filePath);
  if(!f.open(QIODevice::WriteOnly)) {
    Message::warning(MSG_ID, tr( "Saving GSE 2.0 file ..."),
                     tr( "Cannot open signal file %1").arg(filePath) , Message::cancel());
    return false;
  }
  int n=count();
  for(int i=0; i<n; i++) {
    if(!at(i)->writeGse(f)) {
      Message::warning(MSG_ID, tr("Saving GSE ..."),
                       tr("Cannot write to file %1").arg(filePath), Message::cancel());
      return false;
    }
  }
  return true;
}

QStringList SubSignalPool::availableTimePicks() const
{
  TRACE;
  QStringList names;
  for(const_iterator it=begin(); it!=end(); ++it) {
    names.append((*it)->metaData<TimePick>().names());
  }
  std::sort(names.begin(), names.end());
  unique(names);
  return names;
}

/*!
  Return the database to which the signals belong. This function returns
  a null pointer if this SubSignalPool is empty.
*/
SignalDatabase * SubSignalPool::database() const
{
  TRACE;
  if(empty()) {
    return nullptr;
  } else {
    return first()->database();
  }
}

bool SubSignalPool::contains(const Signal * sig) const
{
  if(count()<10) {
    return QList<Signal *>::contains(const_cast<Signal *>(sig));
  } else {
    if(_lookup.count()!=count()) {
      _lookup.clear();
      for(QList<Signal *>::const_iterator it=begin(); it!=end(); it++) {
        _lookup.insert(*it);
      }
    }
    return _lookup.contains(sig);
  }
}

/*!
  Return a map of name-component. The value is an index from 0 to name-component count.
*/
QMap<QString,int> SubSignalPool::signalNames() const
{
  QMap<QString,int> names;
  for(SubSignalPool::const_iterator it=begin(); it!=end(); it++) {
    names.insert((*it)->nameComponent(), 0);
  }
  int i=0;
  for(QMap<QString,int>::iterator it=names.begin(); it!=names.end(); it++) {
    it.value()=i++;
  }
  return names;
}

DateTime SubSignalPool::firstTimePick(const QString& timePick) const
{
  DateTime min=DateTime::maximumTime;
  for(SubSignalPool::const_iterator it=begin(); it!=end(); it++) {
    const Signal * sig=*it;
    DateTime t=sig->metaData<TimePick>().value(timePick);
    if(t<min) {
      min=t;
    }
  }
  return min;
}

DateTime SubSignalPool::lastTimePick(const QString& timePick) const
{
  DateTime max=DateTime::minimumTime;
  for(SubSignalPool::const_iterator it=begin(); it!=end(); it++) {
    const Signal * sig=*it;
    DateTime t=sig->metaData<TimePick>().value(timePick);
    if(t>max) {
      max=t;
    }
  }
  return max;
}

} // namespace GeopsyCore
