/***************************************************************************
**
**  This file is part of geopsyarray.
**
**  geopsyarray 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.
**
**  geopsyarray 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: 2005-10-03
**  Copyright: 2005-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <GeopsyGui.h>
#include <ArrayCore.h>

#include "FKTimeWindows.h"

const Color FKTimeWindows::_highLightedColor(255, 166, 166);
/*!

*/
FKTimeWindows::FKTimeWindows(QWidget* parent)
    : QWidget(parent)
{
  TRACE;
  setupUi(this);

  _currentBlockSet=-1;
  _timeWindowLayer=nullptr;
  /*
    setup graph properties
  */
  waveNumGrid->graphContent()->setGridLines(false);

  _gridLayer=new LiveGridLayer(waveNumGrid);
  _gridLayer->setObjectName("grid");
  _gridLayer->setSampling(3);
  _positiveMap.generateColorScale(20, ColorPalette::McNamesClip, true);
  _positiveNegativeMap.generateColorScale(20, ColorPalette::ColdHot, false);
  _gridLayer->setColorMap(_positiveMap);

  _curveLayer=new LiveCurveLayer(waveNumGrid);
  _curveLayer->setObjectName("curve");
  _curveLayer->setSampling(3);

  _fkMeshLayer=new MeshLayer(waveNumGrid);
  _fkMeshLayer->setObjectName("fkMesh");

  _fkSlowCircleLayer=new CircleViewer(waveNumGrid);
  _fkSlowCircleLayer->setObjectName("userSlownessCircle");
  _fkSlowCircleLayer->resize(1);

  _fkSlowLineLayer=new LineLayer(waveNumGrid);
  _fkSlowLineLayer->setObjectName("userSlownessLine");
  AbstractLine * line=new PlotLine2D;
  line->setPen(Pen(Qt::black, 0.60, Qt::DashLine));
  line->setSymbol(Symbol(Symbol::NoSymbol));
  _fkSlowLineLayer->setReferenceLine(line);
  _fkSlowLineLayer->addLine();
  line=_fkSlowLineLayer->line(0);
  line->append();
  line->append();
  line->setY(0, -std::numeric_limits<double>::infinity());
  line->setY(1, std::numeric_limits<double>::infinity());

  _fkPeaks=new LineLayer(waveNumGrid);
  _fkPeaks->setObjectName("fkPeaks");
  line=new PlotLine2D;
  line->setPen(Pen(Qt::NoPen));
  line->setSymbol(Symbol(Symbol::Cross, 2.0));
  _fkPeaks->setReferenceLine(line);
  _fkPeaks->addLine();

  connect(waveNumGrid->graphContent(), SIGNAL(mouseMoved( QPoint, Qt::MouseButtons, Qt::KeyboardModifiers) ),
           this, SLOT(currentVelocitySlowness(QPoint)));
  valueLabel->setText(QString());
  connect(kxEdit, SIGNAL(valueChanged(const QString&)), this, SLOT(kxkyChanged()));
  connect(kyEdit, SIGNAL(valueChanged(const QString&)), this, SLOT(kxkyChanged()));
  connect(blockSetScroll, SIGNAL(valueChanged(int)), this, SLOT(setCurrentBlockSet()));

  _timeWindows=nullptr;
  _crossSpectrum=nullptr;
  _gridCache=nullptr;
  _taskManager=nullptr;
}

/*!

*/
FKTimeWindows::~FKTimeWindows()
{
  TRACE;
  delete _timeWindows;
  delete _crossSpectrum;
  delete _gridCache;
}

void FKTimeWindows::viewKxCurve()
{
  TRACE;
  waveNumGrid->xAxis()->setTitle("Wave number (rad/m)");
  waveNumGrid->xAxis()->setTitleInversedScale("Wave length/(2*pi) (m/rad)");
  waveNumGrid->xAxis()->setSizeType(Axis::AxisSize);
  waveNumGrid->yAxis()->setTitle("Power");
  waveNumGrid->yAxis()->setTitleInversedScale("1/Power");
  waveNumGrid->yAxis()->setSizeType(Axis::AxisSize);
  waveNumGrid->yAxis()->setScaleType(Scale::Log);
  waveNumGrid->yAxis()->setNumberType('e');
  _curveLayer->setScale(SamplingParameters::Log);

  _gridLayer->setOpacity(0.0);
  _curveLayer->setOpacity(1.0);
  _fkMeshLayer->setOpacity(0.0);
  _fkSlowCircleLayer->setOpacity(0.0);
  _fkSlowLineLayer->setOpacity(1.0);
}

