/***************************************************************************
**
**  This file is part of geopsy.
**
**  geopsy 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.
**
**  geopsy 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 <GeopsyCore.h>
#include <GeopsyGui.h>
#include <QGpGuiTools.h>
#include <SciFigs.h>

#include "GraphicWindow.h"
#include "MagnifierSignal.h"
#include "SignalDisplay.h"
#include "TableWindow.h"
#include "SigSelectionDnD.h"

GraphicWindow::GraphicWindow (QWidget* parent)
    : AbstractGraphicWindow(parent)
{
  TRACE;
  QVBoxLayout * l=new QVBoxLayout(this);
  l->setSpacing(2);
  l->setMargin(1);
  setObjectName("graphic");
  AxisWindow * w=new AxisWindow(this);
  l->addWidget(w);
  w->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
  w->setObjectName("graphic");
  setWindowIcon(QIcon(":graph-22x22.png"));

  _signalLayer=new SignalDisplay(w);
  _signalLayer->setObjectName("graphic");
  _signalLayer->subPoolUpdate(&subPool());
  _signalLayer->addTrackingAction(tr("Add gap"), SignalLayer::AddGaps, tr("Add gaps, e.g. to discard non-valid signal."));
  _signalLayer->addTrackingAction(tr("Remove gaps"), SignalLayer::RemoveGaps, tr("Remove gaps."));
  w->xAxis()->setScaleType(Scale::AbsoluteTime);
  w->yAxis()->setReversedScale(true);
  w->yAxis()->setZoomEnabled(false);
  w->xAxis()->setSizeInfo(15.0);
  w->yAxis()->setSizeInfo(10.0);

  // Drag and drop to transfert signal selection
  SigSelectionDnD * sigDnD=new SigSelectionDnD(w->graphContent());
  connect(sigDnD, SIGNAL(selectionDropped(QWidget *, const SubSignalPool&)), this, SLOT(addSignals(QWidget *, const SubSignalPool&)));
  connect(sigDnD, SIGNAL(getSelection(SubSignalPool&)), this, SLOT(getSelection(SubSignalPool&)));
  connect(w->graphContent(), SIGNAL(mouseTrackingBegin()), sigDnD, SLOT(disableFilter()));
  connect(w->graphContent(), SIGNAL(mouseTrackingEnd()), sigDnD, SLOT(enableFilter()));

  // Time window layer
  _timeWindowLayer=new TimeWindowLayer(_signalLayer);
  _timeWindowLayer->setObjectName("timeWindows");

  // Pick layer
  _pickLayer=new PickLayer(_signalLayer);
  _pickLayer->setObjectName("picks");
  connect(_pickLayer, SIGNAL(pickBegin(bool, int, double, Signal *, int)),
          this, SLOT(showMagnify(bool, int, double, Signal *)));
  connect(_pickLayer, SIGNAL(pickEnd()), this, SLOT(closeMagnify()));
  connect(_pickLayer, SIGNAL(mouseMoved(double)), this, SLOT(setMagnifyPosition(double)));
  connect(_pickLayer, SIGNAL(currentSignalChanged(Signal *, int)), this, SLOT(setMagnifySignal(Signal *)));
  _pickLayer->addTrackingAction(tr("&Pick"), PickLayer::Pick, tr("Pick time events."));
  _pickLayer->addTrackingAction(tr("&Delete picks"), PickLayer::DeletePicks, tr("Delete time picks."));

  // Slope estimator layer
  _slopeLayer=new SlopeEstimator(w);
  _slopeLayer->setObjectName("slopes");
  _slopeLayer->setFormat(SlopeEstimator::RatioYOverX);
  _slopeLayer->addTrackingAction(tr("&New hodochrone"), SlopeEstimator::Pick, tr("Pick a new hodochrone."));
  _slopeLayer->addTrackingAction(tr("&Edit hodochrone"), SlopeEstimator::Edit, tr("Edit an existing hodochrone."));

  // Magnify
  _magWin=nullptr;

  // Restore default properties
  QSettings& reg=CoreApplication::instance()->settings();
  QString signalPlotProperties=reg.value("SignalPlot").toString();
  if(!signalPlotProperties.isEmpty()) {
    XMLSciFigs s;
    s.restoreString(signalPlotProperties, _signalLayer->graphContent(), XMLSciFigs::MakeUp);
  }
  // Custom make up

  if(GeopsyCoreEngine::instance()->preferences()->restoreGraphicMakeUp() &&
     !GeopsyCoreEngine::instance()->preferences()->graphicMakeUpFile().isEmpty()) {
    _signalLayer->graph()->restoreMakeUp(GeopsyCoreEngine::instance()->preferences()->graphicMakeUpFile());
  }

  _controls=new SignalLayerControls(_signalLayer, this);
  l->addWidget(_controls);
}

GraphicWindow::~GraphicWindow ()
{
  TRACE;
  /* Must block painting right now, because subPool (in SubPoolWindow destructor) will be destroyed
  before graphic itself (deleted by QObject tree).*/
  LayerLocker ll(_signalLayer);
  if(_magWin) {
    GeopsyGuiEngine::instance()->removeSubWindow(_magWin);
  }
  // Save as default properties
  QSettings& reg=CoreApplication::instance()->settings();
  XMLSciFigs s;
  QString signalPlotProperties=s.saveString(_signalLayer->graphContent(), XMLSciFigs::MakeUp);
  reg.setValue("SignalPlot", signalPlotProperties);
}

