/***************************************************************************
**
**  This file is part of ArrayCore.
**
**  ArrayCore 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.
**
**  ArrayCore 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: 2019-11-29
**  Copyright: 2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "FKWorker.h"
#include "FKTaskManager.h"
#include "FKGridSearch.h"
#include "ArrayStations.h"
#include "WaveNumberConverter.h"
#include "FKResults.h"

//#define INSPECT_FREQUENCY 2.945
//#define INSPECT_TIME "20170314115338"

namespace ArrayCore {

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

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  FKWorker::FKWorker(const ArraySelection * array,
                     const FKParameters * param)
  {
    TRACE;
    _crossSpectrum=new FKCrossSpectrum(array, param);
    for(int i=0; i<GridCount; i++) {
      _grid[i]=nullptr;
    }
    _peaks.setTimeReference(array->array()->first()->minTime());
  }

  /*!
    Description of destructor still missing
  */
  FKWorker::~FKWorker()
  {
    TRACE;
    for(int i=0; i<GridCount; i++) {
      delete _grid[i];
    }
  }

  void FKWorker::setGrid(FKCache * gridCache)
  {
    TRACE;
    switch(taskManager()->array().array()->mode()) {
    case ArrayStations::Vertical:
      _grid[RayleighGrid]=createGrid(AbstractFKFunction::Rayleigh, gridCache);
      break;
    case ArrayStations::Horizontal:
      _grid[RayleighGrid]=createGrid(AbstractFKFunction::Rayleigh, gridCache);
      if(!crossSpectrum()->parameters()->isSkipLove()) {
        _grid[LoveGrid]=createGrid(AbstractFKFunction::Love, gridCache);
      }
      break;
    case ArrayStations::ThreeComponents:
      if(crossSpectrum()->parameters()->fixedEllipticityFileName().isEmpty()) {
        _grid[RayleighGrid]=createGrid(AbstractFKFunction::Rayleigh, gridCache);
        if(!crossSpectrum()->parameters()->isSkipLove()) {
          _grid[LoveGrid]=createGrid(AbstractFKFunction::Love, gridCache);
        }
      } else {
        _grid[RayleighGrid]=createGrid(AbstractFKFunction::RayleighFixedEll, gridCache);
      }
      break;
    }
    App::log(tr("Cache volume: %1 Gb\n").arg(gridCache->dataSizeMb()/1024.0));
  }

  FKGridSearch * FKWorker::createGrid(AbstractFKFunction::Type t, FKCache * gridCache)
  {
    TRACE;
    AbstractFKFunction * f=AbstractFKFunction::create(t, taskManager()->array().array()->mode(),
                                                      crossSpectrum()->parameters(), gridCache);
    if(f) {
      f->setCrossSpectrum(crossSpectrum());
      FKGridSearch * g;
      const FKParameters * param=crossSpectrum()->parameters();
      // Cross-spectrum depends upon k for RTBF, analytical gradient is not supported.
      /*if(param->inversionMethod()==FKParameters::Gradient && param->processType()!=FKParameters::RTBF) {
         g=new FKGradientSearch;
         g->setFunction(f);
         f->setGrid(static_cast<FKGradientSearch *>(g), param->cacheGridStep(), param->gridStep(), param->effectiveGridSize());
      } else {*/
         g=new FKGridSearch;
         g->setFunction(f);
         f->setGrid(g, param->gridStep(), param->effectiveGridSize());
      //}
      return g;
    } else {
      return nullptr;
    }
  }

  void FKWorker::process(const QVector<int>& blocks)
  {
    // If the frequency band width is null, _crossSpectrum->frequency().center() is not exactly the
    // frequency that is processed.
    // Using DTFT gives exact frequency  ===> TODO check why exactFrequency is commented
    //WaveNumberConverter conv(timeWindows->frequencyFilter().exactFrequency());
    WaveNumberConverter conv(_crossSpectrum->timeWindows()->frequency().center());
    const TimeWindowList& winList=_crossSpectrum->timeWindows()->list();
    conv.setTime(winList.at(blocks.first()).start());

#ifdef INSPECT_FREQUENCY
    if(fabs(conv.frequency()-INSPECT_FREQUENCY)<0.1) {
      App::log(tr("Inspect selected frequency %1 Hz\n").arg(conv.frequency()));
    } else {
      return;
    }
#endif
#ifdef INSPECT_TIME
    DateTime t;
    t.fromString(INSPECT_TIME);
    if(fabs(conv.time().secondsTo(t))<0.1) {
      App::log(tr("Inspect time window starting at %1\n").arg(conv.time().toString()));
    } else {
      return;
    }
#endif
    AbstractFKFunction * rayleigh, *love;
    functions(rayleigh, love);
    if(rayleigh || love) {
      if(crossSpectrum()->calculate(blocks, rayleigh, love)) {
        switch(crossSpectrum()->mode()) {
        case ArrayStations::Vertical:
          conv.setPolarization(Mode::Vertical);
          localizeMax(RayleighGrid);
          if(!addPeak(RayleighGrid, conv)) {
            return;
          }
          break;
        case ArrayStations::Horizontal:
          if(rayleigh) {
            conv.setPolarization(Mode::Radial);
            localizeMax(RayleighGrid);
            if(!addPeak(RayleighGrid, conv)) {
              return;
            }
          }
          if(love) {
            conv.setPolarization(Mode::Transverse);
            localizeMax(LoveGrid);
            if(!addPeak(LoveGrid, conv)) {
              return;
            }
          }
          break;
        case ArrayStations::ThreeComponents:
          if(rayleigh) {
            conv.setPolarization(Mode::Rayleigh);
            localizeMax(RayleighGrid);
            if(!addPeak(RayleighGrid, conv)) {
              return;
            }
          }
          if(love) {
            conv.setPolarization(Mode::Love);
            localizeMax(LoveGrid);
            if(!addPeak(LoveGrid, conv)) {
              return;
            }
          }
          break;
        }
      }
    }
  }

  void FKWorker::mergeResults()
  {
    FKResults * results=static_cast<FKResults *>(taskManager()->results());
    if(results) {
      results->add(_peaks);
    }
  }

  void FKWorker::functions(AbstractFKFunction *& rayleigh, AbstractFKFunction *& love)
  {
    switch(crossSpectrum()->mode()) {
    case ArrayStations::Vertical:
      if(_grid[RayleighGrid] && setSlownessRange(RayleighGrid)) {
        rayleigh=static_cast<AbstractFKFunction *>(_grid[RayleighGrid]->function());
        rayleigh->setTotalLimits(crossSpectrum()->timeWindows()->frequency().squaredOmega());
        rayleigh->setAzimuthRange(crossSpectrum()->parameters()->minimumAzimuth(),
                                  crossSpectrum()->parameters()->maximumAzimuth());
      } else {
        rayleigh=nullptr;
      }
      love=nullptr;
      break;
    case ArrayStations::Horizontal:
    case ArrayStations::ThreeComponents:
      if(_grid[RayleighGrid] && setSlownessRange(RayleighGrid)) {
        rayleigh=static_cast<AbstractFKFunction *>(_grid[RayleighGrid]->function());
        rayleigh->setTotalLimits(crossSpectrum()->timeWindows()->frequency().squaredOmega());
        rayleigh->setAzimuthRange(crossSpectrum()->parameters()->minimumAzimuth(),
                                  crossSpectrum()->parameters()->maximumAzimuth());
      } else {
        rayleigh=nullptr;
      }
      if(_grid[LoveGrid] && setSlownessRange(LoveGrid)) {
        love=static_cast<AbstractFKFunction *>(_grid[LoveGrid]->function());
        love->setTotalLimits(crossSpectrum()->timeWindows()->frequency().squaredOmega());
        love->setAzimuthRange(crossSpectrum()->parameters()->minimumAzimuth(),
                              crossSpectrum()->parameters()->maximumAzimuth());
      } else {
        love=nullptr;
      }
      break;
    }
  }

  void FKWorker::localizeMax(int gridIndex)
  {
    TRACE;
    const FKParameters * param=crossSpectrum()->parameters();
    if(param->maximumPeakCount()>1) {
      AbstractFKFunction * function=static_cast<AbstractFKFunction *>(_grid[gridIndex]->function());
      if(function->hasEllipticity()) {
        _grid[gridIndex]->localMaxBest(param->maximumPeakCount(),
                                       param->absoluteThreshold(),
                                       param->relativeThreshold());
      } else {
        _grid[gridIndex]->localMax(param->maximumPeakCount(),
                                   param->absoluteThreshold(),
                                   param->relativeThreshold());
      }
    } else {
      _grid[gridIndex]->globalMax(param->absoluteThreshold());
    }
  }

  bool FKWorker::addPeak(int gridIndex, const WaveNumberConverter& conv)
  {
    TRACE;
    const FKParameters * param=static_cast<const FKParameters *>(taskManager()->parameters());
    //FKResults * results=static_cast<FKResults *>(_taskManager->results());
    AbstractFKFunction * function=static_cast<AbstractFKFunction *>(_grid[gridIndex]->function());
    FunctionMaximaIterator it;
    APP_LOG(4, tr("    Found %1 peaks\n").arg(_grid[gridIndex]->maximaCount()))
    for(it=_grid[gridIndex]->begin(); it!=_grid[gridIndex]->end(); ++it) {
      const FunctionSearchMaximum& m=**it;
      Point kell=m.pos();
      APP_LOG(4, tr("    New peak at k=%1\n").arg(kell.toString()))
      if(!function->isOnLimit(kell, conv.frequency())) {
        double ell=function->ellipticity(kell); // Ensure that ellipticity is computed before
                                                // everything else: noise,...
        if(!function->refine(kell, ell, &_peaks, conv)) {
          if(!_peaks.add(function->waveNumber(kell),
                         ell,
                         function->noise(kell),
                         function->power(m.value()),
                         conv,
                         param->minimumSlowness())) {
            return false;
          }
        }
      }
    }
    return true;
  }

  bool FKWorker::setSlownessRange(int gridIndex)
  {
    TRACE;
    const FKParameters * param=crossSpectrum()->parameters();
    AbstractFKFunction * function=static_cast<AbstractFKFunction *>(_grid[gridIndex]->function());
    double f=crossSpectrum()->timeWindows()->frequency().center();
    if(param->refineGridIndex()!=-1) {
      if(gridIndex==param->refineGridIndex()) {
        //App::log(tr("Looking for frequency %1 in refinement limits\n").arg(f)
        //              << param->refineMinimumSlowness().toString() << endl;
        int index=param->refineMinimumSlowness().indexOf(f, 1e-4); // frequency step is 2.5%
        if(index<0) {
          APP_LOG(1, tr("  Skipped frequency %1 Hz for grid index %2\n").arg(f).arg(gridIndex))
          return false;
        } else {
          function->setMinimumSlowness(param->refineMinimumSlowness().at(index).y());
          function->setMaximumSlowness(param->refineMaximumSlowness().at(index).y());
          APP_LOG(1, tr("  Minimum slowness %1 s/m at %2 Hz\n").arg(function->minimumSlowness()).arg(f))
          APP_LOG(1, tr("  Maximum slowness %1 s/m at %2 Hz\n").arg(function->maximumSlowness()).arg(f))
          return true;
        }
      } else {
        APP_LOG(1, tr("  Skipped frequency %1 Hz for grid index %2\n").arg(f).arg(gridIndex))
        return false;
      }
    } else {
      // Rayleigh and Love must be computed on the same frequency range but
      // results may accept values on distinct frequency ranges.
      if(param->acceptFrequency(gridIndex, f)) {
        return true;
      } else {
        APP_LOG(1, tr("  Skipped frequency %1 Hz for grid index %2\n").arg(f).arg(gridIndex))
        return false;
      }
    }
  }

} // namespace ArrayCore