void FKTimeWindows::viewKxKyGrid()
{
  TRACE;
  waveNumGrid->xAxis()->setTitle("Wave number X (rad/m)");
  waveNumGrid->xAxis()->setTitleInversedScale( "Wave length X/(2*pi) (m/rad)");
  waveNumGrid->xAxis()->setSizeType(Axis::Scaled);
  waveNumGrid->yAxis()->setTitle("Wave number Y (rad/m)");
  waveNumGrid->yAxis()->setTitleInversedScale("Wave length Y/(2*pi) (m/rad)");
  waveNumGrid->yAxis()->setSizeType(Axis::Scaled);
  waveNumGrid->yAxis()->setScaleType(Scale::Linear);
  waveNumGrid->yAxis()->setNumberType('f');

  _gridLayer->setOpacity(1.0);
  _curveLayer->setOpacity(0.0);
  _fkMeshLayer->setOpacity(0.5);
  _fkSlowCircleLayer->setOpacity(1.0);
  _fkSlowLineLayer->setOpacity(0.0);
}

void FKTimeWindows::viewKEllGrid()
{
  TRACE;
  waveNumGrid->xAxis()->setTitle("Wave number X (rad/m)");
  waveNumGrid->xAxis()->setTitleInversedScale("Wave length X/(2*pi) (m/rad)");
  waveNumGrid->xAxis()->setSizeType(Axis::AxisSize);
  waveNumGrid->yAxis()->setTitle("Ellipticity (deg)");
  waveNumGrid->yAxis()->setSizeType(Axis::AxisSize);
  waveNumGrid->yAxis()->setScaleType(Scale::Linear);
  waveNumGrid->yAxis()->setNumberType('f');

  _gridLayer->setOpacity(1.0);
  _curveLayer->setOpacity(0.0);
  _fkMeshLayer->setOpacity(0.0);
  _fkSlowCircleLayer->setOpacity(0.0);
  _fkSlowLineLayer->setOpacity(1.0);
}

void FKTimeWindows::setTimeWindowLayer(TimeWindowLayer * twLayer)
{
  if(twLayer) {
    twLayer->setShowColors(true);
    _timeWindowLayer=twLayer;
  }
}

/*!
  
*/
bool FKTimeWindows::setParameters(const ArrayStations * array, double frequency, FKParameters * param)
{
  TRACE;
  // Before changing anything to parameters, disable the grid plot (setComponent will reset it)
  _gridLayer->setFunction(nullptr);
  _curveLayer->setFunction(nullptr);

  LayerLocker ll(_timeWindowLayer); // deleting process will affect the list of time windows
  delete _taskManager;
  delete _crossSpectrum;
  delete _timeWindows;
  delete _gridCache;
  _timeWindows=nullptr;
  _crossSpectrum=nullptr;
  _gridCache=nullptr;
  _taskManager=new FKTaskManager(array);
  if(!_taskManager->setParameters(param)) {
    delete _taskManager;
    _taskManager=nullptr;
    return false;
  }
  _crossSpectrum=new FKCrossSpectrum(&_taskManager->array(), param);
  _timeWindows=new ArrayTimeWindows(&_taskManager->array(), _taskManager->keep(), param);
  _crossSpectrum->setTimeWindows(_timeWindows);

  // Steering must use the array selection as FKCrossSpectrum
  // Selection is changed by setParameters above
  delete _gridCache;
  _gridCache=new FKCache(&_taskManager->array());

  App::log(tr("Current frequency: %1\n").arg(frequency));
  _timeWindows->setFrequency(frequency);
  _timeWindows->setWindows();
  ll.unlock();

  setBlockSetList(param->blockAveraging());

  setPlotOptions();
  setPlotOption();
  setGrid(param);
  on_vEdit_valueChanged("");
  if(_timeWindowLayer) _timeWindowLayer->deepUpdate();
  return true;
}

