/***************************************************************************
**
**  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: 2004-01-05
**  Copyright: 2004-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <math.h>

#include "StationPair.h"
#include "RingPairs.h"
#include "SPACCrossSpectrum.h"

namespace ArrayCore {

  RingPairs::RingPairs(const RingPairs& o)
    : QVector<const StationPair *>(o), AutocorrRing(o)
  {
    _weights=new double[count()];
    memcpy(_weights, o._weights, sizeof(double)*count());
  }

  RingPairs::~RingPairs()
  {
    delete [] _weights;
  }

  bool RingPairs::lessThan(const StationPair * s1, const StationPair * s2)
  {
    return s1->azimuth()<s2->azimuth();
  }

  void RingPairs::operator=(const RingPairs& o)
  {
    delete _weights;
    _weights=new double[o.count()];
    memcpy(_weights, o._weights, sizeof(double) * o.count());
    QVector<const StationPair *>::operator=(o);
    AutocorrRing::operator=(o);
  }

  /*!
    Identify pairs of stations belonging to this ring and store the weight of all of them. The range for
    angle does not matter (it can be from 0 to M_PI or -M_PI/2 to M_PI/2), but must be in radians.
  */
  bool RingPairs::setStations(const QVector<StationPair>& stationPairs)
  {
    clear();
    int nPairs=stationPairs.count(); // Total number of pairs available
    for(int i=0;i<nPairs; i++) {
      const StationPair& p=stationPairs.at(i);
      if(p.distance()>=minRadius() && p.distance()<=maxRadius()) {
        append(&p);
      }
    }
    // Compute dPhis
    std::sort(begin(), end(), lessThan);
    // Update number of stationPairs, maybe none were selected
    nPairs=count();   // Number of pairs in this ring
    if(nPairs==0) {
      return false;
    }
    delete _weights;
    _weights=new double[nPairs];
    const StationPair * p;
    const StationPair * lastp=at(0);
    double azimuth, lastAzimuth=0;
    int nStatPerAzimuth=1;
    for(int ip=1; ip<nPairs; ip++) {
      p=at(ip);
      if(p->azimuth()==lastp->azimuth()) {
        nStatPerAzimuth++;
      } else {
        azimuth=(p->azimuth().degrees()+lastp->azimuth().degrees())*0.5;
        if(nStatPerAzimuth==1) {
          _weights[ip-1]=azimuth-lastAzimuth;
        } else {
          // Last nStatPerPhi was not 1, so share dphi between various pairs
          double weight=(azimuth - lastAzimuth)/nStatPerAzimuth;
          int n=ip-1-nStatPerAzimuth;
          for(int i=ip-1; i>n; i--) {
            _weights[i]=weight;
          }
          nStatPerAzimuth=1;
        }
        lastAzimuth=azimuth;
      }
      lastp=p;
    }
    // Close last angular ring section
    double weight=(180.0-lastAzimuth)/nStatPerAzimuth;
    int n=nPairs-1-nStatPerAzimuth;
    for(int i=nPairs-1; i>n; i--) {
      _weights[i]=weight;
    }
    return true;
  }

  QString RingPairs::toString() const
  {
    QString str;
    str+=tr(" --- Ring %1\n").arg(name());
    double totalWeight=0;
    int nPairs=count();
    for(int i=0; i<nPairs; i++) {
      str+=tr("%1 azimuth=%2 weight=%3\n")
           .arg(at(i)->name())
           .arg(at(i)->azimuth().degrees())
           .arg(_weights[i]);
      totalWeight+=_weights[i];
    }
    str+=tr("Total weight=%1\n").arg(totalWeight);
    return str;
  }

  /*!
    Calculate the cross correlation weighted average on the ring for vertical components
  */
  Complex RingPairs::verticalAutocorr(const SPACCrossSpectrum * crossSpectrum) const
  {
    TRACE;
    Complex autocorr;
    const ComplexMatrix& covmat=crossSpectrum->verticalCrossSpectrum();
    for(int i=count()-1; i>=0; i--) {
      const StationPair& p=*at(i);
      autocorr+=covmat.at(p.station1(), p.station2())*_weights[i];
    }
    autocorr/=180.0;
    return autocorr;
  }

  /*!
    Calculate the cross correlation weighted average on the ring for radial components
  */
  Complex RingPairs::radialAutocorr(const SPACCrossSpectrum * crossSpectrum) const
  {
    TRACE;
    Complex autocorr;
    const ComplexMatrix& covmat=crossSpectrum->horizontalCrossSpectrum();
    int n=crossSpectrum->stationCount();
    for(int i=count()-1; i>=0; i--) {
      const StationPair& p=*at(i);
      int iE1=p.station1();
      int iN1=n+p.station1();
      int iE2=p.station2();
      int iN2=n+p.station2();
      const Angle& a=p.azimuth();
      double sin2=a.sin()*a.sin();
      double cos2=a.cos()*a.cos();
      double sincos=a.sin()*a.cos();
      Complex c;
      c+=covmat.at(iN1, iN2)*sin2;
      c+=covmat.at(iN1, iE2)*sincos;
      c+=covmat.at(iE1, iN2)*sincos;
      c+=covmat.at(iE1, iE2)*cos2;
      Complex pow1;
      pow1+=covmat.at(iN1, iN1)*sin2;
      pow1+=(covmat.at(iN1, iE1)+covmat.at(iE1, iN1))*sincos;
      pow1+=covmat.at(iE1, iE1)*cos2;
      Complex pow2;
      pow2+=covmat.at(iN2, iN2)*sin2;
      pow2+=(covmat.at(iN2, iE2)+covmat.at(iE2, iN2))*sincos;
      pow2+=covmat.at(iE2, iE2)*cos2;
      c*=_weights[i]/::sqrt(pow1.re()*pow2.re());
      autocorr+=c;
    }
    autocorr/=180.0;
    return autocorr;
  }

  /*!
    Calculate the cross correlation weighted average on the ring for transverse components
  */
  Complex RingPairs::transverseAutocorr(const SPACCrossSpectrum * crossSpectrum) const
  {
    TRACE;
    Complex autocorr;
    const ComplexMatrix& covmat=crossSpectrum->horizontalCrossSpectrum();
    int n=crossSpectrum->stationCount();
    for(int i=count()-1; i>=0; i--) {
      const StationPair& p=*at(i);
      int iE1=p.station1();
      int iN1=n+p.station1();
      int iE2=p.station2();
      int iN2=n+p.station2();
      const Angle& a=p.azimuth();
      double sin2=a.sin()*a.sin();
      double cos2=a.cos()*a.cos();
      double sincos=a.sin()*a.cos();
      Complex c;
      c+=covmat.at(iN1, iN2)*cos2;
      c-=covmat.at(iN1, iE2)*sincos;
      c-=covmat.at(iE1, iN2)*sincos;
      c+=covmat.at(iE1, iE2)*sin2;
      Complex pow1;
      pow1+=covmat.at(iN1, iN1)*cos2;
      pow1-=(covmat.at(iN1, iE1)+covmat.at(iE1, iN1))*sincos;
      pow1+=covmat.at(iE1, iE1)*sin2;
      Complex pow2;
      pow2+=covmat.at(iN2, iN2)*cos2;
      pow2-=(covmat.at(iN2, iE2)+covmat.at(iE2, iN2))*sincos;
      pow2+=covmat.at(iE2, iE2)*sin2;
      c*=_weights[i]/::sqrt(pow1.re()*pow2.re());
      autocorr+=c;
    }
    autocorr/=180.0;
    return autocorr;
  }