void GraphicWindow::setWindowTitle(QString title)
{
  TRACE;
  SubPoolWindow::setWindowTitle("Graphic - "+title);
}

void GraphicWindow::subPoolUpdate()
{
  TRACE;
  AxisWindow * w=_signalLayer->graph();
  if(subPool().isEmpty()) {
    setWindowTitle(QString());
    return ;
  }
  signalLayer()->subPoolUpdate(&subPool());
  if(subPool().count()==1) {
    Signal & sig=*subPool().at(0);
    QString traceName=sig.nameComponent();
    setWindowTitle(traceName);
  } else {
    setWindowTitle(subPool().name());
  }
  updateLabels();
  setLimits();
  endSignalChange();
  // Make up
  if(GeopsyCoreEngine::instance()->preferences()->restoreGraphicMakeUp()) {
    _signalLayer->graph()->restoreMakeUp(GeopsyCoreEngine::instance()->preferences()->graphicMakeUpFile());
  }
  w->updateInternalGeometry();
  w->deepUpdate();
}

void GraphicWindow::updateLabels()
{
  TRACE;
  AxisWindow * w=_signalLayer->graph();
  int numSpectra=subPool().spectraCount();
  if(numSpectra==subPool().count()) {
    w->xAxis()->setTitle(tr("Frequency (Hz)"));
    w->xAxis()->setNumberType('f');
    w->xAxis()->setScaleType(Scale::Log);
    w->xAxis()->update();
    _timeWindowLayer->setOpacity(0.0);
    _pickLayer->setOpacity(0.0);
    _slopeLayer->setOpacity(0.0);
  } else if(numSpectra==0) {
    w->xAxis()->setTitle(tr("Time"));
    switch(signalLayer()->timeScale()) {
    case SignalLayer::AbsoluteTime:
      w->xAxis()->setScaleType(Scale::AbsoluteTime);
      w->xAxis()->setTimeReference(subPool().timeRange().start());
      break;
    case SignalLayer::RelativeToStartTime:
      w->xAxis()->setScaleType(Scale::RelativeTime);
      w->xAxis()->setTimeReference(DateTime());
      break;
    }
    w->xAxis()->update();
    _timeWindowLayer->setOpacity(1.0);
    _pickLayer->setOpacity(1.0);
    _slopeLayer->setOpacity(1.0);
  } else {
    w->xAxis()->setTitle(tr("Time (s) or Frequency (Hz)"));
    w->xAxis()->setNumberType('f');
    w->xAxis()->setScaleType(Scale::Linear);
    w->xAxis()->update();
    Message::warning(MSG_ID, tr("Mix of waveform and spectra"), tr("Sorry, you cannot mix waveforms and spectra on the same plot."));
    _timeWindowLayer->setOpacity(0.0);
    _pickLayer->setOpacity(0.0);
    _slopeLayer->setOpacity(0.0);
  }
  w->xAxis()->setAutoTicks(true);

  if(!subPool().isEmpty()) {
    SignalLayer * layer=signalLayer();
    if(layer->baseLineCount()==1) {
      // Everything is based on the first signal
      Signal & sig=*subPool().at(0);
      switch (sig.type()) {
      case Signal::Waveform:
        w->yAxis()->setTitle(sig.effectiveAmplitudeUnit());
        w->yAxis()->setNumberType('e');
        break;
      case Signal::Spectrum:
        w->yAxis()->setTitle(sig.effectiveAmplitudeUnit()+"/sqrt(Hz)");
        w->yAxis()->setNumberType('e');
        break;
      case Signal::Phase:
        w->yAxis()->setTitle(tr("Rad"));
        w->yAxis()->setNumberType('f');
        break;
      case Signal::UndefinedSignalType:
        break;
      }
      w->yAxis()->setAutoTicks(true);
      w->yAxis()->setScaleType(Scale::Linear);
      w->yAxis()->removeStringLabels();
      w->yAxis()->setReversedScale(false);
    } else {
      Axis& a=*w->yAxis();
      ASSERT(layer);
      switch(layer->yAxis()) {
      case SignalLayer::Receiver:
        a.setTitle(tr("Projections on receiver line (m)"));
        a.setAutoTicks(true);
        a.setScaleType(Scale::Linear);
        a.removeStringLabels();
        a.setReversedScale(false);
        break;
      case SignalLayer::ViewerIndex: {
          a.setReversedScale(true);
          a.setTitle("");
          a.setAutoTicks(false);
          a.setMajorTicks(1.0);
          a.setMinorTicks(1.0);
          a.setScaleType(Scale::Linear);
          a.removeStringLabels();
          int n=subPool().count()-1;
          for(int i=0; i<n; i++) {
            a.addStringLabel(subPool().at(i)->nameComponent(), false);
          }
          // Update axis... while adding the last label
          a.addStringLabel(subPool().at(n)->nameComponent(), true);
        }
        break;
      case SignalLayer::SignalName: {
          a.setReversedScale(true);
          a.setTitle("");
          a.setAutoTicks(false);
          a.setMajorTicks(1.0);
          a.setMinorTicks(1.0);
          a.setScaleType(Scale::Linear);
          a.removeStringLabels();
          QStringList names=subPool().signalNames().keys();
          int n=names.count()-1;
          for(int i=0; i<n; i++) {
            a.addStringLabel(names.at(i), false);
          }
          // Update axis... while adding the last label
          a.addStringLabel(names.at(n), true);
        }
        break;
      case SignalLayer::Overlay:
        // Handled above, just to avoid warning.
        break;
      }
    }
  }
}