void FKTimeWindows::setKxCurveLimits()
{
  if(_crossSpectrum && _curveLayer->opacity()>0.0) {
    double kmax=_crossSpectrum->parameters()->effectiveGridSize(_timeWindows->frequency().center());
    AxisWindow * w=waveNumGrid->graphContent()->graph();
    if(w->xAxis()->zoomEnabled()) {
      w->xAxis()->setRange(0.0, kmax);
    }
  }
}

void FKTimeWindows::setKxKyGridLimits()
{
  if(_crossSpectrum && _gridLayer->opacity()>0.0) {
    double kmax=_crossSpectrum->parameters()->effectiveGridSize(_timeWindows->frequency().center());
    waveNumGrid->setMapScale(-kmax, -kmax, kmax, kmax);
  }
}

void FKTimeWindows::setKEllGridLimits()
{
  if(_crossSpectrum && _gridLayer->opacity()>0.0) {
    double kmax=_crossSpectrum->parameters()->effectiveGridSize(_timeWindows->frequency().center());
    AxisWindow * w=waveNumGrid->graphContent()->graph();
    if(w->xAxis()->zoomEnabled()) {
      w->xAxis()->setRange(0.0, kmax);
    }
    w->yAxis()->setRange(-0.5*M_PI, 0.5*M_PI);
    w->yAxis()->setUnitFactor(180.0/M_PI);
    w->yAxis()->setAutoTicks(false);
    w->yAxis()->setMajorTicks(M_PI/9.0);
    w->yAxis()->setMinorTicks(M_PI/36.0);
  }
}

void FKTimeWindows::setPowerLimits()
{
  TRACE;
  _gridLayer->setValueFactor(1.0);
  _gridLayer->setForceRatio(true);
  _gridLayer->setForceMinimumValue(false);
  _gridLayer->setForceMaximumValue(false);
  _gridLayer->setScale(SamplingParameters::Log);
  _gridLayer->setSymetric(false);
  if(_gridLayer->autoAdjust()) {
    _gridLayer->setColorMap(_positiveMap);
  }
}

void FKTimeWindows::setEllipticityLimits()
{
  TRACE;
  _gridLayer->setValueFactor(180.0/M_PI);
  _gridLayer->setForceRatio(false);
  _gridLayer->setForceMinimumValue(false);
  _gridLayer->setForceMaximumValue(false);
  _gridLayer->setScale(SamplingParameters::Linear);
  _gridLayer->setSymetric(true);
  if(_gridLayer->autoAdjust()) {
    _gridLayer->setColorMap(_positiveNegativeMap);
  }
}

void FKTimeWindows::setPowerGradientLimits()
{
  TRACE;
  _gridLayer->setValueFactor(1.0);
  _gridLayer->setForceRatio(false);
  _gridLayer->setForceMinimumValue(false);
  _gridLayer->setForceMaximumValue(false);
  _gridLayer->setScale(SamplingParameters::Linear);
  _gridLayer->setSymetric(true);
  if(_gridLayer->autoAdjust()) {
    _gridLayer->setColorMap(_positiveNegativeMap);
  }
}

void FKTimeWindows::setPowerConcaveMaskLimits()
{
  TRACE;
  _gridLayer->setValueFactor(1.0);
  _gridLayer->setForceRatio(false);
  _gridLayer->setForceMinimumValue(true);
  _gridLayer->setForceMaximumValue(true);
  _gridLayer->setMinimumValue(0.0);
  _gridLayer->setMaximumValue(0.0);
  _gridLayer->setScale(SamplingParameters::Linear);
  _gridLayer->setSymetric(false);
  if(_gridLayer->autoAdjust()) {
    ColorMap map;
    map.resize(2);
    map.setColor(0, Qt::white);
    map.setColor(1, Qt::black);
    map.setWhiteTransparent(true);
    _gridLayer->setColorMap(map);
  }
}

void FKTimeWindows::setPowerStepLimits()
{
  TRACE;
  _gridLayer->setValueFactor(1.0);
  _gridLayer->setForceRatio(false);
  _gridLayer->setForceMinimumValue(true);
  _gridLayer->setForceMaximumValue(true);
  Axis * a=_gridLayer->graph()->xAxis();
  double dx=0.05*(a->maximum()-a->minimum());
  _gridLayer->setMinimumValue(-dx);
  _gridLayer->setMaximumValue(dx);
  _gridLayer->setScale(SamplingParameters::Linear);
  _gridLayer->setSymetric(true);
  if(_gridLayer->autoAdjust()) {
    _gridLayer->setColorMap(_positiveNegativeMap);
  }
}

