/***************************************************************************
**
**  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 "ArrayCrossSpectrum.h"

namespace ArrayCore {

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

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  ArrayCrossSpectrum::ArrayCrossSpectrum(const ArraySelection * array,
                                         const ArrayParameters * param)
  {
    TRACE;
    _parameters=param; // Needed for locking time windows
    _mode=array->array()->mode();
    _singleValueSpectrum=(param->frequencyBandwidth()==0.0);
    switch(_mode) {
    case ArrayStations::Vertical:
      _verticalIndex=0;
      break;
    case ArrayStations::Horizontal:
      _northIndex=0;
      _eastIndex=1;
      break;
    case ArrayStations::ThreeComponents:
      _verticalIndex=0;
      _northIndex=1;
      _eastIndex=2;
      break;
    }
    setStations(array);
  }

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

  /*!
    Calculate processed signals (filter, fft, offset removal, taper,...) on time window \a win and lock
    this signals for all stations of the array. If successful, you must call unlockTimeWindow() to release the memory.
    Calling unlockTimeWindow() is not mandatory even between two successive calls.
  */
  bool ArrayCrossSpectrum::lockTimeWindow(const TimeRange& win, const FourierPlan& plan)
  {
    ArrayStationSignals * stat;
    int n=_stations.count();
    for(int i=0; i<n; i++ ) {
      stat=_stations.at(i);
      if(!stat->setSpectrumValue(win, _parameters, plan) ||
         !stat->lockSamples()) {
        for(i--; i>=0; i--) {
          _stations.at(i)->unlockSamples();
        }
        return false;
      }
    }
    return true;
  }

  /*!
    Can be called only after a successful lockTimeWindow().
  */
  void ArrayCrossSpectrum::unlockTimeWindow()
  {
    QList<ArrayStationSignals *>::iterator it;
    for(it=_stations.begin();it!=_stations.end();++it) {
      (*it)->unlockSamples();
    }
  }

  void ArrayCrossSpectrum::setStations(const ArraySelection * array)
  {
    int n=array->count();
    qDeleteAll(_stations);
    _stations.clear();
    for(int i=0; i<n; i++) {
      ArrayStationSignals * s=new ArrayStationSignals(array->at(i));
      _stations.append(s);
    }
  }

  void ArrayCrossSpectrum::resetRayleigh()
  {
    _rayleigh.zero(0.0);
  }

  void ArrayCrossSpectrum::resetLove()
  {
    _love.zero(0.0);
  }

  void ArrayCrossSpectrum::addVertical()
  {
    int stationCount=_stations.count();
    const GaussianFrequencyBand& frequencyFilter=_timeWindows->frequencyFilter();

    ComplexMatrix& covmat=_rayleigh;
    ComplexMatrix sig(stationCount, 1);

    if(_singleValueSpectrum) {
      for(int is=0; is<stationCount; is++) {
        sig.at(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(_verticalIndex, iFreq);
          s*=filter;
        }
        covmat+=sig*sig.conjugate().transposed();
      }
    }
  }

  /*!
    Used by HRFKDirectLove and SPACCrossSpectrum
  */
  void ArrayCrossSpectrum::addHorizontal(ComplexMatrix& covmat)
  {
    int stationCount=_stations.count();
    const GaussianFrequencyBand& frequencyFilter=_timeWindows->frequencyFilter();

    ComplexMatrix sig(2*stationCount, 1);

    if(_singleValueSpectrum) {
      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);
      }
      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;
        }
        covmat+=sig*sig.conjugate().transposed();
      }
    }
  }

  void ArrayCrossSpectrum::meanRayleigh(int nBlocks)
  {
    _rayleigh*=1.0/static_cast<double>(nBlocks);
  }

  void ArrayCrossSpectrum::meanLove(int nBlocks)
  {
    _love*=1.0/static_cast<double>(nBlocks);
  }

} // namespace ArrayCore