#if 0
  void RingPairs::horizontalAutocorr()
  {
    TRACE;
    _radialAutocorr=0.0;
    _transverseAutocorr=0.0;
    double powerR1=0.0;  // Power for radial and transverse
    double powerR2=0.0;
    double powerT1=0.0;
    double powerT2=0.0;
    double r1, r2, t1, t2; // radial and transverse components
    DoubleSignal * sN1=_s1->processed(1);
    DoubleSignal * sE1=_s1->processed(2);
    DoubleSignal * sN2=_s2->processed(1);
    DoubleSignal * sE2=_s2->processed(2);
    CacheProcess cp;
    cp << sN1 << sE1 << sN2 << sE2;
    CONST_LOCK_SAMPLES(double, samplesN1, sN1)
      CONST_LOCK_SAMPLES(double, samplesE1, sE1)
        CONST_LOCK_SAMPLES(double, samplesN2, sN2)
          CONST_LOCK_SAMPLES(double, samplesE2, sE2)
            int n=(int)round(sN1->nSamples() * 0.9);
            for(int i=(int) round(sN1->nSamples() * 0.1);i < n;i++ ) {
              r1=samplesN1[i]*_azimuth.sin()+samplesE1[i]*_azimuth.cos();
              r2=samplesN2[i]*_azimuth.sin()+samplesE2[i]*_azimuth.cos();
              t1=samplesN1[i]*_azimuth.cos()-samplesE1[i]*_azimuth.sin();
              t2=samplesN2[i]*_azimuth.cos()-samplesE2[i]*_azimuth.sin();
              powerR1 += r1 * r1;
              powerR2 += r2 * r2;
              powerT1 += t1 * t1;
              powerT2 += t2* t2;
              _radialAutocorr += r1 * r2;
              _transverseAutocorr += t1 * t2;
            }
          UNLOCK_SAMPLES(sE2)
        UNLOCK_SAMPLES(sN2)
      UNLOCK_SAMPLES(sE1)
    UNLOCK_SAMPLES(sN1)
    _radialPower=sqrt(powerR1 * powerR2);     // geometrical average of the powers
    _transversePower=sqrt(powerT1 * powerT2);
  }
#endif

} // namespace ArrayCore