void FKTimeWindows::setPowerDirectionLimits()
{
  TRACE;
  _gridLayer->setValueFactor(1.0);
  _gridLayer->setForceRatio(false);
  _gridLayer->setForceMinimumValue(true);
  _gridLayer->setForceMaximumValue(true);
  _gridLayer->setMinimumValue(0.0);
  _gridLayer->setMaximumValue(360.0);
  _gridLayer->setScale(SamplingParameters::Linear);
  _gridLayer->setSymetric(false);
  if(_gridLayer->autoAdjust()) {
    _gridLayer->setColorMap(_positiveMap);
  }
}

void FKTimeWindows::setPowerEllStepLimits()
{
  TRACE;
  _gridLayer->setValueFactor(180.0/M_PI);
  _gridLayer->setForceRatio(false);
  _gridLayer->setForceMinimumValue(true);
  _gridLayer->setForceMaximumValue(true);
  _gridLayer->setMinimumValue(-1.0);
  _gridLayer->setMaximumValue(1.0);
  _gridLayer->setScale(SamplingParameters::Linear);
  _gridLayer->setSymetric(true);
  if(_gridLayer->autoAdjust()) {
    _gridLayer->setColorMap(_positiveNegativeMap);
  }
}

void FKTimeWindows::setGrid(FKParameters * param)
{
  TRACE;
  _fkMeshLayer->lockDelayPainting();
  _fkMeshLayer->setSize(param->effectiveGridSize(_timeWindows->frequency().center()));
  _fkMeshLayer->setStep(param->gridStep());
  _fkMeshLayer->unlock();
  _fkMeshLayer->deepUpdate();
}

/*!
  
*/
void FKTimeWindows::setCurrentBlockSet()
{
  if(!_timeWindows) {
    return;
  }
  LayerLocker ll(_timeWindowLayer);
  // Before changing anything to parameters, disable the grid plot
  AbstractFKFunction * func;
  if(_gridLayer->opacity()>0.0) {
    func=static_cast<AbstractFKFunction *>(_gridLayer->takeFunction());
  } else if(_curveLayer->opacity()>0.0) {
    func=static_cast<AbstractFKFunction *>(_curveLayer->takeFunction());
  } else {
    return;
  }
  /*
    Make sure _currentBlockSet is valid and uncolor previous time window
    Set color for the new current time window 
  */
  TimeWindowList& winList=_timeWindows->list();
  if(winList.isEmpty()) {
    _currentBlockSet=-1;
    delete func;
    return ;
  }
  _currentBlockSet=blockSetScroll->value();
  if(_currentBlockSet<0 || _currentBlockSet>=_blockSetList.count()) {
    blockSetScroll->setValue(0);
    delete func;
    return;
  }
  highLightWindows(winList);
  ll.unlock();

  // crossSpectrum is modifying the index
  if(_currentBlockSet>=0 && _currentBlockSet<_blockSetList.count()) {
    const QVector<int>& blocks=_blockSetList.at(_currentBlockSet);
    App::log(tr("Processing block set starting at %1\n")
             .arg(winList.at(blocks.first()).start()
                  .toString(DateTime::defaultUserFormat)));
    if(_crossSpectrum->calculate(blocks, func, nullptr)) {
      if(_gridLayer->opacity()>0.0) {
        _gridLayer->setFunction(func);
      } else if(_curveLayer->opacity()>0.0) {
        _curveLayer->setFunction(func);
      }
    } else {
      delete func;
    }
  } else {
    delete func;
  }

  waveNumGrid->deepUpdate();
  if(_timeWindowLayer) _timeWindowLayer->deepUpdate();
  //showFKPeaks();
}

void FKTimeWindows::highLightWindows(TimeWindowList& winList)
{
  TRACE;
  winList.resetColors();
  QVector<int> blocks=_blockSetList.at(_currentBlockSet);
  for(QVector<int>::const_iterator it=blocks.begin(); it!=blocks.end(); it++) {
    winList.at(*it).setColor(_highLightedColor);
  }
}

