/***************************************************************************
**
**  This file is part of geopsynr.
**
**  geopsynr 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.
**
**  geopsynr 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: 2007-08-06
**  Copyright: 2007-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <DinverCore.h>
#include <GeopsyCore.h>
#include <GeopsyGui.h>
#include <QGpGuiTools.h>
#include "ToolNR.h"
#include "ToolNRd.h"
#include "ShotRecord.h"
#include "T0GridSearch.h"
#include "StackWeights.h"

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

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
ToolNR::ToolNR(QWidget * parent)
    : AbstractToolWidget(parent, 0)
{
  TRACE;
  setWindowIcon(QIcon( ":nr-22x22.png" ));
  setObjectName("ToolNR");

  QVBoxLayout * baseLayout=new QVBoxLayout(this);
  _d=new ToolNRd(this);
  baseLayout->addWidget(_d);
  _d->highlightShot->addItem(tr("None"));

  connect(_d->t0Apply, SIGNAL(clicked()), this, SLOT(t0Apply()) );
  connect(_d->t0Revert, SIGNAL(clicked()), this, SLOT(t0Revert()) );
  connect(_d->uniformStack, SIGNAL(clicked()), this, SLOT(uniformStack()) );
  connect(_d->optimizeStack, SIGNAL(clicked()), this, SLOT(optimizeStack()) );
  connect(_d->highlightShot, SIGNAL(activated(int)), this, SLOT(highlightShot(int)) );
  connect(_d->stackGraphic, SIGNAL(clicked()), this, SLOT(newGraphicViewer()) );
}

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

void ToolNR::updateAllFields()
{
  TRACE;
  _d->t0TimeLimits->updateAllFields();
  _d->stackGlobalTimeLimits->updateAllFields();
  _d->stackSignalTimeLimits->updateAllFields();
}

bool ToolNR::initStations(SubSignalPool * subPool)
{
  TRACE;
  QString str=tr("NR toolbox - ")+subPool->name();
  setWindowTitle(str);

  QHash<const SeismicEvent *, SubSignalPool *> sourceSignals;
  sourceSignals=subPool->associateSources();
  // Sort sources by increasing time
  QList<const SeismicEvent *> sources=sourceSignals.keys();
  std::sort(sources.begin(), sources.end(), SeismicEvent::lessThan);

  int n=sources.count();
  bool allOK=true;
  for(int i=0; i<n; i++) {
    ShotRecord * shot=new ShotRecord(sources.at(i));
    if(!shot->setSubPool(sourceSignals.value(sources.at(i)))) {
      Message::warning(MSG_ID, tr("Gathering shots"),
                            tr("Shots recorded at %1 and %2 have not the same set or samples.").
                            arg(_shots.last()->receivers()->name()).arg(shot->receivers()->name()),
                            Message::cancel(), true);
      allOK=false;
    }
    if(!_shots.isEmpty() && !shot->isSameReceiverSet(*_shots.last())) {
      Message::warning(MSG_ID, tr("Gathering shots"),
                            tr("Shots recorded at %1 and %2 have not the same set of receivers.").
                            arg(_shots.last()->receivers()->name()).arg(shot->receivers()->name()),
                            Message::cancel());
      allOK=false;
    }
    _shots.append(shot);
    _d->highlightShot->addItem(shot->receivers()->name());
  }
  _d->t0TimeLimits->setSubPool(subPool);
  _d->stackGlobalTimeLimits->setSubPool(subPool);
  _d->stackSignalTimeLimits->setSubPool(subPool);
  return true;
}

void ToolNR::highlightShot(int shotIndex)
{
  TRACE;
  int nShots=_shots.count();
  SignalLayer * l=signalLayer();
  if(l) {
    for(int i=0; i<nShots; i++ ) {
      ShotRecord& shot=*_shots.at(i);
      int nReceivers=shot.receivers()->count();
      QColor c=(shotIndex-1==i) ? Qt::green : Qt::black;
      for(int j=0; j<nReceivers; j++) {
        l->setSignalColor(shot.receivers()->at(j), c);
      }
    }
  }
  l->deepUpdate();
}

void ToolNR::t0Apply()
{
  TRACE;
  int nShots=_shots.count();
  int minRecIndex=_shots[0]->receiverIndex(_d->t0MinDistance->value())+1;
  int maxRecIndex=_shots[0]->receiverIndex(_d->t0MaxDistance->value());
  TimeRangeParameters timeLimits;
  _d->t0TimeLimits->getParameters(timeLimits);
  double shifts[nShots];
  shifts[0]=0.0;
  SignalDatabase * db=subPool()->database();
  for(int i=1; i<nShots; i++) {
    GeopsyCoreEngine::instance()->showMessage(db, tr("Adjusting phase shifts for shot %1 (%2/%3) ...")
                                              .arg(_shots[i]->receivers()->name())
                                              .arg(i)
                                              .arg(nShots-1));
    T0GridSearch grid;
    grid.setFunction(new T0Correlation(_shots[0], _shots[i]));
    grid.setGrid(_shots[0]->receivers()->first()->samplingPeriod());
    grid.function()->setTimeLimits(timeLimits);
    grid.function()->setReceiverRange(minRecIndex, maxRecIndex);
    grid.globalMax();
    shifts[i]=grid.bestShift();
  }
  // Caluclate the average shift
  double averageShift=0.0;
  for(int i=0; i<nShots; i++) {
    averageShift+=shifts[i];
  }
  averageShift/=nShots;
  // Shift shots
  for(int i=0; i<nShots; i++) {
    _shots[i]->shift(shifts[i]-averageShift);
    App::log(tr("Shift for shot[%1]=%2\n").arg(i).arg(shifts[i]-averageShift));
  }
  shotsToSubPool();
}

void ToolNR::t0Revert()
{
  TRACE;
  int nShots=_shots.count();
  for(int i=0; i<nShots; i++ ) {
    _shots.at(i)->revert();
  }
  shotsToSubPool();
}

/*!
  Update user visible subpool
*/
void ToolNR::shotsToSubPool()
{
  int nShots=_shots.count();
  const_cast<SubSignalPool *>(subPool())->removeAll();
  for(int i=0; i<nShots; i++ ) {
    const_cast<SubSignalPool *>(subPool())->addSubPool(*_shots[i]->receivers());
  }
  emit updateSubPool();
}