void GraphicWindow::endSignalChange(Signal * sig)
{
  TRACE;
  SignalLayer * layer=signalLayer();
  ASSERT(layer);
  TimeRange r=subPool().timeRange();
  AxisWindow * w=_signalLayer->graph();
  if(subPool().count()==1) {
    updateLabels();
    double ymin, ymax;
    layer->minMaxY(ymin, ymax);
    w->yAxis()->setRange(ymin, ymax);
    w->deepUpdate();
  } else {
    w->xAxis()->update();
    layer->signalsUpdate();
  }
  setLimits();
  SubPoolWindow::endSignalChange(sig);
}

void GraphicWindow::fastFourierTransform(DoubleSignal::SignalType st)
{
  TRACE;
  LayerLocker ll(_signalLayer);
  beginSignalChange();
  subPool().fastFourierTransform(st);
  updateLabels();
  setLimits();
  endSignalChange();
  AxisWindow * w=_signalLayer->graph();
  w->updateInternalGeometry();
  w->deepUpdate();
}

void GraphicWindow::getSelection(SubSignalPool& sel)
{
  TRACE;
  sel.addSubPool(subPool());
}

void GraphicWindow::showMagnify(bool show, int context, double time, Signal * sig)
{
  TRACE;
  if(show) {
    if( !_magWin) {
      _magWin=new MagnifierSignal(this);
      _magWin->setContext(context);
      AxisWindow * w=_magWin->_signalLayer->graph();
      w->graphContent()->setFocus(Qt::OtherFocusReason);
      GeopsyGuiEngine::instance()->addSubWindow(this, _magWin);
      PickLayer * pl=pickLayer();
      w->graphContent()->installEventFilter(pl);
      w->xAxis()->setRange(0, time);
    }
    _magWin->setSignal(&subPool(), sig);
  }
}

void GraphicWindow::closeMagnify()
{
  TRACE;
  if(_magWin) {
    GeopsyGuiEngine::instance()->removeSubWindow(_magWin);
    _magWin=nullptr;
  }
}

void GraphicWindow::setMagnifyPosition(double x)
{
  TRACE;
  if(_magWin) _magWin->setTime(x);
}

void GraphicWindow::setMagnifySignal(Signal * sig)
{
  TRACE;
  if(_magWin) {
    _magWin->setSignal(&subPool(), sig);
     AxisWindow * w=_signalLayer->graph();
    _magWin->setTime(w->graphContent()->options().xs2r(w->xAxis()->lastMousePosition()) );
  }
}

void GraphicWindow::focusInEvent(QFocusEvent * e)
{
  TRACE;
  _signalLayer->graphContent()->setFocus(e->reason());
}