void FKTimeWindows::setPlotOptions()
{
  TRACE;
  const FKParameters * param=_crossSpectrum->parameters();
  ArrayStations::Mode mode=_crossSpectrum->mode();
  int index=plotOption->currentIndex();
  switch(mode) {
  case ArrayStations::Vertical:
    plotOption->clear();
    plotOption->addItem(tr("Rayleigh power"));
    plotOption->addItem(tr("Rayleigh power gradient direction"));
    plotOption->addItem(tr("Rayleigh power concavity"));
    plotOption->addItem(tr("Rayleigh power step direction"));
    break;
  case ArrayStations::Horizontal:
    break;
  case ArrayStations::ThreeComponents:
    switch(param->processType()) {
    case FKParameters::ActiveDirectSteering:
    case FKParameters::ActiveRTBF:
      plotOption->clear();
      plotOption->addItem(tr("Wavenumber power"));
      plotOption->addItem(tr("Imaginary residuals"));
      break;
    case FKParameters::ActiveConventional:
      plotOption->clear();
      plotOption->addItem(tr("Wavenumber power"));
      plotOption->addItem(tr("Ellipticity power"));
      break;
    default:
      plotOption->clear();
      plotOption->addItem(tr("Rayleigh power at fixed ellipticity"));
      plotOption->addItem(tr("Rayleigh power gradient kx-ky direction at fixed ellipticity"));
      plotOption->addItem(tr("Rayleigh power gradient kx-ky at fixed ellipticity"));
      plotOption->addItem(tr("Rayleigh power gradient ell at fixed ellipticity"));
      plotOption->addItem(tr("Rayleigh power concavity at fixed ellipticity"));
      plotOption->addItem(tr("Rayleigh power k step length at fixed ellipticity"));
      plotOption->addItem(tr("Rayleigh power k step direction at fixed ellipticity"));
      plotOption->addItem(tr("Rayleigh power ell step at fixed ellipticity"));
      plotOption->addItem(tr("Rayleigh power at best ellipticity"));
      plotOption->addItem(tr("Rayleigh best ellipticity"));
      plotOption->addItem(tr("Love power"));
      plotOption->addItem(tr("Rayleigh power gradient"));
      break;
    }
    break;
  }
  if(plotOption->count()>0) {
    if(index<0) {
      index=0;
    }
    if(index>=plotOption->count()) {
      index=plotOption->count()-1;
    }
    plotOption->setCurrentIndex(index);
  }
}

