/***************************************************************************
**
**  This file is part of HVCore.
**
**  HVCore 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.
**
**  HVCore 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: 2017-02-14
**  Copyright: 2017-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "AbstractHVTool.h"
#include "CurveResults.h"
#include "AbstractStation.h"

namespace HVCore {

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

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  AbstractHVTool::AbstractHVTool(QObject * parent)
    : AbstractTool(parent)
  {
    TRACE;
    AbstractTool::setParameters(HVParameters());
  }

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

  bool AbstractHVTool::setSubPool(SubSignalPool * subPool)
  {
    TRACE;
    if(!AbstractTool::setSubPool(subPool)) {
      return false;
    }
    App::log("*********** "+subPool->name()+" ***********\n");
    StationSignals::organizeSubPool(subPool);
    return true;
  }

  void AbstractHVTool::setStations()
  {
    TRACE;
    int n=_stationSignals.count();
    for(int i=0; i<n; i++) {
      AbstractStation * stat=createStation(_stationSignals.at(i));
      stat->addResults();
      _stations.append(stat);
      stat->clearWindowingLog();
    }
  }

  bool AbstractHVTool::setParameters(const AbstractParameters& param)
  {
    TRACE;
    const HVParameters * hvParam=dynamic_cast<const HVParameters *>(&param);
    if(!hvParam) {
      qDebug() << "Parameters are not HVParameters in AbstractHVTool::setParameters.";
      return false;
    }
    if(!AbstractTool::setParameters(param)) {
      return false;
    }
    return setLoop();
  }

  bool AbstractHVTool::setLoop()
  {
    TRACE;
    if(!AbstractTool::setLoop()) {
      return false;
    }
    HVTaskManager * loop=static_cast<HVTaskManager *>(AbstractTool::loop());
    if(loop->setParameters(parameters())) {
      loop->setTasks();
      return true;
    } else {
      return false;
    }
  }

  /*!
    Allocate a new global keep vector (ready to add time windows)
  */
  SparseKeepSignal * AbstractHVTool::globalKeep(const TimeRange& r)
  {
    TRACE;
    // Collect ranges for all stations
    SparseTimeRange sr(r);
    int n=_stations.count();
    for(int i=0; i<n; i++) {
      sr=sr.intersection(_stations.at(i)->timeRange(r));
    }
    SparseKeepSignal * keep=new SparseKeepSignal(sr);
    // Adjust range taking sampling of all stations into account
    for(int i=0; i<n; i++) {
      _stations.at(i)->setSampling(keep);
    }
    // Set values of keep
    keep->initValues(1);
    for(int i=0; i<n; i++) {
      _stations.at(i)->setKeep(keep, parameters()->windowing(), i);
    }
    return keep;
  }

  void AbstractHVTool::autoWindows(QList<AbstractStation *> * stations)
  {
    TRACE;
    if(!stations) {
      stations=&_stations;
    }
    int n=stations->count();
    if(parameters()->commonTimeWindows()) {
      TimeRange r=parameters()->timeRange().absoluteRange(subPool());
      SparseKeepSignal * keep=globalKeep(r);
      for(int i=0; i<n; i++) {
        AbstractStation * stat=stations->at(i);
        stat->clearWindowingLog();
        stat->addWindowingLog(tr("Common automatic windowing\n"));
        stat->clearAllWindows();
        stat->addWindows(r, parameters()->windowing(), *keep);
      }
      delete keep;
    } else {
      for(int i=0; i<n; i++) {
        AbstractStation * stat=stations->at(i);
        // Clear log and init it
        stat->clearWindowingLog();
        stat->addWindowingLog(tr("Automatic windowing\n"));
        TimeRange r=parameters()->timeRange().absoluteRange(stat->originalSignals());
        SparseKeepSignal * keep=stat->keep(r, parameters()->windowing(), i);
        stat->clearAllWindows();
        stat->addWindows(r, parameters()->windowing(), *keep);
        delete keep;
      }
    }
  }

  void AbstractHVTool::inverseWindows(QList<AbstractStation *> * stations)
  {
    TRACE;
    if(!stations) {
      stations=&_stations;
    }
    int n=stations->count();
    if(parameters()->commonTimeWindows()) {
      TimeRange r=parameters()->timeRange().absoluteRange(subPool());
      SparseKeepSignal * keep=globalKeep(r);
      for(int i=0; i<n; i++) {
        AbstractStation * stat=stations->at(i);
        stat->inverseWindows(r, parameters()->windowing(), *keep);
        stat->addWindowingLog(tr("Inverse selection\n"));
      }
      delete keep;
    } else {
      for(int i=0; i<n; i++) {
        AbstractStation * stat=stations->at(i);
        TimeRange r=parameters()->timeRange().absoluteRange(stat->originalSignals());
        SparseKeepSignal * keep=stat->keep(r, parameters()->windowing(), i);
        stat->inverseWindows(r, parameters()->windowing(), *keep);
        stat->addWindowingLog(tr("Inverse selection\n"));
        delete keep;
      }
    }
  }

  /*!
    Add time windows to station \a stat inside the range \a r.
    \a r can be specified by the user with the mouse.
  */
  void AbstractHVTool::addWindows(AbstractStation * stat, const TimeRange * r, QString log)
  {
    TRACE;
    // Do not use r for the computation of keep, because r can be shorter than lta
    // which generate a selection that depends upon the length of signal selected.
    if(parameters()->commonTimeWindows()) {
      TimeRange gr=parameters()->timeRange().absoluteRange(subPool());
      if(!r) {
        r=&gr;
      }
      SparseKeepSignal * keep=globalKeep(gr);
      int n=_stations.count();
      for(int i=0; i<n; i++) {
        AbstractStation * stat=_stations.at(i);
        stat->addWindows(*r, parameters()->windowing(), *keep);
        stat->addWindowingLog(log);
      }
      delete keep;
    } else {
      TimeRange gr=parameters()->timeRange().absoluteRange(stat->originalSignals());
      if(!r) {
        r=&gr;
      }
      SparseKeepSignal * keep=stat->keep(gr, parameters()->windowing(), _stations.indexOf(stat));
      stat->addWindows(*r, parameters()->windowing(), *keep);
      stat->addWindowingLog(log);
      delete keep;
    }
  }

  /*!
    Remove time windows to station \a stat inside the range \a r.
    \a r can be specified by the user with the mouse.
  */
  void AbstractHVTool::removeWindows(AbstractStation * stat, const TimeRange& r)
  {
    TRACE;
    QString log=tr("Remove windows in range [%1, %2]\n")
                    .arg(r.start().toString(DateTime::defaultUserFormat))
                    .arg(r.end().toString(DateTime::defaultUserFormat));
    if(parameters()->commonTimeWindows()) {
      int n=_stations.count();
      for(int i=0; i<n; i++) {
        AbstractStation * stat=_stations.at(i);
        stat->removeWindows(r);
        stat->addWindowingLog(log);
      }
    } else {
      stat->removeWindows(r);
      stat->addWindowingLog(log);
    }
  }
  /*!
    Add time windows for the station that contains \a sig.
    Used for dynamic signals.
  */
  bool AbstractHVTool::addWindows(Signal * sig)
  {
    TRACE;
    int n=_stations.count();
    SparseKeepSignal * keep;
    for(int i=0; i<n; i++) {
      AbstractStation * stat=_stations.at(i);
      int oldWindowsCount=stat->windowCount();
      if(stat->originalSignals()->contains(sig)) {
        if(parameters()->commonTimeWindows()) {
          TimeRange r=parameters()->timeRange().absoluteRange(subPool());
          keep=globalKeep(r);
          for(int i=0; i<n; i++) {
            _stations.at(i)->addWindows(r, parameters()->windowing(), *keep);
          }
        } else {
          TimeRange r=parameters()->timeRange().absoluteRange(stat->originalSignals());
          keep=stat->keep(r, parameters()->windowing(), _stations.indexOf(stat));
          stat->addWindows(r, parameters()->windowing(), *keep);
        }
        delete keep;
        return stat->windowCount()>oldWindowsCount;
      }
    }
    return false;
  }

  bool AbstractHVTool::loadWindows(const QStringList& fileNames)
  {
    TRACE;
    if(!fileNames.isEmpty()) {
      bool lookupStationNames;
      if(fileNames.count()==1) {
        lookupStationNames=false;
      } else { // Identify station names with files names
        lookupStationNames=true;
        App::log(tr("Loading windows: more than one file selected, only file names that match station names will be loaded."));
      }
      for(QStringList::const_iterator it=fileNames.begin(); it!=fileNames.end(); it++) {
        const QString fileName=*it;
        QString baseName;
        if(lookupStationNames) {
          QFileInfo fi(fileName);
          baseName=fi.baseName();
        }
        QFile f(fileName);
        if(!f.open(QIODevice::ReadOnly)) {
          App::log(tr("Unable to open file '%1' for reading\n").arg(fileName));
          return false;
        }
        QTextStream s(&f);
        qint64 startOffset=s.pos();
        QString logItem=tr("Loading windows from file '%1'\n").arg(fileName);
        const QString beginPattern="# BEGIN WINDOW LIST";
        const QString endPattern="# END WINDOW LIST";
        if(lookupStationNames) {
          AbstractStation * stat=nullptr;
          for (QList<AbstractStation *>::iterator it=_stations.begin(); it!=_stations.end(); ++it) {
            stat=*it;
            if(stat->name()==baseName) {
              break;
            }
          }
          if(stat) {
            stat->clearWindowingLog();
            stat->addWindowingLog(logItem);
            s.seek(startOffset);
            stat->clearAllWindows();
            if(!stat->addWindows(s, beginPattern, endPattern)) {
              return false;
            }
          }
        } else {
          for (QList<AbstractStation *>::iterator it=_stations.begin(); it!=_stations.end(); ++it) {
            AbstractStation * stat=*it;
            stat->clearWindowingLog();
            stat->addWindowingLog( logItem );
            s.seek(startOffset);
            stat->clearAllWindows();
            if(!stat->addWindows(s, beginPattern, endPattern)) {
              return false;
            }
          }
        }
      }
    }
    return true;
  }

  void AbstractHVTool::setWindowColors()
  {
    TRACE;
    for(int i=_stations.count()-1; i>=0; i--) {
      _stations.at(i)->setWindowColors();
    }
  }

  void AbstractHVTool::setStatistics()
  {
    TRACE;
    for(int i=_stations.count()-1; i>=0; i--) {
      _stations.at(i)->setStatistics();
    }
  }

  void AbstractHVTool::setPeaks()
  {
    TRACE;
    for(int i=_stations.count()-1; i>=0; i--) {
      _stations.at(i)->setPeaks();
    }
  }

  bool AbstractHVTool::save(QDir outputDir)
  {
    TRACE;
    int n=_stations.count();
    for(int i=0; i<n; i++) {
      AbstractStation * stat=_stations.at(i);
      if(!stat->save(outputDir, *parameters())) {
        return false;
      }
    }
    return true;
  }

} // namespace HVCore

