/***************************************************************************
**
**  This file is part of GeopsyCore.
**
**  GeopsyCore is free software: you can redistribute it and/or modify
**  it under the terms of the GNU General Public License as published by
**  the Free Software Foundation, either version 3 of the License, or
**  (at your option) any later version.
**
**  GeopsyCore is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**  GNU General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2006-03-20
**  Copyright: 2006-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <QGpCoreTools.h>

#include "SignalDatabase.h"
#include "SubSignalPool.h"
#include "GeoSignal.h"
#include "SignalGroupFolder.h"
#include "SignalGroup.h"
#include "GeopsyCoreEngine.h"
#include "SharedMetaData.h"
#include "GeopsyPreferences.h"
#include "Comments.h"
#include "KeepSignal.h"

namespace GeopsyCore {

  const QString SignalDatabase::xmlSignalDatabaseTag="SignalDatabase";

  SignalDatabase::SignalDatabase() : _files(this)
  {
    TRACE;
    _version=SIGNAL_DATABASE_CURRENT_VERSION;
    _masterGroup=new MasterSignalGroup(this);
    _masterGroup->setName("/");
    _masterGroup->setComments(tr("Root folder of all groups"));
    _masterGroup->setModified(false);
    _seismicEvents=new SeismicEventTable;
    _allSharedMetaData=nullptr;
  }

  SignalDatabase::~SignalDatabase()
  {
    TRACE;
    delete _masterGroup;
    delete _seismicEvents;
    // Prepare all signals for delete
    for(const_iterator it=begin(); it!=end(); ++it) {
      (*it)->setDatabase(nullptr);  /* avoid removal from internal list upon deletion
                                       (this will be done by SubSignalPool destructor).*/
    }
    ASSERT(!_allSharedMetaData);
  }

  void SignalDatabase::clear()
  {
    TRACE;
    removeAll();
    setName(QString());
    _files.clear();
    _permanentSharedMetaData.clear();
    _masterGroup->clear();
    _seismicEvents->clear();
  }

  /*!
    Return true if samples, header or groups are touched since last save or open
    Also return true if there are temporary files
  */
  bool SignalDatabase::isModified ()
  {
    TRACE;
    //App::log(tr("Checking state of database:\n") );
    if(isHeaderModified()) return true;
    //App::log(tr("  No header modified\n") );
    if(_masterGroup->isModified()) return true;
    //App::log(tr("  No group modified\n") );
    // scan for temporary files
    for(int i=0; i<_files.count(); i++) {
      SignalFile * fsig=_files.at(i);
      if(fsig->format().id()==SignalFileFormat::Temporary ||
         fsig->isPathModified()) {
        return true;
      }
    }
    //App::log(tr("  No temporary files\n") );
    // scan for temporary signals
    int tempSignalToSave;
    QString tempSignalList;
    temporarySignals(tempSignalList, tempSignalToSave);
    if(tempSignalToSave>0) {
      return true;
    }
    //App::log(tr("  No temporary signals\n") );
    return _seismicEvents->isModified();
  }

  /*!
    Returns pointer to signal with \a id or 0 if no signal has \a id.
  */
  Signal * SignalDatabase::signal(int id) const
  {
    TRACE;
    for(SubSignalPool::const_iterator it=begin(); it!=end(); ++it) {
      Signal * sig=*it;
      if(sig->id()==id) {
        return sig;
      }
    }
    return nullptr;
  }

  /*!
    Open group for database version 1 and 2
  */
  void SignalDatabase::openGroupsV12()
  {
    TRACE;
    if(_version<3) {
  #define YACL_String_CLASSID 6
  #define YACL_StringSequence_CLASSID 25
      QFileInfo fi(_name);
      QDir d(fi.path());
      QFile f(d.absoluteFilePath(fi.baseName() + ".grouplist" ));
      if(f.open(QIODevice::ReadOnly) ) {
        QDataStream s(&f);
        if(_version==2) {
          QStringList groupList;
          s >> groupList;
          for(QStringList::iterator it=groupList.begin();it!=groupList.end();++it) {
            QString gName=*it;
            int i=1;
            while(findGroup( gName) ) {
              gName=*it + "_" + i;
              i++;
            }
            SignalGroup* g=new SignalGroup(_masterGroup);
            g->setName(gName);
            g->openV12(_version, d);
          }
        } else {
          s.setByteOrder(QDataStream::LittleEndian); // To read file from YACL
          // read .grouplist file created by YACL libraries in Geopsy Version 1
          // test if classID (type is int 4 bytes) is correct
          int classID;
          s >> classID;
          if(classID!=YACL_StringSequence_CLASSID) {
            Message::warning(MSG_ID, tr( "Opening Database Version 1..." ),
                                tr( "Bad file format for gouplist file: wrong class ID"
                                    " for CL_Sequence. No group will be available." ), Message::ok());
            return ;
          }
          // read size of sequence (number of strings)
          int nstr;
          s >> nstr;
          for(int i=0;i < nstr;i++ ) {
            // test if classID (type is int 4 bytes) is correct
            s >> classID;
            if(classID!=YACL_String_CLASSID) {
              Message::warning(MSG_ID, tr( "Opening Database Version 1..." ),
                                  tr( "Bad file format for gouplist file: wrong class ID"
                                      " for CL_String. No group will be available." ), Message::ok());
              return ;
            }
            // read size of string
            int strlen;
            s >> strlen;
            char * str=new char[ strlen ];
            s.readRawData(str, strlen);
            SignalGroup * g=new SignalGroup(_masterGroup);
            g->setName(str);
            g->openV12(_version, d);
            delete [] str;
          }
        }
      }
    }
  }

  /*!
    Open .sdb file in current path (accept all previous versions 1 & 2)

    Kept only for compatibility
  */
  bool SignalDatabase::openDBFileV12(QString fileName)
  {
    TRACE;
    QFileInfo fi(fileName);
    QDir d(fi.path());
    FILE * f=fopen(fileName.toLatin1().data(), "rb" );
    if( !f) {
      Message::warning(MSG_ID, "Opening database ...",
                           "Unable to open database file", Message::cancel());
      return false;
    }

    int bufLen=256;
    char * buf=new char [ bufLen ];
    File::readLine(buf, bufLen, f, true);
    // Check Geopsy's version, the old Windows and YACL version is number 1
    // Grouplist and all binary files of database have a different format
    if(strncmp( buf, "Version ", 8)==0) {
      sscanf(buf + 8, "%i", &_version);
    } else {
      _version=1;
    }

    bool ret;
    switch (_version) {
    case 1:
      if(Message::warning(MSG_ID, tr( "Opening Database Version 1" ),
                                tr( "You are trying to open a database created under Version 1. "
                                    "After saving it with this version it won't be possible "
                                    "to open it with Sardine (Version 1.0)." ),
                                Message::ok(), Message::cancel())==Message::Answer1) {
        ret=false;
        break;
      }
      {
        QSettings& reg=CoreApplication::instance()->settings();
        // Set format as "Automatic recognition" and avoid dialog box during loading
        reg.beginGroup("DialogOptions");
        reg.beginGroup("LoadFormat");
        reg.setValue("again", (bool) false);
        reg.setValue("fileFormat", 0);
        reg.endGroup();

        // Most of the signals from version 1 are SEG2 from active experiments
        // Set default preference to keep T0 as is
        // TODO : currently no way to change preferences directly, define interface
        reg.beginGroup("Preferences");
        reg.beginGroup("preferenceTab");
        reg.beginGroup("qt_tabwidget_stackedwidget");
        reg.beginGroup("loadTab");
        reg.beginGroup("timeReferenceGroup");
        reg.setValue("timeReferenceAsk",false);
        reg.setValue("timeReferenceFixedDate",false);
        reg.setValue("timeReferenceFixedTime",false);
        reg.setValue("timeReferenceNoCommon",true);
        GeopsyCoreEngine::instance()->preferences()->load();
      }

      while( !feof(f) ) {
        _files.load(d.absoluteFilePath(buf) );
        File::readLine(buf, bufLen, f, true);
      }
      if(strlen( buf) > 0)
        _files.load(d.absoluteFilePath(buf) );
      ret=true;
      break;
    case 2:
      if(Message::warning(MSG_ID, tr( "Opening Database Version 2..." ),
                                tr( "Geopsy database format has changed to version %1.\n\n"
                                    "Importing version 2 database is relatively slow. Convert it "
                                    "to the new format by simply saving it once. On next loading, "
                                    "it will be drastically faster but it won't be "
                                    "possible to open it with old Geopsy releases (<=200600825)." )
                                .arg(SIGNAL_DATABASE_CURRENT_VERSION),
                                Message::ignore(), Message::cancel())==Message::Answer1) {
        ret=false;
        break;
      }
#if __clang__
      [[clang::fallthrough]];
#endif
   default:
      ret=openDBFileV2 (f); // Kept also for transitional version 3 (devel only)
      break;
    }
    delete [] buf;
    fclose(f);
    return ret;
  }

  /*!
    Load Geopsy database version 2
    Return false if not completely loaded

    Kept for compatibility
  */
  bool SignalDatabase::openDBFileV2(FILE * f)
  {
    TRACE;
    int bufLen=256;
    char * buf=new char [ bufLen ];

    // Count the number of files or read it from header (use only for progress)
    int nFiles;
    int startOffset=ftell(f);

    nFiles=0;
    while( !feof(f) ) {
      File::readLine(buf, bufLen, f, true);
      if(strncmp( buf, "FILE=", 5)==0)
        nFiles++;
    }
    fseek(f, startOffset, SEEK_SET);
    GeopsyCoreEngine::instance()->setProgressMaximum(this, nFiles - 1);

    int iProgress=0;
    File::readLine(buf, bufLen, f, true);
    while(strncmp( buf, "FILE=", 5)==0) {
      GeopsyCoreEngine::instance()->setProgressValue(this, iProgress++ );
      bool pathModified=false;
      QString fileName(buf + 5);
      QFileInfo fi(fileName);
      if(!fi.exists()) {
        // Try to translate the path
        PathTranslatorOptions options;
        options.setTitle(tr("Opening Database..."));
        options.setFileType(tr("Signal file (%1)"));
        QString fn=CoreApplication::instance()->translatePath(fileName, options);
        fi.setFile(fn);
        if( !fn.isEmpty() && fi.exists()) {
            pathModified=true;
        } else {
          if(Message::warning(MSG_ID, tr("Opening Database ..."),
                              tr("Cannot find file %1").arg(fi.filePath()),
                              Message::ignore(), Message::cancel(), true)==Message::Answer1)
            return false;
        }
      }
      File::readLine(buf, bufLen, f, true);
      SignalFileFormat fileFormat;
      if(strncmp( buf, "FORMAT=", 7)==0)
        fileFormat=SignalFileFormat((SignalFileFormat::Format)atoi(buf + 7));
      if(fileFormat.id()==SignalFileFormat::Unknown) {
        if(Message::warning(MSG_ID, tr("Opening Database ..."),
                                  tr("You sdb file may be corrupted, the format of file %1 "
                                     "is missing.").arg(fileName),
                                  Message::ignore(), Message::cancel(), true)
                                 ==Message::Answer1) {
          removeAll();
          delete [] buf;
          return false;
        }
        File::readLine(buf, bufLen, f, true);
        while(strncmp( buf, "FILE=", 5)!=0 && !feof(f) ) {
          File::readLine(buf, bufLen, f, true);
        }
        continue;
      }
      int offset=ftell(f);
      File::readLine(buf, bufLen, f, true);
      bool isOriginal=true;
      if(strncmp( buf, "ISORIGINALFILE=", 15)==0)
        isOriginal=(atoi( buf + 15)==1);
      else
        fseek(f, offset, SEEK_SET);
      if(fi.exists()) {
        SignalFile * newFile=new SignalFile(this, fi.absoluteFilePath(), fileFormat);
        newFile->setOriginalFile(isOriginal);

        // Only for version 2 compatibility
        SubSignalPool * fileSubPool=nullptr;
        int fileSubPoolIndex;
        SAFE_UNINITIALIZED(fileSubPoolIndex,0)
        if(_version<3) { // Kept also for transitional version 3 (devel only)
          if(!newFile->load()) { // Force loading now, delayed loading has been abandoned
            App::log(tr("Skipping file %1\n").arg(fi.filePath()) );
            while(strncmp( buf, "FILE=", 5)!=0 && !feof(f) ) {
              File::readLine(buf, bufLen, f, true);
            }
            continue;
          }
          fileSubPool=new SubSignalPool;
          fileSubPool->addFile(newFile);
          fileSubPoolIndex=0;
        }

        Signal * sig;
        int offset=ftell(f);
        do {
          if(_version>=3) { // Kept also for transitional version 3 (devel only)
            sig=new Signal(this);
            sig->setFile(newFile);
          } else { // Only for version 2 compatibility
            sig=fileSubPool->at(fileSubPoolIndex);
            fileSubPoolIndex++;
          }
          fseek(f, offset, SEEK_SET);
          if(!sig->read(f, this)) {
            if(Message::warning(MSG_ID, tr( "Opening Database ..." ),
                                      tr( "You sdb file may be corrupted, the header information of file %1 "
                                          "may be incompletely loaded." ).arg(fileName),
                                      Message::ignore(), Message::cancel(), true)
                                     ==Message::Answer1) {
              removeAll();
              delete [] buf;
              return false;
            }
            File::readLine(buf, bufLen, f, true);
            while(strncmp( buf, "FILE=", 5)!=0 && !feof(f) ) {
              File::readLine(buf, bufLen, f, true);
            }
            delete sig;
            break;
          }
          if(pathModified)
            sig->setHeaderModified(true);
          offset=ftell(f);
          File::readLine(buf, bufLen, f, true);
        } while(strncmp( buf, "ID=", 3)==0 && !feof(f) );
        delete fileSubPool;
        if(strncmp( buf, "FILE=", 5)==0 || feof(f) ) {
          App::log(tr("file %1 loaded\n").arg(fi.fileName()));
        }
      } else {
        // Skip header data linked to skipped file
        App::log(Message::severityString(Message::Warning)
                 +tr(" file %1 skipped\n").arg(fi.fileName()));
        File::readLine(buf, bufLen, f, true);
        while(strncmp( buf, "FILE=", 5)!=0 && !feof(f)) {
          File::readLine(buf, bufLen, f, true);
        }
      }
    }
    delete [] buf;
    return true;
  }

  /*!
    In normal situation this function should always return 0 or 1. If an ID is duplicated, it returns a value > 1.
  */
  int SignalDatabase::countId(int id, int& newId) const
  {
    TRACE;
    int n=0;
    for(const_iterator it=begin(); it!=end(); ++it) {
      int idSig=(*it)->id();
      if(idSig==id) {
        n++;
      }
      if(idSig>newId) {
        newId=idSig;
      }
    }
    return n;
  }

  /*!
    Removes file \a f from database and delete its signals.
  */
  void SignalDatabase::removeFile(SignalFile * f)
  {
    TRACE;
    SubSignalPool subpool;
    subpool.addFile(f);
    for(SubSignalPool::iterator it=subpool.begin(); it!=subpool.end(); ++it) {
      Signal * sig=*it;
      remove(sig);
      _masterGroup->removeSignal(sig);
    }
    f->removeFile();
    _files.remove(f);
  }

  /*!
    \fn void SignalDatabase::removeSignal(Signal * sig)
    Removes one signal from database and delete it through reference mechanism.
  */

  /*!
    Removes \a sig from database. Do not call directly this function, it is
    automatically called from Signal::~Signal().
  */
  void SignalDatabase::signalDeleted(Signal * sig)
  {
    TRACE;
    int i=QList<Signal *>::indexOf(sig);
    QList<Signal *>::removeAt(i);
  }

  QString SignalDatabase::askSaveAs()
  {
    TRACE;
    return Message::getSaveFileName(tr("Save current database as ..."), tr("Geopsy database (*.gpy)"));
  }

  bool SignalDatabase::saveAs(QString fileName)
  {
    TRACE;
    if(fileName.isEmpty()) {
      fileName=askSaveAs();
    }
    if(!fileName.isEmpty()) {
      _name=fileName;
      _version=SIGNAL_DATABASE_CURRENT_VERSION;
      return save();
    } else {
      return false;
    }
  }

  bool SignalDatabase::save()
  {
    TRACE;
    if(!exists()) {
      return saveAs();
    }
    MessageContext mc;
    if(_version<SIGNAL_DATABASE_CURRENT_VERSION) {
      if(_version<3) {
        Message::information(MSG_ID, tr("Converting an old Geopsy or Sardine database"),
                                 tr("Groups are no longer saved in separate files, after conversion, you can "
                                 "safely remove all files .group, .grouplist and .sdb. The new database is saved "
                                 "in a .gpy file which include all signal and group information. This is in fact a "
                                 " .tar.gz that you can unpack to access its XML contents."));
        return saveAs();
      } else {
        Message::information(MSG_ID, tr("Converting an old Geopsy database"),
                                 tr("Signal database internal format has changed and the saved file will "
                                    "not be readable with old Geopsy releases."));
        return saveAs();
      }
    }
    // Here, database may not yet exist on the disk but exist() returns always true.
    // Saving temporary files and signals and modified samples
    if(!saveTemporaryFiles()) {
      return false;
    }
    if(!saveTemporarySignals()) {
      return false;
    }
    // Saving database file
    XMLHeader hdr(this);
    createAllSharedMetaData();
    collectAllSharedMetaData();
    XMLClass::Error err=hdr.xml_saveFile(_name);
    releaseAllSharedMetaData();
    if(err!=XMLClass::NoError) {
      return false;
    }
    setHeaderModified(false);
    for(int i=0; i<_files.count(); i++) {
      _files.at(i)->setPathModified(false);
    }
    _masterGroup->setModified(false);
    return true;
  }

  bool SignalDatabase::open(QString fileName)
  {
    TRACE;
    if(fileName.isEmpty()) {
      return false;
    }
    QFileInfo fi(fileName);
    fileName=fi.absoluteFilePath();

    if(count()>0) {
      if(Message::warning(MSG_ID, tr("Open database"),
                          tr("The current database already contains signals, Do you want to add signals from another database?"),
                          Message::yes(), Message::no())==Message::Answer1) {
        return false;
      }
      SignalDatabase * newDB=new SignalDatabase;
      if(!newDB->open(fileName)) {
        delete newDB;
        return false;
      } else { // New database opened successfully, merge it with current one
        add(newDB);
        delete newDB;
        // Set name as empty to force saveAs()
        _name=QString();
        return true;
      }
    } else {
      _name=fileName;
    }

    XMLHeader hdr(this);
    hdr.addAlternateTag("SignalDB"); // Kept for compatibility for version <6
    createAllSharedMetaData();
    // Passing this as XML context is unused...
    XMLClass::Error err=hdr.xml_restoreFile(fileName, this);
    releaseAllSharedMetaData();
    bool ret;
    switch (err) {
    case ErrorNoDocType:
    case ErrorNoVersion:
    case ErrorEndTagNotFound: // Apparently not an XML file, hence try old formats
      App::log(tr("Trying with an old geopsy database format...\n") );
      ret=openDBFileV12(fileName);
      openGroupsV12();  // in any case, try to open groups
      break;
    default:
      ret=(err==NoError);
      break;
    }
    if(!ret) {
      App::log(XMLClass::message(err, fileName)+"\n");
    }
    return ret;
  }

  /*!
    Adds database \a o to this one (files, signals, permanent metadata and groups).
  */
  bool SignalDatabase::add(SignalDatabase * o)
  {
    TRACE;
    // Files
    _files.add(o->_files);
    // Signals
    for(const_iterator it=o->begin(); it!=o->end(); it++) {
      Signal * sig=*it;
      sig->setDatabase(this);
      addSignal(sig); // sets a new id and adds it.
    }
    // Permanent metadata
    _permanentSharedMetaData.add(o->_permanentSharedMetaData);
    // Groups
    SignalGroupFolder * f=new SignalGroupFolder(_masterGroup);
    QFileInfo fi(o->name());
    f->setName(fi.fileName());
    f->setComments(tr("Groups imported from %1").arg(fi.filePath()));
    f->takeChildren(o->_masterGroup);
    return true;
  }

  /*!
    Returns the directory where to save new files generated by Geopsy
    (signals, results,...).
  */
  QDir SignalDatabase::newFilePath() const
  {
    TRACE;
    if(exists()) {
      QFileInfo fi(name());
      QString fileBaseName=fi.baseName()+".files";
      QDir d(fi.absolutePath());
      if(!d.exists(fileBaseName)) {
        d.mkdir(fileBaseName);
      }
      d.cd(fileBaseName);
      return d;
    } else {
      return QDir();
    }
  }

  /*!
    Inspects database for temporary files and save them. Temporary file are saved in a way that no new
    Signal structure is added to the database, the signals are save in place.
    Returns false in case of error.
  */
  bool SignalDatabase::saveTemporaryFiles()
  {
    TRACE;
    // First scan for temporary files
    QString tempFileList;
    int nf=0;
    for(int i=0; i<_files.count(); ++i) {
      SignalFile * fsig=_files.at(i);
      if(fsig->format().id()==SignalFileFormat::Temporary) {
        tempFileList+="\n"+fsig->shortName();
        nf++;
      }
    }
    if(tempFileList.isEmpty()) return true;
    QString stateMsg, question;
    if(nf>1) {
      stateMsg=tr("Current database contains %1 temporary files").arg(nf);
      question=tr("Do you want to save them?");
    } else {
      stateMsg=tr("Current database contains a temporary file");
      question=tr("Do you want to save it?");
    }
    switch(Message::question(MSG_ID, tr("Saving Database ..."),
                             tr("%1 (e.g. from an acquisition device).\n\n"
                                "%2\n\n"
                                "List of temporary file(s):\n%3")
                             .arg(stateMsg).arg(question).arg(tempFileList))) {
    case Message::Answer0: break;
    case Message::Answer1: return true;
    case Message::Answer2: return false;
    }
    QDir d=newFilePath();
    bool ret=true;
    for(int i=0; i<_files.count(); ++i) {
      SignalFile * fsig=_files.at(i);
      if(fsig->format().id()==SignalFileFormat::Temporary) {
        if(fsig->save(d) ) {
          App::log(tr("Temporary file %1 saved in %2\n").arg(fsig->shortName()).arg(d.canonicalPath()) );
        } else {
          ret=false;
        }
      }
    }
    return ret;
  }

  /*!
    Return all temporary signals that belongs to a group.
    A map is returned that sort all signal by group.
  */
  QMap<const AbstractSignalGroup *, SubSignalPool> SignalDatabase::temporarySignals(QString& tempSignalList, int& tempSignalToSave) const
  {
    tempSignalList.clear();
    tempSignalToSave=0;
    QMap<const AbstractSignalGroup *, SubSignalPool> subPools;
    QMap<const AbstractSignalGroup *, SubSignalPool>::iterator itSubPools;
    QString sigReport(tr("\nSignal ID %1, named '%2'%3"));
    for(const_iterator it=begin(); it!=end(); ++it) {
      Signal * sig=*it;
      if(!sig->file()) {
        QList<const AbstractSignalGroup *> groupList=masterGroup()->find(sig);
        if(!groupList.isEmpty()) {
          QString groupNames;
          QList<const AbstractSignalGroup *>::iterator it=groupList.begin();
          if(it!=groupList.end()) {
            groupNames=tr(" in group(s):\n    * ")+(*it)->pathName();
            for(it++; it!=groupList.end(); it++ ) {
              groupNames+="\n      "+(*it)->name();
            }
          }
          tempSignalList+=sigReport.arg(sig->id()).arg(sig->name()).arg(groupNames);
          itSubPools=subPools.find(groupList.first());
          if(itSubPools==subPools.end()) {
            SubSignalPool subPool;
            subPool.addSignal(sig);
            subPool.setName(groupList.first()->name());
            subPools.insert(groupList.first(), subPool);
          } else {
            itSubPools.value().addSignal(sig);
          }
          tempSignalToSave++;
        }
      }
    }
    return subPools;
  }

  /*!
    Inspects database for temporary signals and save them. The saved signals are added to the database. The
    modified signals are reverted to their original unmodified state.
    Returns false if saving process must be stopped.
  */
  bool SignalDatabase::saveTemporarySignals()
  {
    TRACE;
    int tempSignalToSave;
    QString tempSignalList;
    QMap<const AbstractSignalGroup *, SubSignalPool> subPools=temporarySignals(tempSignalList, tempSignalToSave);
    if(tempSignalToSave==0) return true;
    QString stateMsg;
    if(tempSignalToSave>1) {
      stateMsg=tr("Current database contains %1 temporary signals that belong to %2 group(s)")
          .arg(tempSignalToSave).arg(subPools.count());
    } else {
      stateMsg=tr("Current database contains a temporary signal that belongs to a group");
    }
    switch(Message::question(MSG_ID, tr("Saving Database ..."),
                             tr("%1 (e.g. from waveform processing).\n\n"
                                "Do you want to save them?\n\n"
                                "List of temporary signal(s):\n"
                                "%2\n\n"
                                "If a signal belongs to several groups, it saved in the first one marked with (*).")
                             .arg(stateMsg).arg(tempSignalList),
                             Message::yes(), Message::no(), Message::cancel())) {
    case Message::Answer0: break;
    case Message::Answer1: return true;
    case Message::Answer2: return false;
    }
    QDir d=newFilePath();
    QMap<const AbstractSignalGroup *, SubSignalPool>::iterator it;
    for(it=subPools.begin(); it!=subPools.end(); it++) {
      const SubSignalPool& subPool=it.value();
      QString fileName=File::uniqueName(subPool.name(), d);
      if(!subPool.saveGeopsySignal(fileName) ) {
        Message::warning(MSG_ID, tr( "Saving signals ..."),
                              tr("Cannot write to file: %1\n"
                                 "(Permission, disk space,...)"). arg(fileName), Message::cancel(), true);
        return false;
      }
      SignalFile * newFile=new SignalFile(this, fileName, SignalFileFormat::GeopsySignal);
      newFile->setOriginalFile(false);
      if(!newFile->loadGeopsySignal(subPool)) {
        Message::warning(MSG_ID, tr("Saving signals ..."),
                         tr("Cannot reload file: %1"). arg(fileName), Message::cancel(), true);
        return false;
      }
      App::log(tr("%1 temporary signals saved in %2\n").arg(subPool.count()).arg(fileName));
    }
    return true;
  }

  /*!
    This is the only one function to add new signals to database. It is automatically called by all
    constructors of Signal. Signal::id() is set uniquely here.
  */
  void SignalDatabase::addSignal(Signal * sig)
  {
    TRACE;
    ASSERT(sig->database()==this);
    sig->setId(-1);
    SubSignalPool::addSignal(sig);
  }

  void SignalDatabase::addFile(SignalFile * file)
  {
    TRACE;
    ASSERT(file->database()==this);
    GeopsyCoreEngine * eng=GeopsyCoreEngine::instance();
    eng->beginAddFile(this);
    _files.append(file);
    eng->endAddFile(this);
  }

  void SignalDatabase::collectAllSharedMetaData()
  {
    TRACE;
    ASSERT(_allSharedMetaData);
    for(SubSignalPool::iterator it=begin(); it!=end(); ++it) {
      _allSharedMetaData->add(*it);
    }
    std::sort(_allSharedMetaData->begin(), _allSharedMetaData->end());
    unique(*_allSharedMetaData);
    _allSharedMetaData->setIds();
  }

  void SignalDatabase::createAllSharedMetaData()
  {
    TRACE;
    ASSERT(!_allSharedMetaData);
    _allSharedMetaData=new SharedMetaData;
  }

  void SignalDatabase::releaseAllSharedMetaData()
  {
    TRACE;
    ASSERT(_allSharedMetaData);
    delete _allSharedMetaData;
    _allSharedMetaData=nullptr;
  }

  MetaData * SignalDatabase::resolveMetaData(int index)
  {
    TRACE;
    ASSERT(_allSharedMetaData);
    if(index<0 || index>=_allSharedMetaData->count()) {
      App::log(tr("Cannot resolve link to shared meta data: index %1 does not exist.\n").arg(index) );
      return nullptr;
    } else {
      return _allSharedMetaData->at(index);
    }
  }

  void SignalDatabase::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
  {
    Q_UNUSED(context)
    writeProperty(s, "version", _version);
    // Save links for permanent meta data
    ASSERT(_allSharedMetaData);
    int n=_permanentSharedMetaData.count();
    const SortedVector<int, MetaData>& v=_permanentSharedMetaData.vector();
    for(int i=0; i<n; i++) {
      const MetaData * d=v.at(i);
      if(d->isStored()) {
        d->xml_writeLink(s);
      }
    }
  }

  void SignalDatabase::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
  {
    TRACE;
    // Save all shared meta data
    ASSERT(_allSharedMetaData);
    _allSharedMetaData->xml_save(s, context);
    // Save files and signals
    for(QList<SignalFile *>::const_iterator it=_files.begin(); it!=_files.end(); ++it) {
      (*it)->xml_save(s, context);
    }
    // Save user groups
    _masterGroup->xml_save(s, context);
    _masterGroup->setModified(false);
    // Seismic event table
    _seismicEvents->xml_save(s, context);
    _seismicEvents->setModified(false);
  }

  XMLMember SignalDatabase::xml_member(XML_MEMBER_ARGS)
  {
    Q_UNUSED(attributes)
    Q_UNUSED(context)
    switch (tag[0].unicode()) {
    case 'F':
      if(tag=="File") {
        SignalFile * f=new SignalFile(this);
        return XMLMember(f);
      }
      break;
    case 'M':
      if(tag=="MasterGroup") {
        return XMLMember(_masterGroup);
      }
      break;
    case 'G':
      if(tag=="Group") {               // Kept for compatibility
        return XMLMember(_masterGroup);
      }
      break;
    case 'S':
      if(tag=="SharedMetaData") {
        ASSERT(_allSharedMetaData);
        return XMLMember(_allSharedMetaData);
      } else if(tag==SeismicEventTable::xmlSeismicEventTableTag) {
        return XMLMember(_seismicEvents);
      }
      break;
    case 'v':
      if(tag=="version") {
        return XMLMember(0);
      }
      break;
    default: {
        // Probably a link to permanent shared meta data
        static QString indexAtt="index";
        XMLRestoreAttributeIterator it=attributes.find(indexAtt);
        if(it!=attributes.end()) { // It is a link to shared data
          MetaData * d=resolveMetaData(it.value().toInt());
          if(d) {
            if(tag==d->xml_tagName()) {
              _permanentSharedMetaData.add(d);
              return XMLMember(101);
            } else {
              App::log(tr("Mismatch resolving shared meta data index %1: expecting type %2, found %3.\n")
                  .arg(it.value().toInt()).arg(tag.toString()).arg(d->xml_tagName()));
            }
          }
        }
      }
      break;
    }
    return XMLMember(XMLMember::Unknown);
  }

  bool SignalDatabase::xml_setProperty(XML_SETPROPERTY_ARGS)
  {
    Q_UNUSED(tag)
    Q_UNUSED(attributes)
    Q_UNUSED(context)
    switch (memberID) {
    case 0:
      _version=content.toInt();
      if(_version>SIGNAL_DATABASE_CURRENT_VERSION) {
        App::log(tr("Database saved with a more recent release, upgrade yours.\n") );
        return false;
      }
      return true;
    default:
      return false;
    }
  }

  bool SignalDatabase::xml_polish(XML_POLISH_ARGS)
  {
    TRACE;
    Q_UNUSED(context)
    // Groups refers to signal through IDs. Before starting anything
    // We must convert those IDs into real pointers.
    QHash<int, Signal *> lookup;
    for(SubSignalPool::const_iterator it=begin(); it!=end(); ++it) {
      Signal * sig=*it;
      lookup.insert(sig->id(), sig);
    }
    _masterGroup->convertIds(lookup);
    _masterGroup->setModified(false);
    return true;
  }


  /*!
    Mainly used for debug to display an internal signal. A copy of \a sig is made and added to this database.
  */
  void SignalDatabase::showSignal(const DoubleSignal * sig, const QString& name, const QString& comments)
  {
    TRACE;
    Signal * gsig=new Signal(this);
    gsig->setNSamples(sig->nSamples());
    gsig->setType(sig->type());
    gsig->setSamplingPeriod(sig->samplingPeriod());
    gsig->DoubleSignal::copySamplesFrom(sig);
    gsig->setName(name);
    gsig->setMetaData(Comments(comments));
  }

  /*!
    Mainly used for debug to display an internal signal. A copy of \a sig is made and added to this database.
  */
  void SignalDatabase::showSignal(const KeepSignal * sig, const QString& name, const QString& comments)
  {
    TRACE;
    Signal * gsig=new Signal(this);
    gsig->setNSamples(sig->nSamples());
    gsig->setSamplingPeriod(sig->samplingPeriod());
    gsig->setType(Signal::Waveform);
    gsig->setStartTime(sig->startTime());
    LOCK_SAMPLES(double, gsigSamples, gsig)
      CONST_LOCK_SAMPLES(int, sigSamples, sig)
        int n=sig->nSamples();
        for(int i=0; i<n; i++) {
          gsigSamples[i]=sigSamples[i];
        }
      UNLOCK_SAMPLES(sig)
    UNLOCK_SAMPLES(gsig)
    gsig->setName(name);
    gsig->setMetaData(Comments(comments));
  }


  /*!
    Import signal files into this database.
  */
  SubSignalPool SignalDatabase::load(QStringList fileNames, SignalFileFormat format, bool viewFiles)
  {
    TRACE;
    if(fileNames.isEmpty()) {
      QSettings& reg=CoreApplication::instance()->settings();
      reg.beginGroup("DialogOptions");
      if(GeopsyCoreEngine::instance()->preferences()->askForFormat()) {
        reg.setValue("LoadFormat/again", (bool) true);
      }
      reg.setValue("TimeReference/again", (bool) true);
      fileNames=Message::getOpenFileNames(tr("Load Signals"), tr("Signal file (*)"));
    }

    SubSignalPool subPool;
    if(!fileNames.isEmpty()) {
      fileNames=File::expand(fileNames);
      if(format.id()==SignalFileFormat::Unknown && !GeopsyCoreEngine::instance()->preferences()->askForFormat()) {
        format=GeopsyCoreEngine::instance()->preferences()->fileFormat();
      }
      std::sort(fileNames.begin(), fileNames.end(), lessThanLocalAwareCompareString);
      bool retLoad=false;
      if(viewFiles) {
        viewFiles=!GeopsyCoreEngine::instance()->preferences()->viewIfFewFiles() ||
                  (GeopsyCoreEngine::instance()->preferences()->viewIfFewFiles() &&
                  fileNames.count()<GeopsyCoreEngine::instance()->preferences()->maxNofFiles());
      }
      GeopsyCoreEngine::instance()->setProgressMaximum(this, fileNames.count()-1);
      int iProgress=0;
      for(QStringList::Iterator it=fileNames.begin();it!=fileNames.end();++it) {
        GeopsyCoreEngine::instance()->setProgressValue(this, iProgress++);
        if(GeopsyCoreEngine::instance()->preferences()->askForFormat()) {
          format=GeopsyCoreEngine::instance()->askLoadFormat(*it);
        }
        App::log(tr("Loading %1...\n").arg(*it) );
        retLoad=_files.load(*it, format);
        if(retLoad) {
          subPool.addFile(_files.last());
          if(viewFiles && GeopsyCoreEngine::instance()->preferences()->oneWindowPerFile()) {
            SubSignalPool subPool;
            subPool.addFile(_files.last());
            GeopsyCoreEngine::instance()->newSignalViewer(subPool);
          }
        }
      }
      if(viewFiles && !GeopsyCoreEngine::instance()->preferences()->oneWindowPerFile() && !subPool.isEmpty()) {
        GeopsyCoreEngine::instance()->newSignalViewer(subPool);
      }
    }
    return subPool;
  }

  /*!
    Overloaded function for convenience, load only one signal file.
  */
  SubSignalPool SignalDatabase::load(QString fileName, SignalFileFormat format, bool viewFile)
  {
    TRACE;
    QFileInfo fi(fileName);
    QStringList fileList;
    fileList.append(fi.absoluteFilePath());
    return load(fileList, format, viewFile);
  }

  /*!
    Set all processed files to temporary signals and re-save the database with only the temporary signals referenced in groups
  */
  void SignalDatabase::cleanProcessedFiles()
  {
    TRACE;
    // Detach processed signals from their files
    QList<Signal *> signalToDelete;
    for(iterator it=begin(); it!=end(); it++) {
      Signal * sig=*it;
      if(sig->file()->format()==SignalFileFormat(SignalFileFormat::GeopsySignal)) {
        sig->lockSamples();
        sig->setFile(nullptr);
        if(masterGroup()->find(sig).isEmpty()) {
          signalToDelete.append(sig);
        }
        sig->unlockSamples();
      }
    }
    for(QList<Signal *>::iterator it=signalToDelete.begin(); it!=signalToDelete.end(); it++) {
      removeSignal(*it);
    }
    // Delete empty files
    QList<SignalFile *> fileToDelete;
    for(SignalFilePool::iterator it=_files.begin(); it!=_files.end(); it++) {
      SignalFile * f=*it;
      SubSignalPool s;
      s.addFile(f);
      if(s.isEmpty()) {
        fileToDelete.append(f);
      }
    }
    for(QList<SignalFile *>::iterator it=fileToDelete.begin(); it!=fileToDelete.end(); it++) {
      removeFile(*it);
    }
  }

} // namespace GeopsyCore