void FKTimeWindows::setPlotOption()
{
  TRACE;
  if(!_crossSpectrum) {
    return;
  }
  // Before changing anything to parameters, disable the grid plot
  _gridLayer->setFunction(nullptr);
  _curveLayer->setFunction(nullptr);
  if(_crossSpectrum->array().count()==0) { // Selection is empty (for active)
    return;
  }

  const FKParameters * param=_crossSpectrum->parameters();
  ArrayStations::Mode mode=_crossSpectrum->mode();

  AbstractFKFunction * fk=nullptr;

  switch(mode) {
  case ArrayStations::Vertical:
    ellipticity->setEnabled(false);
    fk=AbstractFKFunction::create(AbstractFKFunction::Rayleigh, mode, param, _gridCache);
    switch(param->processType()) {
    case FKParameters::ActiveDirectSteering:
    case FKParameters::ActiveRTBF:
    case FKParameters::ActiveConventional:
      viewKxCurve();
      setKxCurveLimits();
      _curveLayer->setFunction(fk);
      break;
    default:
      switch(plotOption->currentIndex()) {
      case 0:
        _gridLayer->setVariables(LiveGridLayer::CrossXY);
        setPowerLimits();
        break;
      case 1:
        _gridLayer->setVariables(LiveGridLayer::GradXYDirection);
        setPowerDirectionLimits();
        break;
      case 2:
        _gridLayer->setVariables(LiveGridLayer::ConcavityXY);
        setPowerGradientLimits();
        break;
      case 3:
        _gridLayer->setVariables(LiveGridLayer::StepDirectionXY);
        setPowerDirectionLimits();
        break;
      }
      viewKxKyGrid();
      setKxKyGridLimits();
      _gridLayer->setFunction(fk);
      break;
    }
    break;
  case ArrayStations::Horizontal:
    break;
  case ArrayStations::ThreeComponents:
    switch(param->processType()) {
    case FKParameters::ActiveDirectSteering:
    case FKParameters::ActiveRTBF:
      ellipticity->setEnabled(false);
      _gridLayer->setVariables(LiveGridLayer::CrossXY);
      fk=AbstractFKFunction::create(AbstractFKFunction::Rayleigh, mode, param, _gridCache);
      viewKEllGrid();
      setKEllGridLimits();
      break;
    case FKParameters::ActiveConventional:
      ellipticity->setEnabled(false);
      switch(plotOption->currentIndex()) {
      default:
      case 0:
        _gridLayer->setVariables(LiveGridLayer::CrossXY);
        break;
      case 1:
        _gridLayer->setVariables(LiveGridLayer::InvCrossXY);
        break;
      }
      fk=AbstractFKFunction::create(AbstractFKFunction::Rayleigh, mode, param, _gridCache);
      viewKEllGrid();
      setKEllGridLimits();
      break;
    default:
      switch(plotOption->currentIndex()) {
      case 0:
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
      case 6:
      case 7:
        ellipticity->setEnabled(true);
        _gridLayer->setFixedVariable(Angle::degreesToRadians(ellipticity->value()));
        fk=AbstractFKFunction::create(AbstractFKFunction::Rayleigh, mode, param, _gridCache);
        viewKxKyGrid();
        setKxKyGridLimits();
        switch(plotOption->currentIndex()) {
        case 1:
          _gridLayer->setVariables(LiveGridLayer::GradXYDirection);
          setPowerDirectionLimits();
          break;
        case 2:
          _gridLayer->setVariables(LiveGridLayer::GradXYValue);
          setPowerLimits();
          break;
        case 3:
          _gridLayer->setVariables(LiveGridLayer::GradZvsXY);
          _gridLayer->setValueFactor(180.0/M_PI);
          setPowerGradientLimits();
          break;
        case 4:
          _gridLayer->setVariables(LiveGridLayer::ConcavityXY);
          setPowerGradientLimits();
          break;
        case 5:
          _gridLayer->setVariables(LiveGridLayer::StepLengthXY);
          setPowerLimits();
          break;
        case 6:
          _gridLayer->setVariables(LiveGridLayer::StepDirectionXY);
          setPowerDirectionLimits();
          break;
        case 7:
          _gridLayer->setVariables(LiveGridLayer::StepZ);
          setPowerEllStepLimits();
          break;
        default:
          _gridLayer->setVariables(LiveGridLayer::CrossXY);
          setPowerLimits();
          break;
        }
        break;
      case 8:
        ellipticity->setEnabled(false);
        _gridLayer->setVariables(LiveGridLayer::Value_XYMaxZ);
        fk=AbstractFKFunction::create(AbstractFKFunction::Rayleigh, mode, param, _gridCache);
        viewKxKyGrid();
        setKxKyGridLimits();
        setPowerLimits();
        break;
      case 9:
        ellipticity->setEnabled(false);
        _gridLayer->setVariables(LiveGridLayer::Z_XYMaxZ);
        fk=AbstractFKFunction::create(AbstractFKFunction::Rayleigh, mode, param, _gridCache);
        viewKxKyGrid();
        setKxKyGridLimits();
        setEllipticityLimits();
        break;
      case 10:
        ellipticity->setEnabled(false);
        _gridLayer->setVariables(LiveGridLayer::CrossXY);
        fk=AbstractFKFunction::create(AbstractFKFunction::Love, mode, param, _gridCache);
        viewKxKyGrid();
        setKxKyGridLimits();
        setPowerLimits();
        break;
      default:
        fk=nullptr;
        break;
      }
      _gridLayer->setFunction(fk);
      break;
    }
    break;
  }

  if(fk) {
    fk->setCrossSpectrum(_crossSpectrum);
    fk->setMaximumWavenumber(std::numeric_limits<double>::infinity()); // No limit for plot
    fk->setMaximumSlowness(std::numeric_limits<double>::infinity());
  } else {
    App::log(tr("No FK function available, check parameters\n"));
  }
  setCurrentBlockSet();

  waveNumGrid->deepUpdate();
}

