/***************************************************************************
**
**  This file is part of HVGui.
**
**  HVGui 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.
**
**  HVGui 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: 2003-12-12
**  Copyright: 2003-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <GeopsyGui.h>
#include <HVCore.h>

#include "CurveResultWidget.h"
#include "StatisticResults.h"

namespace HVGui {

  StatisticResults::StatisticResults(QWidget *parent)
    : GraphicSheetMenu(parent)
  {
    TRACE;
    setAttribute(Qt::WA_DeleteOnClose, true);
    Settings::getSize(this, "StatisticResults");
  }

  StatisticResults::~StatisticResults()
  {
    TRACE;
    Settings::setSize(this, "StatisticResults");
  }

  void StatisticResults::createObjects(QStringList& graphTitles)
  {
    TRACE;
    sheet()->setStatusBar(GeopsyGuiEngine::instance()->statusBar());
    int n=graphTitles.count();
    double y=0.5, dy=7, x=0.5, dx=10;
    for(int ig=0;ig < n;ig++ ) {
      AxisWindow * w=sheet()->addGraph();
      w->setGeometry(x, y, dx, dy);
      w->xAxis()->setFrequency();
      w->yAxis()->setTitle(graphTitles[ig]);
      _frequencyLayers.append(new ParallelBands(w));
      _averageLayers.append(new LineLayer(w) );
      PlotLine2D * line=new PlotLine2D;
      line->setPen(Pen( Qt::black, 0.6, Qt::SolidLine));
      line->setSymbol(Symbol());
      _averageLayers.last()->setReferenceLine(line);
      x-=0.5;
      if(x==dx) y+=dy;
      x=0.5+dx-x;
    }
  }

  void StatisticResults::setStatPlot(int graphIndex)
  {
    TRACE;
    LineLayer * plot=averageLayer(graphIndex);
    plot->clear();
    plot->addLine();
    plot->addLine(Pen( Qt::black, 0.6, Qt::DashLine), Symbol());
    plot->addLine(Pen( Qt::black, 0.6, Qt::DashLine), Symbol());
  }

  void StatisticResults::setStatPlot(int graphIndex,
                                     const Curve<Point2D>& curve0,
                                     const Curve<Point2D>& curve1,
                                     const Curve<Point2D>& curve2)
  {
    TRACE;
    setStatPlot(graphIndex);
    LineLayer * plot=averageLayer(graphIndex);
    static_cast<PlotLine2D *>(plot->line(0))->setCurve(curve0);
    static_cast<PlotLine2D *>(plot->line(1))->setCurve(curve1);
    static_cast<PlotLine2D *>(plot->line(2))->setCurve(curve2);
  }

  void StatisticResults::setBandsPlot(int graphIndex, const CurveResults * res)
  {
    TRACE;
    ParallelBands * plot=frequencyLayer(graphIndex);
    LayerLocker ll(plot);
    plot->clear();
    CurveResultWidget::addFrequencyBands(plot, res, 0);
  }


  void StatisticResults::studentTest(QWidget * parent, const CurveResults * ref, const CurveResults * test,
                                     double significance, const QString& makeUp)
  {
    TRACE;
    // Data curves
    const Curve<Point2D>& refCurve=ref->average();
    const Curve<Point2D>& refCurveMin=ref->stddevLow();

    const Curve<Point2D>& testCurve=test->average();
    const Curve<Point2D>& testCurveMin=test->stddevLow();

    // Resample the test curve according to reference
    Curve<Point2D> fTest(testCurve);
    fTest.resample(refCurve);
    Curve<Point2D> fTestMin(testCurveMin);
    fTestMin.resample(refCurve);
    /* This test assumes that the theoretical variance of both samples
       are the same. It can be tested by the Fisher test.
       More details can be found at the following URLs:
       http://www.math-info.univ-paris5.fr/smel/cours/ts/node12.html
       http://www.math-info.univ-paris5.fr/smel/cours/ts/node13.html

       If x is the reference station, and y the test station,
       if nx is the number of windows used for reference, and ny for test,
       if xm is the average for reference, and ym for test,
       if sx is the variance for reference, and sy for test,
        if the true variance of both station are identical, then:

                            sqrt(nx+ny-2)*(xm-ym)*(mux-muy)
       the random variable --------------------------------- follow a Student
                           sqrt(1/nx+1/ny)*sqrt(nx*sx+ny*sy)
       distribution T(nx+ny-2)

       We can compare the value of the statistic for mux=muy with the quartile of
       the Student distribution.

       See also Numerical Recipes chapter 14
    */
    // Construct the result document
    StatisticResults * statRes=new StatisticResults;
    statRes->setObjectName("StatisticResults");
    QStringList titles;
    titles.append(tr("Reference curve"));
    titles.append(tr("Test curve"));
    titles.append(tr("Std. deviations"));
    titles.append(tr("Std. dev. ratio (F-Test)"));
    titles.append(tr("Means ratio\n(Student, equal var)"));
    titles.append(tr("Means ratio\n(Student, unequal var)"));
    statRes->createObjects(titles);
    TextEdit * te=statRes->sheet()->addText();
    te->setGeometry(0.5, 21.5, 20, 5);
    double nx=ref->windowCount();
    double ny=test->windowCount();
    double nux=nx-1;
    double nuy=ny-1;
    Rect r;
    // Frequency peak bands (to copy to all graphs)
    if((ref->peakCount()>1 || test->peakCount()>1)) {
      Message::warning(MSG_ID, tr("Statistical comparison"),
                       tr("The reference or the test contains more than 1 peak frequency band.\n"
                          "Only the first will be used in comparison."), true);
    }
    // Reference curve
    AxisWindow * w;
    statRes->setStatPlot(0, refCurve, refCurveMin, ref->stddevHigh());
    statRes->setBandsPlot(0, ref);
    r=statRes->averageLayer(0)->boundingRect();
    w=statRes->averageLayer(0)->graph();
    w->xAxis()->setRange(r.x1(), r.x2());
    w->yAxis()->setRange(0, r.y2());
    // Test curve
    statRes->setStatPlot(1, testCurve, testCurveMin, test->stddevHigh());
    statRes->setBandsPlot(1, test);
    r=statRes->averageLayer(1) ->boundingRect();
    w=statRes->averageLayer(1)->graph();
    w->xAxis()->setRange(r.x1(), r.x2());
    w->yAxis()->setRange(0, r.y2());
    // Standard deviation plot
    statRes->setBandsPlot(2, ref);
    int nSamples=refCurve.count();
    double * fx=new double [nSamples];
    double * fy=new double [nSamples];
    int is;
    LineLayer * plotVar=statRes->averageLayer(2);
    plotVar->clear();
    plotVar->addLine(Pen( Qt::black, 0.6, Qt::SolidLine), Symbol());
    plotVar->addLine(Pen( Qt::black, 0.6, Qt::DashLine), Symbol());
    Curve<Point2D>& varRef=static_cast<PlotLine2D *>(plotVar->line(0))->curve();
    Curve<Point2D>& varTest=static_cast<PlotLine2D *>(plotVar->line(1))->curve();
    varRef.resize(nSamples);
    varTest.resize(nSamples);
    for(is=0; is<nSamples; is++) {
      double f=refCurve[ is ].x();
      varRef[is].setX(f);
      varTest[ is ].setX(f);
      fx[ is ]=refCurve[ is ].y()/refCurveMin[ is ].y();
      fy[ is ]=fTest.at(is).y()/fTestMin.at(is).y();
      varRef[ is ].setY(fx[ is ]);
      varTest[ is ].setY(fy[ is ]);
    }
    r=plotVar->boundingRect();
    w=plotVar->graph();
    w->xAxis()->setRange(r.x1(), r.x2());
    w->yAxis()->setRange(0, r.y2());
    // Fisher test, fx and fy are the standard deviations
    statRes->setStatPlot(3);
    statRes->setBandsPlot(3, ref);
    LineLayer * plotF=statRes->averageLayer(3);
    Curve<Point2D>& varRatio=static_cast<PlotLine2D *>(plotF->line(0))->curve();
    Curve<Point2D>& minFicher=static_cast<PlotLine2D *>(plotF->line(1))->curve();
    Curve<Point2D>& maxFicher=static_cast<PlotLine2D *>(plotF->line(2))->curve();
    varRatio.resize(nSamples);
    minFicher.resize(nSamples);
    maxFicher.resize(nSamples);
    double fminfactor=sqrt(StatComparator::invFisher(significance/2.0, nux, nuy) );
    double fmaxfactor=sqrt(StatComparator::invFisher(1.0 - significance/2.0, nux, nuy) );
    for(is=0;is <nSamples;is++ ) {
      double f=refCurve[ is ].x();
      varRatio[ is ].setX(f);
      minFicher[ is ].setX(f);
      maxFicher[ is ].setX(f);
      fx[ is ]=log10(fx[ is ] );
      fy[ is ]=log10(fy[ is ] );
      varRatio[ is ].setY(fx[ is ]/fy[ is ]);
      minFicher[ is ].setY(fminfactor);
      maxFicher[ is ].setY(fmaxfactor);
    }
    r=statRes->averageLayer(3)->boundingRect();
    w=statRes->averageLayer(3)->graph();
    w->xAxis()->setRange(r.x1(), r.x2());
    w->yAxis()->setRange(0.95 * r.y1(), 1.05 * r.y2());
    // Student test (equal variances)
    statRes->setStatPlot(4);
    statRes->setBandsPlot(4, ref);
    LineLayer * plotSE=statRes->averageLayer(4);
    Curve<Point2D>& meanRatioEq=static_cast<PlotLine2D *>(plotSE->line(0))->curve();
    Curve<Point2D>& minStudentEq=static_cast<PlotLine2D *>(plotSE->line(1))->curve();
    Curve<Point2D>& maxStudentEq=static_cast<PlotLine2D *>(plotSE->line(2))->curve();
    meanRatioEq.resize(nSamples);
    minStudentEq.resize(nSamples);
    maxStudentEq.resize(nSamples);
    double coef=sqrt(( 1.0/nx + 1.0/ny)/(nux + nuy) ) *
                  StatComparator::invStudent(significance/2, nux + nuy);
    double tmp;
    for(is=0;is <nSamples;is++ ) {
      double f=refCurve[ is ].x();
      meanRatioEq[ is ].setX(f);
      minStudentEq[ is ].setX(f);
      maxStudentEq[ is ].setX(f);
      meanRatioEq[ is ].setY(log10(refCurve[ is ].y()/fTest.at(is).y()));
      fx[ is ] *= fx[ is ];
      fy[ is ] *= fy[ is ];
      tmp=coef * sqrt(nx * fx[ is ] + ny * fy[ is ] );
      minStudentEq[ is ].setY(tmp);
      maxStudentEq[ is ].setY(-tmp);
    }
    // fx and fy are now the variances
    r=statRes->averageLayer(4)->boundingRect();
    w=statRes->averageLayer(4)->graph();
    w->xAxis()->setRange(r.x1(), r.x2());
    w->yAxis()->setRange(r.y1(), r.y2());
    // Student test (unequal variances)
    statRes->setStatPlot(5);
    statRes->setBandsPlot(5, ref);
    LineLayer * plotSU=statRes->averageLayer(5);
    Curve<Point2D>& meanRatioUneq=static_cast<PlotLine2D *>(plotSU->line(0))->curve();
    Curve<Point2D>& minStudentUneq=static_cast<PlotLine2D *>(plotSU->line(1))->curve();
    Curve<Point2D>& maxStudentUneq=static_cast<PlotLine2D *>(plotSU->line(2))->curve();
    meanRatioUneq.resize(nSamples);
    minStudentUneq.resize(nSamples);
    maxStudentUneq.resize(nSamples);
    double tmpx, tmpy;
    for(is=0;is <nSamples;is++ ) {
      double f=refCurve[ is ].x();
      meanRatioUneq[ is ].setX(f);
      minStudentUneq[ is ].setX(f);
      maxStudentUneq[ is ].setX(f);
      meanRatioUneq[ is ].setY(meanRatioEq[ is ].y());
      tmpx=fx[ is ]/nx;
      tmpy=fy[ is ]/ny;
      tmp=tmpx + tmpy;
      tmp=sqrt(tmp) * StatComparator::invStudent(significance, tmp * tmp/(tmpx * tmpx/nux + tmpy * tmpy/nuy) );
      minStudentUneq[ is ].setY(tmp);
      maxStudentUneq[ is ].setY(-tmp);
    }
    r=statRes->averageLayer(5)->boundingRect();
    w=statRes->averageLayer(5)->graph();
    w->xAxis()->setRange(r.x1(), r.x2());
    w->yAxis()->setRange(r.y1(), r.y2());
    delete [] fx;
    delete [] fy;
    // Comments on mean frequency
    QString cmt;
    cmt += "Frequency peak statistics:\n";
    double f0Ref, df0Ref, f0Test, df0Test;
    if(ref->peakCount()>0) {
      f0Ref=ref->peak(0, CurveResults::PeakFrequency);
      df0Ref=f0Ref-ref->peakMinimum(0);
      cmt+="Reference: %1 %2 %3 Hz   ";
    } else cmt+="No peak for Reference   ";
    if(test->peakCount()>0) {
      f0Test=test->peak(0, CurveResults::PeakFrequency);
      df0Test=f0Test-test->peakMinimum(0);
      cmt+="Test: %4 %5 %6 Hz\n";
    } else cmt+="No peak for Test\n";
    if(ref->peakCount()>0 && test->peakCount()>0) {
      double varf0Ref=df0Ref*df0Ref;
      double varf0Test=df0Test*df0Test;
      nx=ref->windowPeakCount(0);
      ny=test->windowPeakCount(0);
      nux=nx - 1;
      nuy=ny - 1;
      cmt=cmt
            .arg(f0Ref - df0Ref, 0, 'f', 2).arg(f0Ref, 0, 'f', 2).arg(f0Ref + df0Ref, 0, 'f', 2)
            .arg(f0Test - df0Test, 0, 'f', 2).arg(f0Test, 0, 'f', 2).arg(f0Test + df0Test, 0, 'f', 2);
      tmp=(nx/nux)/(ny/nuy);
      fminfactor=sqrt(StatComparator::invFisher(1.0 - significance/2.0, nux, nuy)/tmp);
      fmaxfactor=sqrt(StatComparator::invFisher(significance/2.0, nux, nuy)/tmp);
      cmt += "F-Test (%1): %2 in [ %3 , %4 ] => %5similar variances\n";
      tmp=df0Ref/df0Test;
      cmt=cmt
            .arg(significance)
            .arg(tmp)
            .arg(fminfactor).arg(fmaxfactor)
            .arg(( tmp >= fminfactor && tmp <= fmaxfactor) ? "" : "NOT " );
      tmp=sqrt(( 1.0/nx + 1.0/ny)/(nux + nuy) )
            * StatComparator::invStudent(significance/2, nux + nuy)
            * sqrt(nx * varf0Ref + ny * varf0Test);
      cmt += "Student Test (%1, equal variances): |delta(f0)|=%2 %3 %4 => %5similar peaks\n";
      cmt=cmt
            .arg(significance)
            .arg(fabs( f0Ref - f0Test) )
            .arg(fabs( f0Ref - f0Test) <= tmp ? "<=" : ">" )
            .arg(tmp)
            .arg(fabs( f0Ref - f0Test) <= tmp ? "" : "NOT " );
      tmpx=varf0Ref/nx;
      tmpy=varf0Test/ny;
      tmp=tmpx + tmpy;
      tmp=sqrt(tmp) * StatComparator::invStudent(significance/2, tmp * tmp/(tmpx * tmpx/nux + tmpy * tmpy/nuy) );
      cmt += "Student Test (%1, unequal variances): |delta(f0)|=%2 %3 %4 => %5similar peaks\n";
      cmt=cmt
            .arg(significance)
            .arg(fabs( f0Ref - f0Test) )
            .arg(fabs( f0Ref - f0Test) <= tmp ? "<=" : ">" )
            .arg(tmp)
            .arg(fabs( f0Ref - f0Test) <= tmp ? "" : "NOT " );
    }
    te->setText(cmt);
    te->update(); // recompute mask
    statRes->setWindowTitle(ref->parent()->name()+" vs "+test->parent()->name());
    if(!makeUp.isEmpty()) statRes->sheet()->restoreMakeUp(makeUp);
    GeopsyGuiEngine::instance()->addSubWindow(parent, statRes);
  }

} // namespace HVGui