void ToolNR::uniformStack()
{
  TRACE;
  int nReceivers=_shots[0]->receivers()->count();
  bool firstTime=_stack.isEmpty();
  TimeRangeParameters globalTimeLimits, signalTimeLimits;
  _d->stackGlobalTimeLimits->getParameters(globalTimeLimits);
  _d->stackSignalTimeLimits->getParameters(signalTimeLimits);
  for(int i=0; i<nReceivers; i++ ) {
    StackWeights forward(_shots);
    forward.setGlobalTimeLimits(globalTimeLimits);
    forward.setSignalTimeLimits(signalTimeLimits);
    forward.setReceiverIndex(i);
    forward.setUniformWeights();
    forward.stack(_stack);
    bool ok;
    App::log(tr("Noise level [%1]=%2\n").arg(i).arg(forward.misfit(ok)) );
  }
  if(firstTime) showStackResults();
  emit updateSubPool();
}

void ToolNR::optimizeStack()
{
  TRACE;
  int nReceivers=_shots[0]->receivers()->count();
  bool firstTime=_stack.isEmpty();
  TimeRangeParameters globalTimeLimits, signalTimeLimits;
  _d->stackGlobalTimeLimits->getParameters(globalTimeLimits);
  _d->stackSignalTimeLimits->getParameters(signalTimeLimits);
  SignalDatabase * db=subPool()->database();
  for(int i=0; i<nReceivers; i++ ) {
    GeopsyCoreEngine::instance()->showMessage(db, tr("Optimizing weights for receiver %1").arg(i));
    StackWeights forward(_shots);
    forward.setReceiverIndex(i);
    forward.setGlobalTimeLimits(globalTimeLimits);
    forward.setSignalTimeLimits(signalTimeLimits);
    ModelRepository na;
    na.setForward(&forward);
    na.setStorage();
    na.setBestModelCount(10);
    na.setMaximumModelCount(50);
    na.start(1, Generator::MonteCarlo);
    na.wait();
    na.start(1, Generator::Neighborhood);
    na.wait();
    int bestIndex=na.bestModelIndex();
    forward.setWeights(na.model(bestIndex) );
    forward.stack(_stack);
    App::log(tr("Noise level [%1]=%2\n").arg(i).arg(na.misfit(bestIndex) ) );
  }
  if(firstTime) showStackResults();
  emit updateSubPool();
}

void ToolNR::showStackResults()
{
  TRACE;
  const_cast<SubSignalPool *>(subPool())->addSubPool(_stack);
  SignalLayer * l=signalLayer();
  if(l) {
    QColor c=Qt::magenta;
    int nReceivers=_stack.count();
    for(int i=0; i<nReceivers; i++) {
      l->setSignalColor(_stack.at(i), c);
    }
  }
}

void ToolNR::newGraphicViewer()
{
  TRACE;
  if(_stack.isEmpty()) {
    uniformStack();
  }
  GeopsyGuiEngine::instance()->newGraphicWindow(this, _stack);
}