void FKTimeWindows::on_kEdit_valueChanged(const QString & )
{
  TRACE;
  if(!_timeWindows) {
    return;
  }
  double val=kEdit->value();
  vEdit->blockSignals(true);
  vEdit->setValue(2*M_PI*_timeWindows->frequency().center()/val);
  vEdit->blockSignals(false);
  _fkSlowCircleLayer->set(0, 0, 0, val, val, 0.0, Qt::black);
  _fkSlowCircleLayer->deepUpdate();
  AbstractLine * line=_fkSlowLineLayer->line(0);
  line->setX(0, val);
  line->setX(1, val);
  _fkSlowLineLayer->deepUpdate();
}

void FKTimeWindows::on_vEdit_valueChanged(const QString & )
{
  TRACE;
  if(!_timeWindows) {
    return;
  }
  double val=vEdit->value();
  kEdit->blockSignals(true);
  kEdit->setValue(2*M_PI*_timeWindows->frequency().center()/val);
  kEdit->blockSignals(false);
  double k=2 * M_PI * _timeWindows->frequency().center()/val;
  _fkSlowCircleLayer->set(0, 0, 0, k, k, 0.0, Qt::black);
  _fkSlowCircleLayer->deepUpdate();
  AbstractLine * line=_fkSlowLineLayer->line(0);
  line->setX(0, k);
  line->setX(1, k);
  _fkSlowLineLayer->deepUpdate();
}

void FKTimeWindows::kxkyChanged()
{
  TRACE;
  Point kell(kxEdit->value(), kyEdit->value(), ellipticity->value());
  valueLabel->setText(tr("Value %1").arg(_gridLayer->value(kell), 0, 'g', 5));
  Curve<Point2D>& plot=static_cast<PlotLine2D *>(_fkPeaks->line(0))->curve();
  plot.clear();
  plot.append(kell);
  _fkPeaks->deepUpdate();
}

void FKTimeWindows::on_plotOption_currentIndexChanged(int)
{
  setPlotOption();
}

void FKTimeWindows::on_ellipticity_valueChanged(double)
{
  setPlotOption();
}

void FKTimeWindows::on_direction_valueChanged(double)
{
  setPlotOption();
}

void FKTimeWindows::currentVelocitySlowness(QPoint mousePos)
{
  TRACE;
  if(!_crossSpectrum || !_timeWindows) {
    return;
  }
  Point2D p=waveNumGrid->graphContent()->options().s2r(mousePos);
  Point2D origin(0, 0);

  enum Propagation {Propagation1D, Propagation2D};
  Propagation prop=Propagation2D;

  const FKParameters * param=_crossSpectrum->parameters();
  ArrayStations::Mode mode=_crossSpectrum->mode();
  switch(mode) {
  case ArrayStations::Vertical:
    switch(param->processType()) {
    case FKParameters::ActiveDirectSteering:
    case FKParameters::ActiveRTBF:
    case FKParameters::ActiveConventional:
      prop=Propagation1D;
      break;
    default:
      break;
    }
    break;
  case ArrayStations::Horizontal:
    break;
  case ArrayStations::ThreeComponents:
    switch(param->processType()) {
    case FKParameters::ActiveDirectSteering:
    case FKParameters::ActiveRTBF:
      prop=Propagation1D;
      break;
    case FKParameters::ActiveConventional:
      prop=Propagation1D;
      break;
    default:
      break;
    }
    break;
  }
  switch(prop) {
  case Propagation1D: {
      double kr=p.x();
      double sr=kr/(2.0*M_PI*_timeWindows->frequency().center());
      velSlowLabel->setText(tr("Propagation at k=%1 rad/m, v=%2 m/s or s=%3 s/km" )
                             .arg(kr, 0, 'g', 3)
                             .arg(1.0/sr, 0, 'f', 0)
                             .arg(sr*1000.0, 0, 'g', 5) );
    }
    break;
  case Propagation2D: {
      double kr=p.distanceTo(origin);
      double az=Angle::mathToGeographic(origin.azimuthTo(p));
      double sr=kr/(2.0*M_PI*_timeWindows->frequency().center());
      velSlowLabel->setText(tr("Propagation towards %1 counted from North\nat k=%2 rad/m, v=%3 m/s or s=%4 s/km" )
                             .arg(az, 0, 'f', 4)
                             .arg(kr, 0, 'g', 3)
                             .arg(1.0/sr, 0, 'f', 0)
                             .arg(sr*1000.0, 0, 'g', 5));
    }
    break;
  }
}

