/***************************************************************************
**
**  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-12-02
**  Copyright: 2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include <QGpCoreWave.h>

#include "FKCrossSpectrum.h"

namespace ArrayCore {

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

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  FKCrossSpectrum::FKCrossSpectrum(const ArraySelection * array,
                                   const FKParameters * param)
    : ArrayCrossSpectrum(array, param), _array(array)
  {
    TRACE;
    _rotateStepCount=param->rotateStepCount();
    if(_rotateStepCount<1) {
      _rotateStepCount=1;
    }
    _rotateStep=2.0*M_PI/static_cast<double>(_rotateStepCount);
    _invRotateStep=1.0/_rotateStep;

    _rotatedRayleigh=nullptr;
    _rotatedLove=nullptr;

    resize();
  }

  /*!
    Description of destructor still missing
  */
  FKCrossSpectrum::~FKCrossSpectrum()
  {
    TRACE;
    delete [] _rotatedRayleigh;
    delete [] _rotatedLove;
  }

  void FKCrossSpectrum::resize()
  {
    int stationCount=_stations.count();
    FKParameters::ProcessType processType;
    processType=static_cast<const FKParameters *>(_parameters)->processType();
    switch(_mode) {
    case ArrayStations::Vertical:
      _rayleigh.resize(stationCount);
      break;
    case ArrayStations::Horizontal:
      switch(processType) {
      case FKParameters::DirectSteering:
      case FKParameters::ActiveDirectSteering:
      case FKParameters::DirectSteeringRadial:
      case FKParameters::DirectSteeringRefined:
      case FKParameters::DirectSteeringVertical:
      case FKParameters::Omni:
        _rayleigh.resize(2*stationCount);
        _love.resize(2*stationCount);
        break;
      case FKParameters::Conventional:
      case FKParameters::RTBF:
      case FKParameters::RTBFRadial:
      case FKParameters::ActiveRTBF:
      case FKParameters::ActiveConventional:
      case FKParameters::PoggiVertical:
      case FKParameters::PoggiRadial:
        delete [] _rotatedRayleigh;
        delete [] _rotatedLove;
        _rotatedRayleigh=new ComplexMatrix[_rotateStepCount];
        _rotatedLove=new ComplexMatrix[_rotateStepCount];
        for(int i=0; i<_rotateStepCount; i++) {
          _rotatedRayleigh[i].resize(stationCount);
          _rotatedLove[i].resize(stationCount);
        }
      }
      break;
    case ArrayStations::ThreeComponents:
      switch(processType) {
      case FKParameters::Conventional:
      case FKParameters::RTBF:
      case FKParameters::RTBFRadial:
        delete [] _rotatedRayleigh;
        delete [] _rotatedLove;
        _rotatedRayleigh=new ComplexMatrix[_rotateStepCount];
        _rotatedLove=new ComplexMatrix[_rotateStepCount];
        for(int i=0; i<_rotateStepCount; i++) {
          _rotatedRayleigh[i].resize(2*stationCount);
          _rotatedLove[i].resize(stationCount);
        }
        break;
      case FKParameters::PoggiVertical:
      case FKParameters::PoggiRadial:
        delete [] _rotatedRayleigh;
        delete [] _rotatedLove;
        _rayleigh.resize(stationCount);
        _rotatedRayleigh=new ComplexMatrix[_rotateStepCount];
        _rotatedLove=new ComplexMatrix[_rotateStepCount];
        for(int i=0; i<_rotateStepCount; i++) {
          _rotatedRayleigh[i].resize(stationCount);
          _rotatedLove[i].resize(stationCount);
        }
        break;
      case FKParameters::DirectSteering:
      case FKParameters::DirectSteeringRadial:
      case FKParameters::DirectSteeringRefined:
      case FKParameters::DirectSteeringVertical:
      case FKParameters::ActiveDirectSteering:
      case FKParameters::Omni:
        _rayleigh.resize(3*stationCount);
        _love.resize(2*stationCount);
        break;
      case FKParameters::ActiveConventional:
      case FKParameters::ActiveRTBF:
        _rayleigh.resize(2*stationCount);
        _love.resize(stationCount);
        break;
      }
      break;
    }
  }

  /*!
    Get the average source location from all active sources during blockset.
    Stations are then selected according to min and max distance parameters.
  */
  Point FKCrossSpectrum::setActiveSource(const QVector<int>& blocks, bool& ok)
  {
    const FKParameters * param=parameters();
    Point src;
    if(_source) {  // Only one source is forced
      src=_source->position();
    } else {
      const SeismicEventTable * activeSources;
      activeSources=_array.array()->first()->originals(0).database()->seismicEvents();

      // Get the list of all events recorded by all receivers
      QMap<const SeismicEvent *, int> eventList;
      QMap<const SeismicEvent *, int>::iterator eventIt;
      QList<const SeismicEvent *> srcList;
      int nRec=_array.array()->count();
      for(int iRec=0; iRec<nRec; iRec++) {
        for(QVector<int>::const_iterator it=blocks.begin(); it!=blocks.end(); it++) {
          const TimeRange& win=_timeWindows->list().at(*it);
          srcList=activeSources->events(win, _array.array()->at(iRec)->coordinates(), param->maximumSlowness());
          for(QList<const SeismicEvent *>::iterator it=srcList.begin(); it!=srcList.end(); it++) {
            eventIt=eventList.find(*it);
            if(eventIt!=eventList.end()) {
              eventIt.value()++;
            } else {
              eventList.insert(*it, 1);
            }
          }
        }
      }

      // Average the position of sources recorded by at least half of the receivers
      int nSrc=0;
      nRec/=2;
      for(eventIt=eventList.begin(); eventIt!=eventList.end(); eventIt++) {
        if(eventIt.value()>nRec) {
          src+=eventIt.key()->position();
          nSrc++;
        } else {
          App::log(tr("Event %1 probably not recorded by all receivers, skipped.\n").arg(eventIt.key()->name()));
        }
      }
      if(nSrc==0) {
        ok=false;
        App::log(tr("  No source found, aborting.\n"));
        return Point();
      } else {
        App::log(tr("  Found %1 active sources.\n").arg(nSrc));
      }
      src/=static_cast<double>(nSrc);
    }

    _array.clear();
    APP_LOG(1, tr("Checking distances from source (%1)...\n").arg(src.toString(2, 'f')))
    int n=_array.count();
    _array.select(src, param->minimumDistance(), param->maximumDistance());
    if(_array.count()<n) {
      App::log(1, tr("Selected %1 stations:\n%2")
                       .arg(_array.count())
                       .arg(_array.toString(ArraySelection::Absolute)));
    }
    parameters()->selectStations(_array);

    if(_array.count()<2) {
      ok=false;
      App::log(tr("Less than 2 stations selected, aborting\n"));
    } else {
      setStations(&_array);
      resize();
    }
    return src;
  }

  bool FKCrossSpectrum::calculate(const QVector<int>& blocks,
                                  AbstractFKFunction * rayleigh,
                                  AbstractFKFunction * love)
  {
    TRACE;
#ifdef CROSS_SPECTRUM_TIMING
    QTime chrono;
    int t1, t2, t3, t4, t5, tBeforeLock;
    int tLock=0;
    chrono.start();
#endif
    // For active process, estimate the average source location
    if(rayleigh) {
      if(!rayleigh->setSource(blocks)) {
        return false;
      }
    } else if(love) {
      if(!love->setSource(blocks)) {
        return false;
      }
    }

#ifdef CROSS_SPECTRUM_TIMING
    t1=chrono.elapsed();
#endif
    if(rayleigh) {
      rayleigh->resetCrossSpectrum();
    }
    if(love) {
      love->resetCrossSpectrum();
    }

#ifdef CROSS_SPECTRUM_TIMING
    t2=chrono.elapsed();
#endif
    for(QVector<int>::const_iterator it=blocks.begin(); it!=blocks.end(); it++) {
#ifdef CROSS_SPECTRUM_TIMING
    tBeforeLock=chrono.elapsed();
#endif
      if(lockTimeWindow(_timeWindows->list().at(*it), _timeWindows->plan())) {
#ifdef CROSS_SPECTRUM_TIMING
    tLock+=chrono.elapsed()-tBeforeLock;
#endif
        if(rayleigh) {
          rayleigh->addCrossSpectrum();
        }
        if(love) {
          love->addCrossSpectrum();
        }
        unlockTimeWindow();
      }
    }
#ifdef CROSS_SPECTRUM_TIMING
    t3=chrono.elapsed();
#endif

    if(rayleigh) {
      rayleigh->meanCrossSpectrum(blocks.count());
      if(!rayleigh->setFixedEllipticity(_timeWindows->frequency().center())) {
        return false;
      }
    }
    if(love) {
      love->meanCrossSpectrum(blocks.count());
    }

    // Experimental, mask built once with station name lists (1 or 0)
    if(rayleigh && !_rayleighArrayMask.isEmpty()) {
      _rayleigh=_rayleigh.dotMultiply(_rayleighArrayMask);
      APP_LOG(4, _rayleigh.toOctaveString()+"\n")
    }
    if(love && !_loveArrayMask.isEmpty()) {
      _love=_love.dotMultiply(_loveArrayMask);
    }

    switch(parameters()->processType()) {
    case FKParameters::Conventional:
    case FKParameters::ActiveConventional:
      return true;
    default:
      break;
    }

#ifdef CROSS_SPECTRUM_TIMING
    t4=chrono.elapsed();
#endif
    // Invert average cross spectra
    // Using Cholesky does not change speed for Vertical
    // Using Cholesky does change speed for 3-Component: from ~11' to 3'20" for a typical case.
    if(rayleigh) {
      if(!rayleigh->invertCrossSpectrum()) {
        return false;
      }
    }
    if(love) {
      if(!love->invertCrossSpectrum()) {
        return false;
      }
    }
#ifdef CROSS_SPECTRUM_TIMING
    t5=chrono.elapsed();
    App::log(3, tr("Cross spectrum computation took %1 ms (block %2 ms, reset %3 ms, lock %7 ms, add %4 ms, mean %5 ms, inv %6 ms)\n")
             .arg(t5).arg(t1).arg(t2-t1).arg(t3-t2).arg(t4-t3).arg(t5-t4).arg(tLock));
#endif
    return true;
  }

  void FKCrossSpectrum::resetRotatedRayleigh()
  {
    for(int i=0; i<_rotateStepCount; i++) {
      _rotatedRayleigh[i].zero(0.0);
    }
  }

  void FKCrossSpectrum::resetRotatedLove()
  {
    for(int i=0; i<_rotateStepCount; i++) {
      _rotatedLove[i].zero(0.0);
    }
  }

  void FKCrossSpectrum::addRotatedRadial()
  {
    for(int i=0; i<_rotateStepCount; i++) {
      addRotatedRadial(i);
    }
  }

  void FKCrossSpectrum::addRotatedTransverse()
  {
    for(int i=0; i<_rotateStepCount; i++) {
      addRotatedTransverse(i);
    }
  }

  void FKCrossSpectrum::addRotatedRayleigh()
  {
    for(int i=0; i<_rotateStepCount; i++) {
      addRotatedRayleigh(i);
    }
  }

  void FKCrossSpectrum::meanRotatedRayleigh(int nBlocks)
  {
    double invBlockCount=1.0/static_cast<double>(nBlocks);
    for(int i=0; i<_rotateStepCount; i++) {
      _rotatedRayleigh[i]*=invBlockCount;
    }
  }

  void FKCrossSpectrum::meanRotatedLove(int nBlocks)
  {
    double invBlockCount=1.0/static_cast<double>(nBlocks);
    for(int i=0; i<_rotateStepCount; i++) {
      _rotatedLove[i]*=invBlockCount;
    }
  }

  bool FKCrossSpectrum::errorInvert(QString component)
  {
    TRACE;
    App::log(tr("Cannot invert cross spectrum for %1\n").arg(component));
    return false;
  }

  bool FKCrossSpectrum::invertRayleigh()
  {
    if(!invert(_rayleigh)) {
      return errorInvert(Mode::userPolarization(Mode::Rayleigh));
    }
    return true;
  }

  bool FKCrossSpectrum::invertLove()
  {
    if(!invert(_love)) {
      return errorInvert(Mode::userPolarization(Mode::Love));
    }
    return true;
  }

  bool FKCrossSpectrum::invertRotatedRayleigh()
  {
    for(int i=0; i<_rotateStepCount; i++) {
      if(!invert(_rotatedRayleigh[i])) {
        return errorInvert(Mode::userPolarization(Mode::Radial));
      }
    }
    return true;
  }

  bool FKCrossSpectrum::invertRotatedLove()
  {
    for(int i=0; i<_rotateStepCount; i++) {
      if(!invert(_rotatedLove[i])) {
        return errorInvert(Mode::userPolarization(Mode::Transverse));
      }
    }
    return true;
  }

  void FKCrossSpectrum::addRotatedRadial(int rotationIndex)
  {
    int stationCount=_stations.count();
    const GaussianFrequencyBand& frequencyFilter=_timeWindows->frequencyFilter();

    const ArrayStationSignals * stat;
    ComplexMatrix& covmat=_rotatedRayleigh[rotationIndex];
    ComplexMatrix sig(stationCount, 1);
    Angle rotation;
    rotation.setRadians(_rotateStep*rotationIndex);

    // Calculate orientation of each station
    // TODO: take that value from station data itself
    /*Angle * orientations=new Angle[stationCount];
    for(int is=0; is<stationCount; is++) {
      Point2D p=_stations.at(is)->originalSignals()->coordinates();
      p-=Point2D(2000, 2000);
      Angle& orientation=orientations[is];
      orientation.set(p.x(), p.y());
      orientation.chSign();
    }*/

    if(_singleValueSpectrum) {
      for(int is=0; is<stationCount; is++) {
        stat=_stations.at(is);
        Complex& s=sig.at(is, 0);
        s=rotation.sin()*stat->spectrumValue(_northIndex);
        s+=rotation.cos()*stat->spectrumValue(_eastIndex);
      }
      covmat+=sig*sig.conjugate().transposed();
    } else {
      int nFreq=frequencyFilter.maximumIndex();
      double filter;
      //Complex north, east;
      for(int iFreq=frequencyFilter.minimumIndex(); iFreq<=nFreq; iFreq++) {
        filter=frequencyFilter.taperValue(iFreq);
        for(int is=0; is<stationCount; is++) {
          stat=_stations.at(is);
          Complex& s=sig.at(is, 0);
          s=rotation.sin()*stat->spectrumValue(_northIndex, iFreq);
          s+=rotation.cos()*stat->spectrumValue(_eastIndex, iFreq);
          /*north=stat->spectrumValue(_northIndex, iFreq);
          if(is%5==0) {
            east=stat->spectrumValue(_eastIndex, iFreq);
          } else {
            east.set(0.0, 0.0);
          }
          s=rotation.sin()*(north*orientations[is].cos()+east*orientations[is].sin());
          s+=rotation.cos()*(east*orientations[is].cos()-north*orientations[is].sin());*/
          s*=filter;
        }
        covmat+=sig*sig.conjugate().transposed();
      }
    }
  }

  void FKCrossSpectrum::addRotatedTransverse(int rotationIndex)
  {
    int stationCount=_stations.count();
    const GaussianFrequencyBand& frequencyFilter=_timeWindows->frequencyFilter();

    // Calculate orientation of each station
    // TODO: take that value from station data itself
    /*Angle * orientations=new Angle[stationCount];
    for(int is=0; is<stationCount; is++) {
      Point2D p=_stations.at(is)->originalSignals()->coordinates();
      p-=Point2D(2000, 2000);
      Angle& orientation=orientations[is];
      orientation.set(p.x(), p.y());
      orientation.chSign();
    }*/

    const ArrayStationSignals * stat;
    ComplexMatrix& covmat=_rotatedLove[rotationIndex];
    ComplexMatrix sig(stationCount, 1);
    Angle rotation;
    rotation.setRadians(_rotateStep*rotationIndex);
    if(parameters()->frequencyBandwidth()==0.0) {
      for(int is=0; is<stationCount; is++) {
        stat=_stations.at(is);
        Complex& s=sig.at(is, 0);
        s=rotation.cos()*stat->spectrumValue(_northIndex);
        s-=rotation.sin()*stat->spectrumValue(_eastIndex);
      }
      covmat+=sig*sig.conjugate().transposed();
    } else {
      int nFreq=frequencyFilter.maximumIndex();
      //Complex north, east;
      double filter;
      for(int iFreq=frequencyFilter.minimumIndex(); iFreq<=nFreq; iFreq++) {
        filter=frequencyFilter.taperValue(iFreq);
        for(int is=0; is<stationCount; is++) {
          stat=_stations.at(is);
          Complex& s=sig.at(is, 0);
          s=rotation.cos()*stat->spectrumValue(_northIndex, iFreq);
          s-=rotation.sin()*stat->spectrumValue(_eastIndex, iFreq);
          /* Test optical fiber array
          north=stat->spectrumValue(_northIndex, iFreq);
          if(is%5==0) {
            east=stat->spectrumValue(_eastIndex, iFreq);
          } else {
            east.set(0.0, 0.0);
          }
          s=rotation.cos()*(north*orientations[is].cos()+east*orientations[is].sin());
          s-=rotation.sin()*(east*orientations[is].cos()-north*orientations[is].sin());*/
          s*=filter;
        }
        covmat+=sig*sig.conjugate().transposed();
      }
    }
  }

  void FKCrossSpectrum::addRotatedRayleigh(int rotationIndex)
  {
    int stationCount=_stations.count();
    const GaussianFrequencyBand& frequencyFilter=_timeWindows->frequencyFilter();

    const ArrayStationSignals * stat;
    ComplexMatrix& covmat=_rotatedRayleigh[rotationIndex];
    ComplexMatrix sig(2*stationCount, 1);
    Angle rotation;
    rotation.setRadians(_rotateStep*rotationIndex);
    if(parameters()->frequencyBandwidth()==0.0) {
      for(int is=0; is<stationCount; is++) {
        stat=_stations.at(is);
        Complex& s=sig.at(is, 0);
        s=rotation.sin()*stat->spectrumValue(_northIndex);
        s+=rotation.cos()*stat->spectrumValue(_eastIndex);
      }
      for(int is=0; is<stationCount; is++) {
        sig.at(stationCount+is, 0)=_stations.at(is)->spectrumValue(_verticalIndex);
      }
      covmat+=sig*sig.conjugate().transposed();
    } else {
      int nFreq=frequencyFilter.maximumIndex();
      double filter;
      for(int iFreq=frequencyFilter.minimumIndex(); iFreq<=nFreq; iFreq++) {
        filter=frequencyFilter.taperValue(iFreq);
        for(int is=0; is<stationCount; is++) {
          stat=_stations.at(is);
          Complex& s=sig.at(is, 0);
          s=rotation.sin()*stat->spectrumValue(_northIndex, iFreq);
          s+=rotation.cos()*stat->spectrumValue(_eastIndex, iFreq);
          s*=filter;
        }
        for(int is=0; is<stationCount; is++) {
          Complex& s=sig.at(stationCount+is, 0);
          s=_stations.at(is)->spectrumValue(_verticalIndex, iFreq);
          s*=filter;
        }
        covmat+=sig*sig.conjugate().transposed();
      }
    }
  }

  void FKCrossSpectrum::addThreeComponent()
  {
    int stationCount=_stations.count();
    int stationCount2=2*stationCount;
    const GaussianFrequencyBand& frequencyFilter=_timeWindows->frequencyFilter();

    ComplexMatrix& covmat=_rayleigh;
    ComplexMatrix sig(3*stationCount, 1);
    if(parameters()->frequencyBandwidth()==0.0) {
      for(int is=0; is<stationCount; is++) {
         sig.at(is, 0)=_stations.at(is)->spectrumValue(_eastIndex);
      }
      for(int is=0; is<stationCount; is++) {
         sig.at(stationCount+is, 0)=_stations.at(is)->spectrumValue(_northIndex);
      }
      for(int is=0; is<stationCount; is++) {
        sig.at(stationCount2+is, 0)=_stations.at(is)->spectrumValue(_verticalIndex);
      }
      covmat+=sig*sig.conjugate().transposed();
    } else {
      int nFreq=frequencyFilter.maximumIndex();
      double filter;
      for(int iFreq=frequencyFilter.minimumIndex(); iFreq<=nFreq; iFreq++) {
        filter=frequencyFilter.taperValue(iFreq);
        for(int is=0; is<stationCount; is++) {
          Complex& s=sig.at(is, 0);
          s=_stations.at(is)->spectrumValue(_eastIndex, iFreq);
          s*=filter;
        }
        for(int is=0; is<stationCount; is++) {
          Complex& s=sig.at(stationCount+is, 0);
          s=_stations.at(is)->spectrumValue(_northIndex, iFreq);
          s*=filter;
        }
        for(int is=0; is<stationCount; is++) {
          Complex& s=sig.at(stationCount2+is, 0);
          s=_stations.at(is)->spectrumValue(_verticalIndex, iFreq);
          s*=filter;
        }
        covmat+=sig*sig.conjugate().transposed();
      }
    }
  }

  // Test of optical fiber arrays
  /*void FKCrossSpectrum::addHorizontalRotated(ComplexMatrix& covmat)
  {
    int stationCount=_stations.count();
    const GaussianFrequencyBand& frequencyFilter=_timeWindows->frequencyFilter();

    ComplexMatrix sig(2*stationCount, 1);

    // Calculate orientation of each station
    // TODO: take that value from station data itself
    Angle * orientations=new Angle[stationCount];
    for(int is=0; is<stationCount; is++) {
      Point2D p=_stations.at(is)->originalSignals()->coordinates();
      p-=Point2D(2000, 2000);
      Angle& orientation=orientations[is];
      orientation.set(p.x(), p.y());
      orientation.chSign();
    }

    if(_singleValueSpectrum) {
      for(int is=0; is<stationCount; is++) {
        sig.at(is, 0)=_stations.at(is)->spectrumValue(_eastIndex)*orientations[is].cos()
                     -_stations.at(is)->spectrumValue(_northIndex)*orientations[is].sin();
      }
      for(int is=0; is<stationCount; is++) {
        sig.at(stationCount+is, 0)=_stations.at(is)->spectrumValue(_northIndex)*orientations[is].cos()
                                  +_stations.at(is)->spectrumValue(_eastIndex)*orientations[is].sin();
      }
      covmat+=sig*sig.conjugate().transposed();
    } else {
      int nFreq=frequencyFilter.maximumIndex();
      double filter;
      for(int iFreq=frequencyFilter.minimumIndex(); iFreq<=nFreq; iFreq++) {
        filter=frequencyFilter.taperValue(iFreq);
        for(int is=0; is<stationCount; is++) {
          Complex& s=sig.at(is, 0);
          s=_stations.at(is)->spectrumValue(_eastIndex, iFreq)*orientations[is].cos()
           -_stations.at(is)->spectrumValue(_northIndex, iFreq)*orientations[is].sin();
          s*=filter;
        }
        for(int is=0; is<stationCount; is++) {
          Complex& s=sig.at(stationCount+is, 0);
          s=_stations.at(is)->spectrumValue(_northIndex, iFreq)*orientations[is].cos()
           +_stations.at(is)->spectrumValue(_eastIndex, iFreq)*orientations[is].sin();
          s*=filter;
        }
        covmat+=sig*sig.conjugate().transposed();
      }
    }
    delete [] orientations;
  }*/

  /*!
    Signals are corrected by factor 1/sqrt(distance to source)
  */
  void FKCrossSpectrum::addActiveVertical(const ActiveFKSteering& geom)
  {
    int stationCount=_stations.count();
    const GaussianFrequencyBand& frequencyFilter=_timeWindows->frequencyFilter();

    ComplexMatrix& covmat=_rayleigh;
    ComplexMatrix sig(stationCount, 1);
#ifndef COMPATIBILITY_2_5_0
    if(parameters()->frequencyBandwidth()==0.0) {
      for(int is=0; is<stationCount; is++) {
        Complex& s=sig.at(is, 0);
        s=_stations.at(is)->spectrumValue(_verticalIndex);
        s*=geom.amplitudeFactor(is);
      }
      covmat+=sig*sig.conjugate().transposed();
    } else {
#endif
      int nFreq=frequencyFilter.maximumIndex();
      double filter;
      for(int iFreq=frequencyFilter.minimumIndex(); iFreq<=nFreq; iFreq++) {
        filter=frequencyFilter.taperValue(iFreq);
        for(int is=0; is<stationCount; is++) {
          Complex& s=sig.at(is, 0);
          s=_stations.at(is)->spectrumValue(_verticalIndex, iFreq);
#ifdef COMPATIBILITY_2_5_0
          s*=filter;
#else
          s*=filter*geom.amplitudeFactor(is);
#endif
        }
        covmat+=sig*sig.conjugate().transposed();
      }
#ifndef COMPATIBILITY_2_5_0
    }
#endif
  }

  void FKCrossSpectrum::addActiveRayleigh(const ActiveFKSteering& geom)
  {
    int stationCount=_stations.count();
    const GaussianFrequencyBand& frequencyFilter=_timeWindows->frequencyFilter();

    const ArrayStationSignals * stat;
    ComplexMatrix& covmat=_rayleigh;
    ComplexMatrix sig(2*stationCount, 1);
    if(parameters()->frequencyBandwidth()==0.0) {
      for(int is=0; is<stationCount; is++) {
        stat=_stations.at(is);
        Complex& s=sig.at(is, 0);
        s=geom.radialRotation(is).sin()*stat->spectrumValue(_northIndex);
        s+=geom.radialRotation(is).cos()*stat->spectrumValue(_eastIndex);
        s*=geom.amplitudeFactor(is);
      }
      for(int is=0; is<stationCount; is++) {
        Complex& s=sig.at(stationCount+is, 0);
        s=_stations.at(is)->spectrumValue(_verticalIndex);
        s*=geom.amplitudeFactor(is);
      }
    } else {
      int nFreq=frequencyFilter.maximumIndex();
      double filter;
      for(int iFreq=frequencyFilter.minimumIndex(); iFreq<=nFreq; iFreq++) {
        filter=frequencyFilter.taperValue(iFreq);
        for(int is=0; is<stationCount; is++) {
          stat=_stations.at(is);
          Complex& s=sig.at(is, 0);
          s=geom.radialRotation(is).sin()*stat->spectrumValue(_northIndex, iFreq);
          s+=geom.radialRotation(is).cos()*stat->spectrumValue(_eastIndex, iFreq);
          s*=filter*geom.amplitudeFactor(is);
        }
        for(int is=0; is<stationCount; is++) {
          Complex& s=sig.at(stationCount+is, 0);
          s=_stations.at(is)->spectrumValue(_verticalIndex, iFreq);
          s*=filter*geom.amplitudeFactor(is);
        }
        covmat+=sig*sig.conjugate().transposed();
      }
    }
  }

  void FKCrossSpectrum::addActiveTransverse(const ActiveFKSteering& geom)
  {
    int stationCount=_stations.count();
    const GaussianFrequencyBand& frequencyFilter=_timeWindows->frequencyFilter();

    const ArrayStationSignals * stat;
    ComplexMatrix& covmat=_rayleigh;
    ComplexMatrix sig(stationCount, 1);
    if(parameters()->frequencyBandwidth()==0.0) {
      for(int is=0; is<stationCount; is++) {
        stat=_stations.at(is);
        Complex& s=sig.at(is, 0);
        s=geom.transverseRotation(is).sin()*stat->spectrumValue(_northIndex);
        s+=geom.transverseRotation(is).cos()*stat->spectrumValue(_eastIndex);
        s*=geom.amplitudeFactor(is);
      }
      covmat+=sig*sig.conjugate().transposed();
    } else {
      int nFreq=frequencyFilter.maximumIndex();
      double filter;
      for(int iFreq=frequencyFilter.minimumIndex(); iFreq<=nFreq; iFreq++) {
        filter=frequencyFilter.taperValue(iFreq);
        for(int is=0; is<stationCount; is++) {
          stat=_stations.at(is);
          Complex& s=sig.at(is, 0);
          s=geom.transverseRotation(is).sin()*stat->spectrumValue(_northIndex, iFreq);
          s+=geom.transverseRotation(is).cos()*stat->spectrumValue(_eastIndex, iFreq);
          s*=filter*geom.amplitudeFactor(is);
        }
        covmat+=sig*sig.conjugate().transposed();
      }
    }
  }

  void FKCrossSpectrum::addActiveThreeComponent(const ActiveFKSteering& geom)
  {
    int stationCount=_stations.count();
    int stationCount2=2*stationCount;
    const GaussianFrequencyBand& frequencyFilter=_timeWindows->frequencyFilter();

    ComplexMatrix& covmat=_rayleigh;
    ComplexMatrix sig(3*stationCount, 1);
    if(parameters()->frequencyBandwidth()==0.0) {
    } else {
      int nFreq=frequencyFilter.maximumIndex();
      double filter;
      for(int iFreq=frequencyFilter.minimumIndex(); iFreq<=nFreq; iFreq++) {
        filter=frequencyFilter.taperValue(iFreq);
        for(int is=0; is<stationCount; is++) {
          Complex& s=sig.at(is, 0);
          s=_stations.at(is)->spectrumValue(_eastIndex, iFreq);
          s*=filter*geom.amplitudeFactor(is);
        }
        for(int is=0; is<stationCount; is++) {
          Complex& s=sig.at(stationCount+is, 0);
          s=_stations.at(is)->spectrumValue(_northIndex, iFreq);
          s*=filter*geom.amplitudeFactor(is);
        }
        for(int is=0; is<stationCount; is++) {
          Complex& s=sig.at(stationCount2+is, 0);
          s=_stations.at(is)->spectrumValue(_verticalIndex, iFreq);
          s*=filter*geom.amplitudeFactor(is);
        }
        covmat+=sig*sig.conjugate().transposed();
      }
    }
  }

  /*!
    Normilize by diagonal terms
  */
  void FKCrossSpectrum::normalize(ComplexMatrix& covmat)
  {
    double * scales=new double[covmat.rowCount()];
    for(int i=covmat.rowCount()-1; i>=0; i--) {
      scales[i]=1.0/::sqrt(covmat.at(i, i).re());
    }
    for(int i=covmat.rowCount()-1; i>=0; i--) {
      for(int j=covmat.columnCount()-1; j>=0; j--) {
        covmat.at(i, j)*=scales[i]*scales[j];
      }
    }
    delete [] scales;
  }

  /*!
  */
  bool FKCrossSpectrum::invert(ComplexMatrix& covmat)
  {
    ComplexMatrix u, vt;
    QVector<double> sigma;
    int n=covmat.rowCount();
    if(parameters()->damping()>0.0) {
      // We need an estimation of average power
      double d=covmat.trace(0.0).re()/static_cast<double>(n);
      d*=parameters()->damping();
      for(int ir=0; ir<n; ir++) {
        covmat.at(ir, ir)+=d;
      }
    }
    covmat.singularValue(sigma, u, vt);
    double sRef=1e-12*sigma.first();
    for(int row=0; row<n ;row++ ) {
      if(sigma.at(row)<sRef) {
        App::log(tr("%1 Hz, effective rank of cross-spectrum matrix: %2\n")
                         .arg(_timeWindows->frequency().center())
                         .arg(row));
        return false;
      }
    }
    return covmat.invert(u, sigma, vt);
  }

  bool FKCrossSpectrum::setMask(AbstractFKFunction::Type t)
  {
    TRACE;
    FKParameters::ProcessType processType;
    processType=static_cast<const FKParameters *>(_parameters)->processType();
    switch(t) {
    case AbstractFKFunction::Rayleigh:
      switch(_mode) {
      case ArrayStations::Vertical:
        switch(processType) {
        case FKParameters::Conventional:
        case FKParameters::Omni:
        case FKParameters::ActiveRTBF:
        case FKParameters::ActiveDirectSteering:
        case FKParameters::ActiveConventional:
          break;
        case FKParameters::DirectSteering:
        case FKParameters::DirectSteeringRadial:
        case FKParameters::DirectSteeringRefined:
        case FKParameters::DirectSteeringVertical:
        case FKParameters::RTBF:
        case FKParameters::RTBFRadial:
        case FKParameters::PoggiVertical:
        case FKParameters::PoggiRadial:
          return setOneComponentArrayMask();
        }
        break;
      case ArrayStations::Horizontal:
        break;
      case ArrayStations::ThreeComponents:
        switch(processType) {
        case FKParameters::Conventional:
        case FKParameters::RTBF:
        case FKParameters::RTBFRadial:
        case FKParameters::DirectSteeringRadial:
        case FKParameters::DirectSteeringVertical:
        case FKParameters::DirectSteeringRefined:
        case FKParameters::Omni:
        case FKParameters::PoggiVertical:
        case FKParameters::PoggiRadial:
        case FKParameters::ActiveRTBF:
        case FKParameters::ActiveDirectSteering:
        case FKParameters::ActiveConventional:
          break;
        case FKParameters::DirectSteering:
          return setRayleighArrayMask();
        }
      }
      break;
    case AbstractFKFunction::RayleighFixedEll:
    case AbstractFKFunction::Love:
      break;
    }
    return false;
  }

  bool FKCrossSpectrum::setRayleighArrayMask()
  {
    const QList<QStringList>& arrays=static_cast<const FKParameters *>(_parameters)->arrays();
    if(arrays.isEmpty()) {
      return true;
    }
    // Map station names to indexes
    QHash<QString, int> stationIndex;
    int n=_stations.count();
    for(int i=n-1; i>=0; i--) {
      stationIndex.insert(_stations.at(i)->originalSignals()->name(), i);
    }
    _rayleighArrayMask.resize(3*n);
    _rayleighArrayMask.zero(0.0);
    QList<QStringList>::const_iterator ita;
    APP_LOG(1, tr("Mapping station names to index...\n"))
    for(ita=arrays.begin(); ita!=arrays.end(); ita++) {
      QVector<int> index;
      index.reserve(ita->count());
      for(QStringList::const_iterator it=ita->begin(); it!=ita->end(); it++) {
        int i=stationIndex.value(*it, -1);
        if(i==-1) {
          App::log(tr("unknwon station name '%1'\n").arg(*it));
          return false;
        }
        index.append(i);
        APP_LOG(1, tr("'%1' ---> %2\n").arg(*it).arg(i))
      }
      for(QVector<int>::const_iterator itr=index.begin(); itr!=index.end(); itr++) {
        int ir0=*itr;
        for(QVector<int>::const_iterator itc=index.begin(); itc!=index.end(); itc++) {
          int ic0=*itc;
          for(int ir=2*n+ir0; ir>=ir0; ir-=n) {
            for(int ic=2*n+ic0; ic>=ic0; ic-=n) {
              double& v=_rayleighArrayMask.at(ir, ic);
              if(v==0.0) {
                v=1;
              }
            }
          }
        }
      }
    }
    APP_LOG(4, _rayleighArrayMask.toUserString())
    return true;
  }

  bool FKCrossSpectrum::setOneComponentArrayMask()
  {
    const QList<QStringList>& arrays=static_cast<const FKParameters *>(_parameters)->arrays();
    if(arrays.isEmpty()) {
      return true;
    }
    // Map station names to indexes
    QHash<QString, int> stationIndex;
    int n=_stations.count();
    for(int i=n-1; i>=0; i--) {
      stationIndex.insert(_stations.at(i)->originalSignals()->name(), i);
    }
    _rayleighArrayMask.resize(n);
    _rayleighArrayMask.zero(0.0);
    QList<QStringList>::const_iterator ita;
    APP_LOG(1, tr("Mapping station names to index...\n"))
    for(ita=arrays.begin(); ita!=arrays.end(); ita++) {
      QVector<int> index;
      index.reserve(ita->count());
      APP_LOG(1, tr("  Array containing %1 stations\n").arg(ita->count()))
      for(QStringList::const_iterator it=ita->begin(); it!=ita->end(); it++) {
        int i=stationIndex.value(*it, -1);
        if(i==-1) {
          App::log(tr("    unknown station name '%1'\n").arg(*it));
          return false;
        }
        index.append(i);
        APP_LOG(1, tr("    '%1' ---> %2\n").arg(*it).arg(i))
      }
      for(QVector<int>::const_iterator itr=index.begin(); itr!=index.end(); itr++) {
        for(QVector<int>::const_iterator itc=index.begin(); itc!=index.end(); itc++) {
          double& v=_rayleighArrayMask.at(*itr, *itc);
          if(v==0.0) {
            v=1;
          }
        }
      }
    }
    APP_LOG(4, _rayleighArrayMask.toOctaveString()+"\n")
    return true;
  }

  void FKCrossSpectrum::setEllipticity(const ComplexMatrix& ell)
  {
    if(_rotatedRayleigh) {
      for(int i=0; i<_rotateStepCount; i++) {
        _rotatedRayleigh[i]=_rotatedRayleigh[i].dotMultiply(ell);
      }
    } else {
      _rayleigh=_rayleigh.dotMultiply(ell);
    }
  }

} // namespace ArrayCore