void FKTimeWindows::setBlockSetList(const BlockAveragingParameters& param)
{
  TRACE;
  // List is not "exact" if the signal contains gaps and with overlap
  int stationCount=_crossSpectrum->stationCount();
  QVector<int> blocks;
  _blockSetList.clear();
  int i=0;
  while(true) {
    blocks=param.list(i, stationCount, timeWindowList());
    if(blocks.isEmpty()) {
      break;
    }
    _blockSetList.append(blocks);
    i+=param.increment(timeWindowList(), blocks);
  }
  if(_blockSetList.isEmpty()) {
    blockSetScroll->setEnabled(false);
  } else {
    blockSetScroll->setEnabled(true);
    blockSetScroll->setMaximum(_blockSetList.count()-1);
  }
}

void FKTimeWindows::showFKPeaks()
{
  TRACE;
  const FKParameters * param=_crossSpectrum->parameters();
  ArrayStations::Mode mode=_crossSpectrum->mode();

  AbstractFKFunction * fk=nullptr;

  switch(mode) {
  case ArrayStations::Vertical:
    fk=AbstractFKFunction::create(AbstractFKFunction::Rayleigh, mode, param, _gridCache);
    switch(param->processType()) {
    case FKParameters::ActiveDirectSteering:
    case FKParameters::ActiveRTBF:
    case FKParameters::ActiveConventional:
      return;
    default:
      break;
    }
    break;
  case ArrayStations::Horizontal:
    return;
  case ArrayStations::ThreeComponents:
    switch(param->processType()) {
    case FKParameters::ActiveDirectSteering:
    case FKParameters::ActiveRTBF:
    case FKParameters::ActiveConventional:
      return;
    default:
      switch(plotOption->currentIndex()) {
      case 0:
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
      case 6:
      case 7:
      case 8:
        fk=AbstractFKFunction::create(AbstractFKFunction::Rayleigh, mode, param, _gridCache);
        break;
      case 9:
        fk=AbstractFKFunction::create(AbstractFKFunction::Love, mode, param, _gridCache);
        break;
      default:
        return;
      }
      break;
    }
    break;
  }
  fk->setMaximumSlowness(param->maximumSlowness());
  fk->setMaximumWavenumber(param->effectiveGridSize(_timeWindows->frequency().center()));
  fk->setTotalLimits(_timeWindows->frequency().squaredOmega());

  Curve<Point2D>& plot=static_cast<PlotLine2D *>(_fkPeaks->line(0))->curve();
  plot.clear();
  double gridSize=param->effectiveGridSize(_timeWindows->frequency().center());

  GridSearch * gs;
  /*if(param->inversionMethod()==FKParameters::Gradient) {
    FKGradientSearch * g=new FKGradientSearch();
    g->setFunction(fk);
    fk->setGrid(g, param->cacheGridStep(), param->gridStep(), param->effectiveGridSize());
    gs=g;
    gs->localMax(INT_MAX, 0.0, 0.0);
  } else {*/
    FKGridSearch * g=new FKGridSearch;
    g->setFunction(fk);
    fk->setGrid(g, param->gridStep(), gridSize);
    gs=g;
    if(fk->hasEllipticity()) {
      gs->localMaxBest(INT_MAX, 0.0, 0.0);
    } else {
      gs->localMax(INT_MAX, 0.0, 0.0);
    }
  //}

  FunctionMaximaIterator it;
  for(it=gs->begin(); it!=gs->end(); ++it) {
    Point kell=(*it)->pos();
    plot.append(kell);
    App::log(tr("Peak at k=(%1, %2), e=%3 deg. v=%4\n")
             .arg(kell.x()).arg(kell.y())
             .arg(Angle::radiansToDegrees(kell.z()))
             .arg(2.0*M_PI*_timeWindows->frequency().center()/Point2D(kell.x(), kell.y()).length()));
  }
  delete gs;

  _fkPeaks->deepUpdate();
}
