/*
 * Copyright (c) 2013 Damien Grauser (Damien.Grauser@gmail.com)
 *
 * This program 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 2 of the License, or (at your option)
 * any later version.
 *
 * This program 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 this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */


#include "ExtendedCriticalPower.h"
#include "Statistic.h"
#include "Colors.h"
#include "Settings.h"
#include "RideFileCache.h"
#include <qwt_symbol.h>
#include <qwt_color_map.h>
#include <qwt_text.h>

#include <QtGui>
#include <QMessageBox>

ExtendedCriticalPower::ExtendedCriticalPower(Context *context) : context(context)
{
}

ExtendedCriticalPower::~ExtendedCriticalPower()
{
}

TestModel
ExtendedCriticalPower::deriveExtendedCP_2_3_Parameters(RideFileCache *bests, RideFile::SeriesType series, double sanI1, double sanI2, double anI1, double anI2, double aeI1, double aeI2, double laeI1, double laeI2)
{
    TestModel model;
    model.version = "eCP v2.3";

    // bounds on anaerobic interval in minutes
    const double t1 = sanI1;
    const double t2 = sanI2;

    // bounds on anaerobic interval in minutes
    const double t3 = anI1;
    const double t4 = anI2;

    // bounds on aerobic interval in minutes
    const double t5 = aeI1;
    const double t6 = aeI2;

    // bounds on long aerobic interval in minutes
    const double t7 = laeI1;
    const double t8 = laeI2;

    // bounds of these time values in the data
    int i1, i2, i3, i4, i5, i6, i7, i8;

    // find the indexes associated with the bounds
    // the first point must be at least the minimum for the anaerobic interval, or quit
    for (i1 = 0; i1 < 60 * t1; i1++)
        if (i1 + 1 >= bests->meanMaxArray(series).size())
            return model;
    // the second point is the maximum point suitable for anaerobicly dominated efforts.
    for (i2 = i1; i2 + 1 <= 60 * t2; i2++)
        if (i2 + 1 >= bests->meanMaxArray(series).size())
            return model;

    // the third point is the beginning of the minimum duration for aerobic efforts
    for (i3 = i2; i3 < 60 * t3; i3++)
        if (i3 + 1 >= bests->meanMaxArray(series).size())
            return model;
    for (i4 = i3; i4 + 1 <= 60 * t4; i4++)
        if (i4 + 1 >= bests->meanMaxArray(series).size())
            break;

    // the fifth point is the beginning of the minimum duration for aerobic efforts
    for (i5 = i4; i5 < 60 * t5; i5++)
        if (i5 + 1 >= bests->meanMaxArray(series).size())
            return model;
    for (i6 = i5; i6 + 1 <= 60 * t6; i6++)
        if (i6 + 1 >= bests->meanMaxArray(series).size())
            break;

    // the first point must be at least the minimum for the anaerobic interval, or quit
    for (i7 = i6; i7 < 60 * t7; i7++)
        if (i7 + 1 >= bests->meanMaxArray(series).size())
            return model;
    // the second point is the maximum point suitable for anaerobicly dominated efforts.
    for (i8 = i7; i8 + 1 <= 60 * t8; i8++)
        if (i8 + 1 >= bests->meanMaxArray(series).size())
            break;

    bool withoutEcpDec = false;

    // initial estimate of tau
    if (model.paa == 0)
        model.paa = 300;

    if (model.paa_dec == 0)
        model.paa_dec = -2;

    if (model.etau == 0)
        model.etau = 1;

    if (model.ecp == 0)
        model.ecp = 300;

    if (model.ecp_del == 0)
        model.ecp_del = -1;

    if (model.ecp_dec == 0)
        model.ecp_dec = -0.2;

    if (model.ecp_dec_del == 0)
        model.ecp_dec_del = -180;


    // lower bound on tau
    const double paa_min = 100;
    //const double paa_max = 2000;
    const double etau_min = 0.5;
    const double paa_dec_max = -0.25;
    const double paa_dec_min = -3;
    const double ecp_dec_min = -1.5;

    // convergence delta for tau
    //const double ecp_delta_max = 1e-4;
    const double etau_delta_max = 1e-4;
    const double paa_delta_max = 1e-2;
    const double paa_dec_delta_max = 1e-4;
    const double ecp_del_delta_max = 1e-4;
    const double ecp_dec_delta_max  = 1e-8;

    // previous loop value of tau and t0
    double etau_prev;
    double paa_prev;
    double paa_dec_prev;
    double ecp_del_prev;
    double ecp_dec_prev;

    // maximum number of loops
    const int max_loops = 100;

    // loop to convergence
    int iteration = 0;
    do {
        if (iteration ++ > max_loops) {
            qDebug()<<"Maximum number of loops exceeded in ecp2 model";
            break;
        }

        // record the previous version of tau, for convergence
        etau_prev = model.etau;
        paa_prev = model.paa;
        paa_dec_prev = model.paa_dec;
        ecp_del_prev = model.ecp_del;
        ecp_dec_prev = model.ecp_dec;


        if (withoutEcpDec)
            model.ecp_dec = 0;

        // P = paa*exp(paa_dec*(x/60)) + ecp * (1-exp(ecp_del*x/60)) * (1+ecp_dec*exp(-180/x/60) (1 + etau/(x/60))
        // bests->meanMaxArray(series)[i] = paa*exp(paa_dec*(i/60.0)) + ecp * (1-exp(ecp*i/60.0)) * (exp(40+ecp_dec*i/60.0)) * ( 1 + etau/(i/60.0));

        // estimate ecp
        int i;
        model.ecp = 0;
        for (i = i5; i <= i6; i++) {
            double ecpn = (bests->meanMaxArray(series)[i] - model.paa*exp(model.paa_dec*(i/60.0))) / (1-exp(model.ecp_del*i/60.0)) / (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) / ( 1 + model.etau/(i/60.0));

            if (model.ecp < ecpn)
                model.ecp = ecpn;
        }
        //qDebug() << "estimate ecp" << model.ecp;

        // if ecp = 0; no valid data; give up
        if (model.ecp == 0.0)
            return model;

        // estimate etau
        model.etau = etau_min;
        for (i = i3; i <= i4; i++) {
            double etaun = ((bests->meanMaxArray(series)[i] - model.paa*exp(model.paa_dec*(i/60.0))) /model. ecp / (1-exp(model.ecp_del*i/60.0)) / (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) - 1) * (i/60.0);

            if (model.etau < etaun)
                model.etau = etaun;
        }
        //qDebug() << "estimate etau" << model.etau;

        model.paa_dec = paa_dec_min;
        for (i = i1; i <= i2; i++) {
            double paa_decn = log((bests->meanMaxArray(series)[i] - model.ecp * (1-exp(model.ecp_del*i/60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) * ( 1 + model.etau/(i/60.0)) ) / model.paa) / (i/60.0);
            if (model.paa_dec < paa_decn && paa_decn < paa_dec_max) {
                model.paa_dec = paa_decn;
            }
        }
        //qDebug() << "estimate paa_dec" << model.paa_dec;

        model.paa = paa_min;
        double _avg_paa = 0.0;
        int count=1;
        for (i = 1; i <= 8; i++) {
            double paan = (bests->meanMaxArray(series)[i] - model.ecp * (1-exp(model.ecp_del*i/60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) * ( 1 + model.etau/(i/60.0))) / exp(model.paa_dec*(i/60.0));
            _avg_paa = (double)((count-1)*_avg_paa+paan)/count;

            if (model.paa < paan)
                model.paa = paan;

            count++;
        }
        //qDebug() << "estimate paa" << model.paa;

        // Max power can be wrong data
        // Use avg if the diff is too big
        if (_avg_paa<0.95*model.paa) {
            model.paa = _avg_paa;
            //qDebug() << "use avg paa" << model.paa;
        }

        if (!withoutEcpDec) {
            model.ecp_dec = ecp_dec_min;
            for (i = i7; i <= i8; i=i+120) {
                double ecp_decn = ((bests->meanMaxArray(series)[i] - model.paa*exp(model.paa_dec*(i/60.0))) / model.ecp / (1-exp(model.ecp_del*i/60.0)) / ( 1 + model.etau/(i/60.0)) - 1) / exp(model.ecp_dec_del/(i / 60.0));

                if (ecp_decn > 0)
                    ecp_decn = 0;

                if (model.ecp_dec < ecp_decn)
                    model.ecp_dec = ecp_decn;
            }
            //qDebug() << "estimate ecp_dec" << model.ecp_dec;
        }
    } while ((fabs(model.etau - etau_prev) > etau_delta_max) ||
             (fabs(model.paa - paa_prev) > paa_delta_max)  ||
             (fabs(model.paa_dec - paa_dec_prev) > paa_dec_delta_max)  ||
             (fabs(model.ecp_del - ecp_del_prev) > ecp_del_delta_max)  ||
             (fabs(model.ecp_dec - ecp_dec_prev) > ecp_dec_delta_max)
            );


    qDebug() << iteration << "iterations";
    verifyMin(model.paa, paa_min, model.paa_dec, paa_dec_min, model.ecp_dec, ecp_dec_min, model.etau, etau_min);

    model.pMax = model.paa*exp(model.paa_dec*(1/60.0)) + model.ecp * (1-exp(model.ecp_del*(1/60.0))) * (1+model.ecp_dec*exp(model.ecp_dec_del/(1/60.0))) * ( 1 + model.etau/(1/60.0));
    model.mmp60 = model.paa*exp(model.paa_dec*(60.0)) + model.ecp * (1-exp(model.ecp_del*60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/(60.0))) * ( 1 + model.etau/(60.0));

    qDebug() <<"eCP(2.3) " << "paa" << model.paa << "paa_dec" << model.paa_dec << "ecp" << model.ecp << "etau" << model.etau  << "ecp_del" << model.ecp_del << "ecp_dec" << model.ecp_dec  << "ecp_dec_del" << model.ecp_dec_del;
    qDebug() <<"eCP(2.3) " << "pmax" << model.pMax << "mmp60" << model.mmp60;

    return model;
}

QwtPlotCurve*
ExtendedCriticalPower::getPlotCurveForExtendedCP_2_3(TestModel model)
{
    const int extendedCurve2_points = 1000;

    QVector<double> extended_cp_curve2_power(extendedCurve2_points);
    QVector<double> extended_cp_curve2_time(extendedCurve2_points);
    double tmin = 1.0/60;
    double tmax = 600;

    for (int i = 0; i < extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);
        extended_cp_curve2_time[i] = t;
        extended_cp_curve2_power[i] = model.paa * exp(model.paa_dec*(t)) + model.ecp * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 + model.etau/(t));
    }

    QwtPlotCurve *extendedCPCurve2 = new QwtPlotCurve("eCP");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        extendedCPCurve2->setRenderHint(QwtPlotItem::RenderAntialiased);
    QPen e2pen(GColor(CHEARTRATE));
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::DashLine);
    extendedCPCurve2->setPen(e2pen);
    extendedCPCurve2->setSamples(extended_cp_curve2_time.data(), extended_cp_curve2_power.data(), extendedCurve2_points);

    return extendedCPCurve2;
}

TestModel
ExtendedCriticalPower::deriveExtendedCP_4_3_Parameters(bool usebest, RideFileCache *bests, RideFile::SeriesType series, double sanI1, double sanI2, double anI1, double anI2, double aeI1, double aeI2, double laeI1, double laeI2)
{
    TestModel model;
    model.version = "eCP v4.3";

    // bounds on anaerobic interval in minutes
    const double t1 = sanI1;
    const double t2 = sanI2;

    // bounds on anaerobic interval in minutes
    const double t3 = anI1;
    const double t4 = anI2;

    // bounds on aerobic interval in minutes
    const double t5 = aeI1;
    const double t6 = aeI2;

    // bounds on long aerobic interval in minutes
    const double t7 = laeI1;
    const double t8 = laeI2;

    // bounds of these time values in the data
    int i1, i2, i3, i4, i5, i6, i7, i8;

    // find the indexes associated with the bounds
    // the first point must be at least the minimum for the anaerobic interval, or quit
    for (i1 = 0; i1 < 60 * t1; i1++)
        if (i1 + 1 >= bests->meanMaxArray(series).size())
            return model;
    // the second point is the maximum point suitable for anaerobicly dominated efforts.
    for (i2 = i1; i2 + 1 <= 60 * t2; i2++)
        if (i2 + 1 >= bests->meanMaxArray(series).size())
            return model;

    // the third point is the beginning of the minimum duration for aerobic efforts
    for (i3 = i2; i3 < 60 * t3; i3++)
        if (i3 + 1 >= bests->meanMaxArray(series).size())
            return model;
    for (i4 = i3; i4 + 1 <= 60 * t4; i4++)
        if (i4 + 1 >= bests->meanMaxArray(series).size())
            break;

    // the fifth point is the beginning of the minimum duration for aerobic efforts
    for (i5 = i4; i5 < 60 * t5; i5++)
        if (i5 + 1 >= bests->meanMaxArray(series).size())
            return model;
    for (i6 = i5; i6 + 1 <= 60 * t6; i6++)
        if (i6 + 1 >= bests->meanMaxArray(series).size())
            break;

    // the first point must be at least the minimum for the anaerobic interval, or quit
    for (i7 = i6; i7 < 60 * t7; i7++)
        if (i7 + 1 >= bests->meanMaxArray(series).size())
            return model;
    // the second point is the maximum point suitable for anaerobicly dominated efforts.
    for (i8 = i7; i8 + 1 <= 60 * t8; i8++)
        if (i8 + 1 >= bests->meanMaxArray(series).size())
            break;

    bool withoutEd = false;

    // initial estimate of tau
    if (model.paa == 0)
        model.paa = 300;

    if (model.etau == 0)
        model.etau = 1;

    if (model.ecp == 0)
        model.ecp = 300;

    if (model.paa_dec == 0)
        model.paa_dec = -2;

    if (model.ecp_del == 0)
        model.ecp_del = -1;

    if (model.ecp_dec == 0)
        model.ecp_dec = -1;

    if (model.ecp_dec_del == 0)
        model.ecp_dec_del = -180;


    // lower bound on tau
    const double paa_min = 100;
    //const double paa_max = 2000;
    const double etau_min = 0.5;
    const double paa_dec_max = -0.25;
    const double paa_dec_min = -3;
    const double ecp_dec_min = -5; // Long

    // convergence delta for tau
    //const double ecp_delta_max = 1e-4;
    const double etau_delta_max = 1e-4;
    const double paa_delta_max = 1e-2;
    const double paa_dec_delta_max = 1e-4;
    const double ecp_del_delta_max = 1e-4;
    const double ecp_dec_delta_max  = 1e-8;

    // previous loop value of tau and t0
    double etau_prev;
    double paa_prev;
    double paa_dec_prev;
    double ecp_del_prev;
    double ecp_dec_prev;

    // maximum number of loops
    const int max_loops = 100;

    // loop to convergence
    int iteration = 0;
    do {
        if (iteration ++ > max_loops) {
            qDebug()<<"Maximum number of loops exceeded in ecp5 model";
            break;
        }

        // record the previous version of tau, for convergence
        etau_prev = model.etau;
        paa_prev = model.paa;
        paa_dec_prev = model.paa_dec;
        ecp_del_prev = model.ecp_del;
        ecp_dec_prev = model.ecp_dec;

        if (withoutEd)
            model.ecp_dec = 0;

        // P = paa* (2.0-exp(-1*(x/60.0)))*exp(paa_dec*(x/60)) + ecp * (1-exp(ecp_del*x/60)) * (1+ecp_dec*exp(-180/x/60) (1 + etau/(x/60))
        // bests->meanMaxArray(series)[i] = paa*(2.0-exp(-1*(i/60.0)))*exp(paa_dec*(i/60.0))  + ecp * (1-exp(ecp*i/60.0)) * (1+2d*exp(180/i/60.0)) * ( 1 + etau/(i/60.0));

        // estimate ecp
        int i;

        model.ecp = 0;
        double _avg_ecp = 0.0;
        int count=1;
        for (i = i5; i <= i6; i++) {
            double ecpn = (bests->meanMaxArray(series)[i] - model.paa * (2.0-exp(-1*(i/60.0))) * exp(model.paa_dec*(i/60.0))) / (1-exp(model.ecp_del*i/60.0)) / (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) / ( 1 + model.etau/(i/60.0));
            _avg_ecp = (double)((count-1)*_avg_ecp+ecpn)/count;

            if (model.ecp < ecpn)
                model.ecp = ecpn;

            count++;
        }
        //qDebug() << "estimate ecp" << model.ecp;
        if (!usebest) {
            model.ecp = _avg_ecp;
        }

        // if ecp = 0; no valid data; give up
        if (model.ecp == 0.0)
            return model;

        // estimate etau
        model.etau = etau_min;
        double _avg_etau = 0.0;
        count=1;
        for (i = i3; i <= i4; i++) {
            double etaun = ((bests->meanMaxArray(series)[i] - model.paa * (2.0-exp(-1*(i/60.0))) * exp(model.paa_dec*(i/60.0))) /model. ecp / (1-exp(model.ecp_del*i/60.0)) / (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) - 1) * (i/60.0);
            _avg_etau = (double)((count-1)*_avg_etau+etaun)/count;

            if (model.etau < etaun)
                model.etau = etaun;
            count++;
        }
        //qDebug() << "estimate etau" << model.etau;
        if (!usebest) {
            model.etau = _avg_etau;
        }



        model.paa_dec = paa_dec_min;
        double _avg_paa_dec = 0.0;
        count=1;
        for (i = i1; i <= i2; i++) {
            double paa_decn = log((bests->meanMaxArray(series)[i] - model.ecp * (1-exp(model.ecp_del*i/60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) * ( 1 + model.etau/(i/60.0)) ) / model.paa / (2.0-exp(-1*(i/60.0))) ) / (i/60.0);
            _avg_paa_dec = (double)((count-1)*_avg_paa_dec+paa_decn)/count;

            if (model.paa_dec < paa_decn && paa_decn < paa_dec_max) {
                model.paa_dec = paa_decn;
            }
            count++;
        }
        //qDebug() << "estimate paa_dec" << model.paa_dec;
        if (!usebest) {
            model.paa_dec = _avg_paa_dec;
        }

        model.paa = paa_min;
        double _avg_paa = 0.0;
        count=1;
        for (i = 1; i <= 8; i++) {
            double paan = (bests->meanMaxArray(series)[i] - model.ecp * (1-exp(model.ecp_del*i/60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) * ( 1 + model.etau/(i/60.0))) / exp(model.paa_dec*(i/60.0)) / (2.0-exp(-1*(i/60.0)));
            _avg_paa = (double)((count-1)*_avg_paa+paan)/count;

            //qDebug() << "   estimate paan" << paan;
            if (model.paa < paan)
                model.paa = paan;
            count++;
        }
        //qDebug() << "estimate paa" << model.paa;
        if (!usebest  || _avg_paa<0.9*model.paa) {
            model.paa = _avg_paa;
        }

        if (!withoutEd) {
            model.ecp_dec = ecp_dec_min;
            double _avg_ecp_dec = 0.0;
            count=1;
            for (i = i7; i <= i8; i=i+120) {
                double ecp_decn = ((bests->meanMaxArray(series)[i] - model.paa * (2.0-exp(-1*(i/60.0)))*exp(model.paa_dec*(i/60.0))) / model.ecp / (1-exp(model.ecp_del*i/60.0)) / ( 1 + model.etau/(i/60.0)) -1 ) / exp(model.ecp_dec_del/(i / 60.0));
                _avg_ecp_dec = (double)((count-1)*_avg_ecp_dec+ecp_decn)/count;

                if (ecp_decn > 0)
                    ecp_decn = 0;

                if (model.ecp_dec < ecp_decn)
                    model.ecp_dec = ecp_decn;
                count++;
            }
            //qDebug() << "estimate ecp_dec" << model.ecp_dec;
            if (!usebest) {
                model.ecp_dec = _avg_ecp_dec;
            }

        }
    } while ((fabs(model.etau - etau_prev) > etau_delta_max) ||
             (fabs(model.paa - paa_prev) > paa_delta_max)  ||
             (fabs(model.paa_dec - paa_dec_prev) > paa_dec_delta_max)  ||
             (fabs(model.ecp_del - ecp_del_prev) > ecp_del_delta_max)  ||
             (fabs(model.ecp_dec - ecp_dec_prev) > ecp_dec_delta_max)
            );

    qDebug() << iteration << "iterations";
    verifyMin(model.paa, paa_min, model.paa_dec, paa_dec_min, model.ecp_dec, ecp_dec_min, model.etau, etau_min);

    model.pMax = model.paa*(2.0-exp(-1*(1/60.0)))*exp(model.paa_dec*(1/60.0)) + model.ecp * (1-exp(model.ecp_del*(1/60.0))) * (1+model.ecp_dec*exp(model.ecp_dec_del/(1/60.0))) * ( 1 + model.etau/(1/60.0));
    model.mmp60 = model.paa*(2.0-exp(-1*60.0))*exp(model.paa_dec*(60.0)) + model.ecp * (1-exp(model.ecp_del*60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/60.0)) * ( 1 + model.etau/(60.0));

    qDebug() <<"eCP(4.3) " << "paa" << model.paa  << "ecp" << model.ecp << "etau" << model.etau << "paa_dec" << model.paa_dec << "ecp_del" << model.ecp_del << "ecp_dec" << model.ecp_dec << "ecp_dec_del" << model.ecp_dec_del;
    qDebug() <<"eCP(4.3) " << "pmax" << model.pMax << "mmp60" << model.mmp60;

    return model;
}

TestModel
ExtendedCriticalPower::deriveExtendedCP_4_3_ParametersForBest(double best5s, double best1min, double best5min, double best1hour)
{
    TestModel model;

    // initial estimate of tau
    model.paa = 300;
    model.etau = 1;
    model.ecp = 300;
    model.paa_dec = -2;
    model.ecp_del = -1;
    model.ecp_dec = 0;

    // convergence delta for tau
    //const double ecp_delta_max = 1e-4;
    const double etau_delta_max = 1e-4;
    const double paa_delta_max = 1e-2;
    const double paa_dec_delta_max = 1e-4;
    const double ecp_del_delta_max = 1e-4;
    const double ecp_dec_delta_max  = 1e-8;

    // previous loop value of tau and t0
    double etau_prev;
    double paa_prev;
    double paa_dec_prev;
    double ecp_del_prev;
    double ecp_dec_prev;

    // maximum number of loops
    const int max_loops = 100;

    // loop to convergence
    int iteration = 0;
    do {
        if (iteration ++ > max_loops) {
            qDebug()<<"Maximum number of loops exceeded in ecp2 model";
            break;
        }

        if (model.paa_dec>0)
            model.paa_dec = -0.5;

        // record the previous version of tau, for convergence
        etau_prev = model.etau;
        paa_prev = model.paa;
        paa_dec_prev = model.paa_dec;
        ecp_del_prev = model.ecp_del;
        ecp_dec_prev = model.ecp_dec;

        //qDebug() << "best1hour" << best1hour;
        model.ecp = (best1hour - model.paa * (2.0-exp(-1*(3600/60.0))) * (exp(model.paa_dec*(3600/60.0))) ) / (1-exp(model.ecp_del*3600/60.0)) / (1+model.ecp_dec*exp(model.ecp_dec_del/(3600/60.0))) / ( 1 + model.etau/(3600/60.0));
        //qDebug() << "model.ecp" << model.ecp;

        //qDebug() << "best5min" << best5min;
        model.etau = ((best5min - model.paa * (2.0-exp(-1*(300/60.0))) * (exp(model.paa_dec*(300/60.0))) ) / model.ecp / (1-exp(model.ecp_del*300/60.0)) / (1+model.ecp_dec*exp(model.ecp_dec_del/(300/60.0))) - 1) * (300/60.0);
        //qDebug() << "model.etau" << model.etau;

        //qDebug() << "best1min" << best1min;
        model.paa_dec = log((best1min - model.ecp * (1-exp(model.ecp_del*60/60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/(60/60.0))) * ( 1 + model.etau/(60/60.0)) ) / model.paa / (2.0-exp(-1*(60/60.0)))) / (60/60.0);
        //qDebug() << "model.paa_dec" << model.paa_dec;

        //qDebug() << "best5s" << best5s;
        model.paa = (best5s - model.ecp * (1-exp(model.ecp_del*5/60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/(5/60.0))) * ( 1 + model.etau/(5/60.0))) / exp(model.paa_dec*(5/60.0)) / (2.0-exp(-1*(5/60.0)));
        //qDebug() << "model.paa" << model.paa;

    } while ((fabs(model.etau - etau_prev) > etau_delta_max) ||
             (fabs(model.paa - paa_prev) > paa_delta_max)  ||
             (fabs(model.paa_dec - paa_dec_prev) > paa_dec_delta_max)  ||
             (fabs(model.ecp_del - ecp_del_prev) > ecp_del_delta_max)  ||
             (fabs(model.ecp_dec - ecp_dec_prev) > ecp_dec_delta_max)
            );

    //qDebug() << iteration << "iterations";
    qDebug() <<"BEST eCP(4.3) " << "paa" << model.paa  << "ecp" << model.ecp << "etau" << model.etau << "paa_dec" << model.paa_dec << "ecp_del" << model.ecp_del  << "ecp_dec" << model.ecp_dec ;

    return model;
}

QwtPlotCurve*
ExtendedCriticalPower::getPlotCurveForExtendedCP_4_3(TestModel model)
{
    //qDebug() <<"getPlotCurveForExtendedCP_4_3()";
    //qDebug() <<"Model eCP(4.3) " << "paa" << model.paa  << "ecp" << model.ecp << "etau" << model.etau << "paa_dec" << model.paa_dec << "ecp_del" << model.ecp_del  << "ecp_dec" << model.ecp_dec ;

    const int extendedCurve2_points = 1000;

    QVector<double> extended_cp_curve2_power(extendedCurve2_points);
    QVector<double> extended_cp_curve2_time(extendedCurve2_points);
    double tmin = 1.0/60;
    double tmax = 600;

    for (int i = 0; i < extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);
        extended_cp_curve2_time[i] = t;
        extended_cp_curve2_power[i] = model.paa*(2.0-exp(-1*t))*exp(model.paa_dec*(t)) + model.ecp * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 + model.etau/(t));
    }

    QwtPlotCurve *extendedCPCurve2 = new QwtPlotCurve("eCP2");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        extendedCPCurve2->setRenderHint(QwtPlotItem::RenderAntialiased);
    QPen e2pen(Qt::yellow);
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::DashLine);
    extendedCPCurve2->setPen(e2pen);
    extendedCPCurve2->setSamples(extended_cp_curve2_time.data(), extended_cp_curve2_power.data(), extendedCurve2_points);

    return extendedCPCurve2;
}

QwtPlotCurve*
ExtendedCriticalPower::getPlotLevelForExtendedCP_4_3(TestModel model)
{
    const int extendedCurve2_points = 20;

    QVector<double> extended_cp_curve2_power(4*extendedCurve2_points);
    QVector<double> extended_cp_curve2_time(4*extendedCurve2_points);

    double tmin = 1.0/60;
    double tmax = 8.0/60;
    for (int i = 0; i < extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);
        extended_cp_curve2_time[i] = t;
        extended_cp_curve2_power[i] = model.paa*(2.0-exp(-1*t))*exp(model.paa_dec*(t)) + model.ecp * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 + model.etau/(t));
    }

    tmin = 20.0/60;
    tmax = 40.0/60;
    for (int i = 1*extendedCurve2_points; i < 2*extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);
        extended_cp_curve2_time[i] = t;
        extended_cp_curve2_power[i] = model.paa*(2.0-exp(-1*t))*exp(model.paa_dec*(t)) + model.ecp * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 + model.etau/(t));
    }

    tmin = 2.0;
    tmax = 5.0;
    for (int i = 2*extendedCurve2_points; i < 3*extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);
        extended_cp_curve2_time[i] = t;
        extended_cp_curve2_power[i] = model.paa*(2.0-exp(-1*t))*exp(model.paa_dec*(t)) + model.ecp * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 + model.etau/(t));
    }

    tmin = 10.0;
    tmax = 45.0;
    for (int i = 3*extendedCurve2_points; i < 4*extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);
        extended_cp_curve2_time[i] = t;
        extended_cp_curve2_power[i] = model.paa*(2.0-exp(-1*t))*exp(model.paa_dec*(t)) + model.ecp * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 + model.etau/(t));
    }

    QwtPlotCurve *extendedCPCurve2 = new QwtPlotCurve("eCP2");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        extendedCPCurve2->setRenderHint(QwtPlotItem::RenderAntialiased);
    QPen e2pen(GColor(CCP));
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::NoPen);
    extendedCPCurve2->setPen(e2pen);

    QwtSymbol *sym = new QwtSymbol;
    sym->setStyle(QwtSymbol::HLine);
    sym->setSize(2 *dpiXFactor);
    sym->setPen(QPen(Qt::black));
    sym->setBrush(QBrush(Qt::NoBrush));
    extendedCPCurve2->setSymbol(sym);

    extendedCPCurve2->setSamples(extended_cp_curve2_time.data(), extended_cp_curve2_power.data(), 4*extendedCurve2_points);

    return extendedCPCurve2;
}

QwtPlotMarker*
ExtendedCriticalPower::getPlotMarkerForExtendedCP(TestModel model)
{
    QwtPlotMarker* extendedCurveTitle2 = new QwtPlotMarker();
    QString extendedCurve2_title;

    extendedCurve2_title.asprintf("CP=%.0f W, MMP60=%.0d W, Pmax=%.0d W, W'=%.0f kJ (%s)", model.ecp, model.mmp60, model.pMax, model.etau*model.ecp* 60.0 / 1000.0, model.version.toLatin1().constData());
    QwtText text(extendedCurve2_title, QwtText::PlainText);
    text.setColor(GColor(CPLOTMARKER));
    extendedCurveTitle2->setLabel(text);

    return extendedCurveTitle2;
}

QwtPlotCurve*
ExtendedCriticalPower::getPlotCurveForExtendedCP_4_3_WSecond(TestModel model)
{
    const int extendedCurve2_points = 1000;

    QVector<double> extended_cp_curve2_power(extendedCurve2_points);
    QVector<double> extended_cp_curve2_time(extendedCurve2_points);
    double tmin = 1.0/60;
    double tmax = 600;

    for (int i = 0; i < extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);
        extended_cp_curve2_time[i] = t;
        extended_cp_curve2_power[i] = model.paa*(2.0-exp(-1*t))*exp(model.paa_dec*(t));
    }

    QwtPlotCurve *extendedCPCurve2 = new QwtPlotCurve("eCP_4_3_WSecond");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        extendedCPCurve2->setRenderHint(QwtPlotItem::RenderAntialiased);
    QPen e2pen(GColor(CCADENCE));
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::DashLine);
    extendedCPCurve2->setPen(e2pen);
    extendedCPCurve2->setSamples(extended_cp_curve2_time.data(), extended_cp_curve2_power.data(), extendedCurve2_points);

    return extendedCPCurve2;
}

QwtPlotCurve*
ExtendedCriticalPower::getPlotCurveForExtendedCP_4_3_WPrime(TestModel model)
{
    const int extendedCurve2_points = 1000;

    QVector<double> extended_cp_curve2_power(extendedCurve2_points);
    QVector<double> extended_cp_curve2_time(extendedCurve2_points);
    double tmin = 1.0/60;
    double tmax = 600;

    for (int i = 0; i < extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);
        extended_cp_curve2_time[i] = t;
        extended_cp_curve2_power[i] = model.ecp * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( model.etau/(t) );
    }

    QwtPlotCurve *extendedCPCurve2 = new QwtPlotCurve("eCP_4_3_WPrime");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        extendedCPCurve2->setRenderHint(QwtPlotItem::RenderAntialiased);
    QPen e2pen(GColor(CPOWER));
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::DashLine);
    extendedCPCurve2->setPen(e2pen);
    extendedCPCurve2->setSamples(extended_cp_curve2_time.data(), extended_cp_curve2_power.data(), extendedCurve2_points);

    return extendedCPCurve2;
}

QwtPlotCurve*
ExtendedCriticalPower::getPlotCurveForExtendedCP_4_3_CP(TestModel model)
{
    const int extendedCurve2_points = 1000;

    QVector<double> extended_cp_curve2_power(extendedCurve2_points);
    QVector<double> extended_cp_curve2_time(extendedCurve2_points);
    double tmin = 1.0/60;
    double tmax = 600;

    for (int i = 0; i < extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);
        extended_cp_curve2_time[i] = t;
        extended_cp_curve2_power[i] = model.ecp * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 );
    }

    QwtPlotCurve *extendedCPCurve2 = new QwtPlotCurve("eCP_4_3_CP");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        extendedCPCurve2->setRenderHint(QwtPlotItem::RenderAntialiased);
    QPen e2pen(GColor(CHEARTRATE));
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::DashLine);
    extendedCPCurve2->setPen(e2pen);
    extendedCPCurve2->setSamples(extended_cp_curve2_time.data(), extended_cp_curve2_power.data(), extendedCurve2_points);

    return extendedCPCurve2;
}



QwtPlotCurve*
ExtendedCriticalPower::getPlotCurveForExtendedCP_4_3_WPrime_CP(TestModel model)
{
    const int extendedCurve2_points = 1000;

    QVector<double> extended_cp_curve2_power(extendedCurve2_points);
    QVector<double> extended_cp_curve2_time(extendedCurve2_points);
    double tmin = 1.0/60;
    double tmax = 600;

    for (int i = 0; i < extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);
        extended_cp_curve2_time[i] = t;
        extended_cp_curve2_power[i] = model.ecp * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 + model.etau/(t));
    }

    QwtPlotCurve *extendedCPCurve2 = new QwtPlotCurve("eCP_4_3_WPrime_CP");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        extendedCPCurve2->setRenderHint(QwtPlotItem::RenderAntialiased);
    QPen e2pen(GColor(CCP));
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::DashLine);
    extendedCPCurve2->setPen(e2pen);
    extendedCPCurve2->setSamples(extended_cp_curve2_time.data(), extended_cp_curve2_power.data(), extendedCurve2_points);

    return extendedCPCurve2;
}

TestModel
ExtendedCriticalPower::deriveExtendedCP_5_3_Parameters(bool usebest, RideFileCache *bests, RideFile::SeriesType series, double sanI1, double sanI2, double anI1, double anI2, double aeI1, double aeI2, double laeI1, double laeI2)
{
    TestModel model;
    model.version = "eCP v5.3";
    qDebug() << model.version;

    // bounds on anaerobic interval in minutes
    const double t1 = sanI1 / 60.0;
    const double t2 = sanI2 / 60.0;

    // bounds on anaerobic interval in minutes
    const double t3 = anI1 / 60.0;
    const double t4 = anI2 / 60.0;

    // bounds on aerobic interval in minutes
    const double t5 = aeI1 / 60.0;
    const double t6 = aeI2 / 60.0;

    // bounds on long aerobic interval in minutes
    const double t7 = laeI1 / 60.0;
    const double t8 = laeI2 / 60.0;

    // bounds of these time values in the data
    int i1, i2, i3, i4, i5, i6, i7, i8;

    // find the indexes associated with the bounds
    // the first point must be at least the minimum for the anaerobic interval, or quit
    for (i1 = 0; i1 < 60 * t1; i1++)
        if (i1 + 1 >= bests->meanMaxArray(series).size())
            return model;
    // the second point is the maximum point suitable for anaerobicly dominated efforts.
    for (i2 = i1; i2 + 1 <= 60 * t2; i2++)
        if (i2 + 1 >= bests->meanMaxArray(series).size())
            return model;

    // the third point is the beginning of the minimum duration for aerobic efforts
    for (i3 = i2; i3 < 60 * t3; i3++)
        if (i3 + 1 >= bests->meanMaxArray(series).size())
            return model;
    for (i4 = i3; i4 + 1 <= 60 * t4; i4++)
        if (i4 + 1 >= bests->meanMaxArray(series).size())
            break;

    // the fifth point is the beginning of the minimum duration for aerobic efforts
    for (i5 = i4; i5 < 60 * t5; i5++)
        if (i5 + 1 >= bests->meanMaxArray(series).size())
            return model;
    for (i6 = i5; i6 + 1 <= 60 * t6; i6++)
        if (i6 + 1 >= bests->meanMaxArray(series).size())
            break;

    // the first point must be at least the minimum for the anaerobic interval, or quit
    for (i7 = i6; i7 < 60 * t7; i7++)
        if (i7 + 1 >= bests->meanMaxArray(series).size())
            return model;
    // the second point is the maximum point suitable for anaerobicly dominated efforts.
    for (i8 = i7; i8 + 1 <= 60 * t8; i8++)
        if (i8 + 1 >= bests->meanMaxArray(series).size())
            break;

    bool withoutEd = false;

    // initial estimate of tau
    if (model.paa == 0)
        model.paa = 300;

    if (model.etau == 0)
        model.etau = 1;

    if (model.ecp == 0)
        model.ecp = 300;

    if (model.paa_dec == 0)
        model.paa_dec = -2;

    if (model.ecp_del == 0)
        model.ecp_del = -0.9;

    if (model.tau_del == 0)
        model.tau_del = -4.8;

    if (model.ecp_dec == 0)
        model.ecp_dec = -1;

    if (model.ecp_dec_del == 0)
        model.ecp_dec_del = -180;

    // lower bound on tau
    const double paa_min = 100;
    //const double paa_max = 2000;
    const double etau_min = 0.5;
    const double paa_dec_max = -0.25;
    const double paa_dec_min = -3;
    const double ecp_dec_min = -5; // Long

    // convergence delta for tau
    //const double ecp_delta_max = 1e-4;
    const double etau_delta_max = 1e-4;
    const double paa_delta_max = 1e-2;
    const double paa_dec_delta_max = 1e-4;
    const double ecp_del_delta_max = 1e-4;
    const double ecp_dec_delta_max  = 1e-8;

    // previous loop value of tau and t0
    double etau_prev;
    double paa_prev;
    double paa_dec_prev;
    double ecp_del_prev;
    double ecp_dec_prev;

    // maximum number of loops
    const int max_loops = 100;

    // loop to convergence
    int iteration = 0;

    qDebug() << "start iteration";
    do {
        if (iteration ++ > max_loops) {
            qDebug()<<"Maximum number of loops exceeded in ecp2 model";
            break;
        }

        // record the previous version of tau, for convergence
        etau_prev = model.etau;
        paa_prev = model.paa;
        paa_dec_prev = model.paa_dec;
        ecp_del_prev = model.ecp_del;
        ecp_dec_prev = model.ecp_dec;

        if (withoutEd)
            model.ecp_dec = 0;

        // P = paa* (1.20-0.20*exp(-1*(x/60.0)))*exp(paa_dec*(x/60)) + ecp * (1-exp(tau_del*x/60)) * (1-exp(ecp_del*x/60)) * (1+ecp_dec*exp(-180/x/60) (1 + etau/(x/60))
        // bests->meanMaxArray(series)[i] = paa*(1.20-0.20*exp(-1*(i/60.0)))*exp(paa_dec*(i/60.0))  + ecp * (1-exp(tau_del*x/60)) * (1-exp(ecp*i/60.0)) * (1+2d*exp(180/i/60.0)) * ( 1 + etau/(i/60.0));

        // estimate ecp
        int i;

        model.ecp = 0;
        double _avg_ecp = 0.0;
        int count=1;
        for (i = i5; i <= i6; i++) {
            double ecpn = (bests->meanMaxArray(series)[i] - model.paa * (1.20-0.20*exp(-1*(i/60.0))) * exp(model.paa_dec*(i/60.0))) / (1-exp(model.tau_del*i/60.0)) / (1-exp(model.ecp_del*i/60.0)) / (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) / ( 1 + model.etau/(i/60.0));
            _avg_ecp = (double)((count-1)*_avg_ecp+ecpn)/count;

            if (model.ecp < ecpn)
                model.ecp = ecpn;

            count++;
        }
        //qDebug() << "estimate ecp" << model.ecp;
        if (!usebest) {
            model.ecp = _avg_ecp;
        }

        // if ecp = 0; no valid data; give up
        if (model.ecp == 0.0)
            return model;

        // estimate etau
        model.etau = etau_min;
        double _avg_etau = 0.0;
        count=1;
        for (i = i3; i <= i4; i++) {
            double etaun = ((bests->meanMaxArray(series)[i] - model.paa * (1.20-0.20*exp(-1*(i/60.0))) * exp(model.paa_dec*(i/60.0))) /model. ecp / (1-exp(model.tau_del*i/60.0)) / (1-exp(model.ecp_del*i/60.0)) / (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) - 1) * (i/60.0);
            _avg_etau = (double)((count-1)*_avg_etau+etaun)/count;

            if (model.etau < etaun)
                model.etau = etaun;
            count++;
        }
        //qDebug() << "estimate etau" << model.etau;
        if (!usebest) {
            model.etau = _avg_etau;
        }



        model.paa_dec = paa_dec_min;
        double _avg_paa_dec = 0.0;
        count=1;
        for (i = i1; i <= i2; i++) {
            double paa_decn = log((bests->meanMaxArray(series)[i] - model.ecp * (1-exp(model.tau_del*i/60.0)) * (1-exp(model.ecp_del*i/60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) * ( 1 + model.etau/(i/60.0)) ) / model.paa / (1.20-0.20*exp(-1*(i/60.0))) ) / (i/60.0);
            _avg_paa_dec = (double)((count-1)*_avg_paa_dec+paa_decn)/count;

            if (model.paa_dec < paa_decn && paa_decn < paa_dec_max) {
                model.paa_dec = paa_decn;
            }
            count++;
        }
        //qDebug() << "estimate paa_dec" << model.paa_dec;
        if (!usebest) {
            model.paa_dec = _avg_paa_dec;
        }

        model.paa = paa_min;
        double _avg_paa = 0.0;
        count=1;
        for (i = 2; i <= 8; i++) {
            double paan = (bests->meanMaxArray(series)[i] - model.ecp * (1-exp(model.tau_del*i/60.0)) * (1-exp(model.ecp_del*i/60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) * ( 1 + model.etau/(i/60.0))) / exp(model.paa_dec*(i/60.0)) / (1.20-0.20*exp(-1*(i/60.0)));
            _avg_paa = (double)((count-1)*_avg_paa+paan)/count;

            //qDebug() << "   estimate paan" << paan;
            if (model.paa < paan)
                model.paa = paan;
            count++;
        }
        //qDebug() << "estimate paa" << model.paa;
        if (!usebest  || _avg_paa<0.95*model.paa) {
            model.paa = _avg_paa;
        }

        if (!withoutEd) {
            model.ecp_dec = ecp_dec_min;
            double _avg_ecp_dec = 0.0;
            count=1;
            for (i = i7; i <= i8; i=i+120) {
                double ecp_decn = ((bests->meanMaxArray(series)[i] - model.paa * (1.20-0.20*exp(-1*(i/60.0))) * exp(model.paa_dec*(i/60.0))) / model.ecp / (1-exp(model.tau_del*i/60.0)) / (1-exp(model.ecp_del*i/60.0)) / ( 1 + model.etau/(i/60.0)) -1 ) / exp(model.ecp_dec_del/(i / 60.0));
                _avg_ecp_dec = (double)((count-1)*_avg_ecp_dec+ecp_decn)/count;

                if (ecp_decn > 0)
                    ecp_decn = 0;

                if (model.ecp_dec < ecp_decn)
                    model.ecp_dec = ecp_decn;
                count++;
            }
            //qDebug() << "estimate ecp_dec" << model.ecp_dec;
            if (!usebest) {
                model.ecp_dec = _avg_ecp_dec;
            }

        }
    } while ((fabs(model.etau - etau_prev) > etau_delta_max) ||
             (fabs(model.paa - paa_prev) > paa_delta_max)  ||
             (fabs(model.paa_dec - paa_dec_prev) > paa_dec_delta_max)  ||
             (fabs(model.ecp_del - ecp_del_prev) > ecp_del_delta_max)  ||
             (fabs(model.ecp_dec - ecp_dec_prev) > ecp_dec_delta_max)
            );

    qDebug() << iteration << "iterations";
    verifyMin(model.paa, paa_min, model.paa_dec, paa_dec_min, model.ecp_dec, ecp_dec_min, model.etau, etau_min);

    model.pMax = model.paa*(1.20-0.20*exp(-1*(1/60.0)))*exp(model.paa_dec*(1/60.0)) + model.ecp * (1-exp(model.tau_del*(1/60.0))) * (1-exp(model.ecp_del*(1/60.0))) * (1+model.ecp_dec*exp(model.ecp_dec_del/(1/60.0))) * ( 1 + model.etau/(1/60.0));
    model.mmp60 = model.paa*(1.20-0.20*exp(-1*60.0))*exp(model.paa_dec*(60.0)) + model.ecp * (1-exp(model.tau_del*(60.0))) * (1-exp(model.ecp_del*60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/60.0)) * ( 1 + model.etau/(60.0));

    qDebug() <<"-> eCP(5.3) " << "paa" << model.paa  << "ecp" << model.ecp << "etau" << model.etau << "paa_dec" << model.paa_dec << "ecp_del" << model.ecp_del << "ecp_dec" << model.ecp_dec << "ecp_dec_del" << model.ecp_dec_del;
    qDebug() <<"-> eCP(5.3) " << "pmax" << model.pMax << "mmp60" << model.mmp60;

    return model;
}

TestModel
ExtendedCriticalPower::deriveExtendedCP_5_3_ParametersForBest(double best5s, double best1min, double best5min, double best1hour)
{
    TestModel model;
    model.version = "eCP v5.3";

    // initial estimate of tau
    model.paa = 300;
    model.paa_dec = -2;
    model.etau = 1;
    model.tau_del = -4.8;
    model.ecp = 300;
    model.ecp_del = -0.9;
    model.ecp_dec = 0;
    model.ecp_dec_del = -180;

    // convergence delta for tau
    const double etau_delta_max = 1e-4;
    const double paa_delta_max = 1e-2;
    const double paa_dec_delta_max = 1e-4;

    // previous loop value of tau and t0
    double etau_prev;
    double paa_prev;
    double paa_dec_prev;

    // maximum number of loops
    const int max_loops = 100;

    // loop to convergence
    int iteration = 0;
    do {
        if (iteration ++ > max_loops) {
            qDebug()<<"Maximum number of loops exceeded in ecp2 model";
            break;
        }

        // record the previous version of tau, for convergence
        etau_prev = model.etau;
        paa_prev = model.paa;
        paa_dec_prev = model.paa_dec;

        // P = paa* (1.20-0.20*exp(-1*(x/60.0)))*exp(paa_dec*(x/60)) + ecp * (1-exp(tau_del*x/60)) * (1-exp(ecp_del*x/60)) * (1+ecp_dec*exp(-180/x/60) (1 + etau/(x/60))
        // bests->meanMaxArray(series)[i] = paa*(1.20-0.20*exp(-1*(i/60.0)))*exp(paa_dec*(i/60.0))  + ecp * (1-exp(tau_del*x/60)) * (1-exp(ecp*i/60.0)) * (1+2d*exp(180/i/60.0)) * ( 1 + etau/(i/60.0));

        // estimate ecp
        model.ecp = (best1hour - model.paa * (1.20-0.20*exp(-1*(3600/60.0))) * exp(model.paa_dec*(3600/60.0))) / (1-exp(model.tau_del*(3600/60.0))) / (1-exp(model.ecp_del*(3600/60.0))) / (1+model.ecp_dec*exp(model.ecp_dec_del/(3600/60.0))) / ( 1 + model.etau/(3600/60.0));
        //qDebug() << "model.ecp" << model.ecp;

        // estimate etau
        model.etau = ((best5min - model.paa * (1.20-0.20*exp(-1*(300/60.0))) * exp(model.paa_dec*(300/60.0))) /model.ecp / (1-exp(model.tau_del*(300/60.0))) / (1-exp(model.ecp_del*(300/60.0))) / (1+model.ecp_dec*exp(model.ecp_dec_del/(300/60.0))) - 1) * (300/60.0);
        //qDebug() << "model.etau" << model.etau;

        // estimate paa_dec
        model.paa_dec = log((best1min - model.ecp * (1-exp(model.tau_del*(60/60.0))) * (1-exp(model.ecp_del*(60/60.0))) * (1+model.ecp_dec*exp(model.ecp_dec_del/(60/60.0))) * ( 1 + model.etau/(60/60.0)) ) / model.paa / (1.20-0.20*exp(-1*(60/60.0))) ) / (60/60.0);
        //qDebug() << "paa_dec.etau" << model.paa_dec;

        // estimate paa
        model.paa = (best5s - model.ecp * (1-exp(model.tau_del*(5/60.0))) * (1-exp(model.ecp_del*(5/60.0))) * (1+model.ecp_dec*exp(model.ecp_dec_del/(5/60.0))) * ( 1 + model.etau/(5/60.0))) / exp(model.paa_dec*(5/60.0)) / (1.20-0.20*exp(-1*(5/60.0)));
        //qDebug() << "model.paa" << model.paa;

    } while ((fabs(model.etau - etau_prev) > etau_delta_max) ||
             (fabs(model.paa - paa_prev) > paa_delta_max)  ||
             (fabs(model.paa_dec - paa_dec_prev) > paa_dec_delta_max)
            );

    qDebug() <<"BEST eCP(5.3) " << "paa" << model.paa  << "ecp" << model.ecp << "etau" << model.etau << "paa_dec" << model.paa_dec << "ecp_del" << model.ecp_del << "ecp_dec" << model.ecp_dec << "ecp_dec_del" << model.ecp_dec_del;

    return model;
}

QwtPlotCurve*
ExtendedCriticalPower::getPlotCurveForExtendedCP_5_3(TestModel model)
{
    const int extendedCurve2_points = 1000;

    QVector<double> extended_cp_curve2_power(extendedCurve2_points);
    QVector<double> extended_cp_curve2_time(extendedCurve2_points);
    double tmin = 1.0/60;
    double tmax = 600;

    for (int i = 0; i < extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);
        extended_cp_curve2_time[i] = t;
        extended_cp_curve2_power[i] = model.paa*(1.20-0.20*exp(-1*t))*exp(model.paa_dec*(t)) + model.ecp * (1-exp(model.tau_del*t)) * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 + model.etau/(t));
    }

    QwtPlotCurve *extendedCPCurve2 = new QwtPlotCurve("eCP_5_3");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        extendedCPCurve2->setRenderHint(QwtPlotItem::RenderAntialiased);
    QPen e2pen(Qt::cyan); // Qt::cyan GColor(CCP)
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::DashLine);
    extendedCPCurve2->setPen(e2pen);
    extendedCPCurve2->setSamples(extended_cp_curve2_time.data(), extended_cp_curve2_power.data(), extendedCurve2_points);

    return extendedCPCurve2;
}

QwtPlotCurve*
ExtendedCriticalPower::getPlotLevelForExtendedCP_5_3(TestModel model)
{
    const int extendedCurve2_points = 1000;

    QVector<double> extended_cp_curve2_power(extendedCurve2_points);
    QVector<double> extended_cp_curve2_time(extendedCurve2_points);
    double tmin = 1.0/60;
    double tmax = 600;

    for (int i = 0; i < extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);
        extended_cp_curve2_time[i] = t;
        extended_cp_curve2_power[i] = model.paa*(1.20-0.20*exp(-1*t))*exp(model.paa_dec*(t)) + model.ecp * (1-exp(model.tau_del*t)) * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 + model.etau/(t));
    }

    QwtPlotCurve *extendedCPCurve2 = new QwtPlotCurve("level_eCP_5_3");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        extendedCPCurve2->setRenderHint(QwtPlotItem::RenderAntialiased);
    QPen e2pen(GColor(CRIDEPLOTYAXIS));
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::SolidLine);
    extendedCPCurve2->setPen(e2pen);
    extendedCPCurve2->setSamples(extended_cp_curve2_time.data(), extended_cp_curve2_power.data(), extendedCurve2_points);

    return extendedCPCurve2;
}

QwtPlotIntervalCurve*
ExtendedCriticalPower::getPlotCurveForExtendedCP_5_3_WSecond(TestModel model, bool stacked, bool inversed, int balance)
{
    const int extendedCurve2_points = 1000;

    QVector<QwtIntervalSample> extended_cp_curve_power(extendedCurve2_points);

    double tmin = 1.0/60;
    double tmax = 600;

    for (int i = 0; i < extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);

        double power_wsecond = model.paa * (1.20-0.20*exp(-1*t))*exp(model.paa_dec*(t));
        double power_wprime = model.ecp * (1-exp(model.tau_del*t)) * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( balance/100.0 * model.etau/(t) );
        double power_cp = model.ecp * (1-exp(model.tau_del*t)) * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 );


        extended_cp_curve_power[i] = QwtIntervalSample(t, (stacked?(inversed?power_cp+power_wprime:0):0), (stacked?(inversed?power_cp+power_wprime+power_wsecond:power_wsecond):power_wsecond));
    }

    QwtPlotIntervalCurve *extendedCPCurve = new QwtPlotIntervalCurve("eCP_5_3_WSecond");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        extendedCPCurve->setRenderHint(QwtPlotItem::RenderAntialiased);

    QPen e2pen(GColor(CCADENCE));
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::DashLine);
    extendedCPCurve->setPen(e2pen);

    QColor color1 = GColor(CCADENCE);
    color1.setAlpha(64);
    QColor color2 = color1.darker();
    QLinearGradient linearGradient(0, 0, 0, model.paa);
    linearGradient.setColorAt(0.0, color1);
    linearGradient.setColorAt(1.0, color2);
    linearGradient.setSpread(QGradient::PadSpread);
    extendedCPCurve->setBrush(linearGradient);   // fill below the line

    extendedCPCurve->setSamples(new QwtIntervalSeriesData(extended_cp_curve_power));

    return extendedCPCurve;
}

QwtPlotIntervalCurve*
ExtendedCriticalPower::getPlotCurveForExtendedCP_5_3_WPrime(TestModel model, bool stacked, bool inversed, int balance)
{
    const int extendedCurve2_points = 1000;

    QVector<QwtIntervalSample> extended_cp_curve_power(extendedCurve2_points);

    double tmin = 1.0/60;
    double tmax = 600;

    for (int i = 0; i < extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);

        double power_wsecond = model.paa*(1.20-0.20*exp(-1*t))*exp(model.paa_dec*(t));
        double power_wprime = model.ecp * (1-exp(model.tau_del*t)) * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( balance/100.0 * model.etau/(t) );
        double power_cp = model.ecp * (1-exp(model.tau_del*t)) * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 );

        extended_cp_curve_power[i] = QwtIntervalSample(t, (stacked?(inversed?power_cp:power_wsecond):0), (stacked?(inversed?power_wprime + power_cp: power_wprime + power_wsecond):power_wprime));
    }

    QwtPlotIntervalCurve *extendedCPCurve = new QwtPlotIntervalCurve("eCP_5_3_WPrime");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        extendedCPCurve->setRenderHint(QwtPlotItem::RenderAntialiased);

    QPen e2pen(GColor(CPOWER));
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::DashLine);
    extendedCPCurve->setPen(e2pen);

    QColor color1 = GColor(CPOWER);
    color1.setAlpha(64);
    QColor color2 = color1.darker();
    QLinearGradient linearGradient(0, 0, 0, model.ecp);
    linearGradient.setColorAt(0.0, color1);
    linearGradient.setColorAt(1.0, color2);
    linearGradient.setSpread(QGradient::PadSpread);
    extendedCPCurve->setBrush(linearGradient);   // fill below the line

    extendedCPCurve->setSamples(new QwtIntervalSeriesData(extended_cp_curve_power));


    return extendedCPCurve;
}

QwtPlotIntervalCurve*
ExtendedCriticalPower::getPlotCurveForExtendedCP_5_3_CP(TestModel model, bool stacked, bool inversed)
{
    const int extendedCurve2_points = 1000;

    QVector<QwtIntervalSample> extended_cp_curve_power(extendedCurve2_points);

    double tmin = 1.0/60;
    double tmax = 600;

    for (int i = 0; i < extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);

        double power_wsecond = model.paa*(1.20-0.20*exp(-1*t))*exp(model.paa_dec*(t));
        double power_wprime = model.ecp * (1-exp(model.tau_del*t)) * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( model.etau/(t) );
        double power_cp = model.ecp * (1-exp(model.tau_del*t)) * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 );

        extended_cp_curve_power[i] = QwtIntervalSample(t, (stacked?(inversed?0:power_wprime + power_wsecond):0), (stacked?(inversed?power_cp : power_wprime + power_wsecond + power_cp):power_cp));
    }

    QwtPlotIntervalCurve *extendedCPCurve = new QwtPlotIntervalCurve("eCP_5_3_CP");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        extendedCPCurve->setRenderHint(QwtPlotItem::RenderAntialiased);

    QPen e2pen(GColor(CHEARTRATE));
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::DashLine);
    extendedCPCurve->setPen(e2pen);

    QColor color1 = GColor(CHEARTRATE);
    color1.setAlpha(64);
    QColor color2 = color1.darker();
    QLinearGradient linearGradient(0, 0, 0, 1.1*model.ecp);
    linearGradient.setColorAt(0.0, color1);
    linearGradient.setColorAt(1.0, color2);
    linearGradient.setSpread(QGradient::PadSpread);
    extendedCPCurve->setBrush(linearGradient);   // fill below the line

    extendedCPCurve->setSamples(new QwtIntervalSeriesData(extended_cp_curve_power));

    return extendedCPCurve;
}



/**************************************************
 * Version 6
 *
 **************************************************/
TestModel
ExtendedCriticalPower::deriveExtendedCP_6_3_Parameters(bool usebest, RideFileCache *bests, RideFile::SeriesType series, double sanI1, double sanI2, double anI1, double anI2, double aeI1, double aeI2, double laeI1, double laeI2)
{
    TestModel model;
    model.version = "eCP v6.3";
    qDebug() << model.version;

    // bounds on anaerobic interval in minutes
    const double t1 = sanI1 / 60.0;
    const double t2 = sanI2 / 60.0;

    // bounds on anaerobic interval in minutes
    const double t3 = anI1 / 60.0;
    const double t4 = anI2 / 60.0;

    // bounds on aerobic interval in minutes
    const double t5 = aeI1 / 60.0;
    const double t6 = aeI2 / 60.0;

    // bounds on long aerobic interval in minutes
    const double t7 = laeI1 / 60.0;
    const double t8 = laeI2 / 60.0;

    // bounds of these time values in the data
    int i1, i2, i3, i4, i5, i6, i7, i8;

    // find the indexes associated with the bounds
    // the first point must be at least the minimum for the anaerobic interval, or quit
    for (i1 = 0; i1 < 60 * t1; i1++)
        if (i1 + 1 >= bests->meanMaxArray(series).size())
            return model;
    // the second point is the maximum point suitable for anaerobicly dominated efforts.
    for (i2 = i1; i2 + 1 <= 60 * t2; i2++)
        if (i2 + 1 >= bests->meanMaxArray(series).size())
            return model;

    // the third point is the beginning of the minimum duration for aerobic efforts
    for (i3 = i2; i3 < 60 * t3; i3++)
        if (i3 + 1 >= bests->meanMaxArray(series).size())
            return model;
    for (i4 = i3; i4 + 1 <= 60 * t4; i4++)
        if (i4 + 1 >= bests->meanMaxArray(series).size())
            break;

    // the fifth point is the beginning of the minimum duration for aerobic efforts
    for (i5 = i4; i5 < 60 * t5; i5++)
        if (i5 + 1 >= bests->meanMaxArray(series).size())
            return model;
    for (i6 = i5; i6 + 1 <= 60 * t6; i6++)
        if (i6 + 1 >= bests->meanMaxArray(series).size())
            break;

    // the first point must be at least the minimum for the anaerobic interval, or quit
    for (i7 = i6; i7 < 60 * t7; i7++)
        if (i7 + 1 >= bests->meanMaxArray(series).size())
            return model;
    // the second point is the maximum point suitable for anaerobicly dominated efforts.
    for (i8 = i7; i8 + 1 <= 60 * t8; i8++)
        if (i8 + 1 >= bests->meanMaxArray(series).size())
            break;

    bool withoutEd = false;

    // initial estimate of tau
    if (model.paa == 0)
        model.paa = 300;

    if (model.etau == 0)
        model.etau = 1;

    if (model.ecp == 0)
        model.ecp = 300;

    if (model.paa_dec == 0)
        model.paa_dec = -2;

    if (model.ecp_del == 0)
        model.ecp_del = -0.6;

    if (model.tau_del == 0)
        model.tau_del = -1.2;

    if (model.ecp_dec == 0)
        model.ecp_dec = -1;

    if (model.ecp_dec_del == 0)
        model.ecp_dec_del = -180;

    // lower bound on tau
    const double paa_min = 100;
    //const double paa_max = 2000;
    const double etau_min = 0.5;
    const double paa_dec_max = -0.25;
    const double paa_dec_min = -5;
    const double ecp_dec_min = -5; // Long

    // convergence delta for tau
    //const double ecp_delta_max = 1e-4;
    const double etau_delta_max = 1e-4;
    const double paa_delta_max = 1e-2;
    const double paa_dec_delta_max = 1e-4;
    const double ecp_del_delta_max = 1e-4;
    const double ecp_dec_delta_max  = 1e-8;

    // previous loop value of tau and t0
    double etau_prev;
    double paa_prev;
    double paa_dec_prev;
    double ecp_del_prev;
    double ecp_dec_prev;

    // maximum number of loops
    const int max_loops = 100;

    // loop to convergence
    int iteration = 0;
    qDebug() << "start iteration";
    do {
        if (iteration ++ > max_loops) {
            qDebug()<<"Maximum number of loops exceeded in ecp2 model";
            break;
        }

        // record the previous version of tau, for convergence
        etau_prev = model.etau;
        paa_prev = model.paa;
        paa_dec_prev = model.paa_dec;
        ecp_del_prev = model.ecp_del;
        ecp_dec_prev = model.ecp_dec;

        if (withoutEd)
            model.ecp_dec = 0;

        // P = paa* (1.10-(1.10-1)*exp(-8*(x/60.0)))*exp(paa_dec*(x/60)) + ecp * (1+ecp_dec*exp(-180/x/60) (1 * (1-exp(ecp_del*x/60)) + (1-exp(tau_del*x/60))^2 * etau/(x/60))
        // bests->meanMaxArray(series)[i] = paa * (1.10-(1.10-1)*exp(-8*(i/60.0))) * exp(paa_dec*(i/60.0))  + ecp * (1+ecp_dec*exp(-180/i/60.0)) * ( 1 *  (1-exp(ecp*i/60.0))  + (1-exp(tau_del*x/60))^2 * etau/(i/60.0));

        // estimate ecp
        int i;

        model.ecp = 0;
        double _avg_ecp = 0.0;
        int count=1;
        for (i = i5; i <= i6; i++) {
            double ecpn = (bests->meanMaxArray(series)[i] - model.paa * (1.10-(1.10-1)*exp(-8*(i/60.0))) * exp(model.paa_dec*(i/60.0))) / (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) / ( 1 * (1-exp(model.ecp_del*i/60.0)) + pow((1-exp(model.tau_del*i/60.0)),2) * model.etau/(i/60.0));
            _avg_ecp = (double)((count-1)*_avg_ecp+ecpn)/count;

            if (model.ecp < ecpn)
                model.ecp = ecpn;

            count++;
        }
        //qDebug() << "estimate ecp" << model.ecp;
        if (!usebest) {
            model.ecp = _avg_ecp;
        }

        // if ecp = 0; no valid data; give up
        if (model.ecp == 0.0)
            return model;

        // estimate etau
        model.etau = etau_min;
        double _avg_etau = 0.0;
        count=1;
        for (i = i3; i <= i4; i++) {
            double etaun = ((bests->meanMaxArray(series)[i] - model.paa * (1.10-(1.10-1)*exp(-8*(i/60.0))) * exp(model.paa_dec*(i/60.0))) / model. ecp / (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) - 1 * (1-exp(model.ecp_del*i/60.0))) * (i/60.0) / pow((1-exp(model.tau_del*i/60.0)),2);
            _avg_etau = (double)((count-1)*_avg_etau+etaun)/count;

            if (model.etau < etaun)
                model.etau = etaun;
            count++;
        }
        //qDebug() << "estimate etau" << model.etau;
        if (!usebest) {
            model.etau = _avg_etau;
        }



        model.paa_dec = paa_dec_min;
        double _avg_paa_dec = 0.0;
        count=1;
        for (i = i1; i <= i2; i++) {
            double paa_decn = log((bests->meanMaxArray(series)[i] - model.ecp * (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) * ( 1 * (1-exp(model.ecp_del*i/60.0)) + pow((1-exp(model.tau_del*i/60.0)),2) * model.etau/(i/60.0)) ) / model.paa / (1.10-(1.10-1)*exp(-8*(i/60.0))) ) / (i/60.0);
            _avg_paa_dec = (double)((count-1)*_avg_paa_dec+paa_decn)/count;

            if (model.paa_dec < paa_decn && paa_decn < paa_dec_max) {
                model.paa_dec = paa_decn;
            }
            count++;
        }
        //qDebug() << "estimate paa_dec" << model.paa_dec;
        if (!usebest) {
            model.paa_dec = _avg_paa_dec;
        }

        model.paa = paa_min;
        double _avg_paa = 0.0;
        count=1;
        for (i = 2; i <= 8; i++) {
            double paan = (bests->meanMaxArray(series)[i] - model.ecp * (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) * ( 1 * (1-exp(model.ecp_del*i/60.0)) + pow((1-exp(model.tau_del*i/60.0)),2) * model.etau/(i/60.0))) / exp(model.paa_dec*(i/60.0)) / (1.10-(1.10-1)*exp(-8*(i/60.0)));
            _avg_paa = (double)((count-1)*_avg_paa+paan)/count;

            //qDebug() << "   estimate paan" << paan;
            if (model.paa < paan)
                model.paa = paan;
            count++;
        }
        //qDebug() << "estimate paa" << model.paa;
        if (!usebest  || _avg_paa<0.95*model.paa) {
            model.paa = _avg_paa;
        }

        if (!withoutEd) {
            model.ecp_dec = ecp_dec_min;
            double _avg_ecp_dec = 0.0;
            count=1;
            for (i = i7; i <= i8; i=i+120) {
                double ecp_decn = ((bests->meanMaxArray(series)[i] - model.paa * (1.10-(1.10-1)*exp(-8*(i/60.0))) * exp(model.paa_dec*(i/60.0))) / model.ecp / ( 1 * (1-exp(model.ecp_del*i/60.0)) + pow((1-exp(model.tau_del*i/60.0)),2) * model.etau/(i/60.0)) -1 ) / exp(model.ecp_dec_del/(i / 60.0));
                _avg_ecp_dec = (double)((count-1)*_avg_ecp_dec+ecp_decn)/count;

                if (ecp_decn > 0)
                    ecp_decn = 0;

                if (model.ecp_dec < ecp_decn)
                    model.ecp_dec = ecp_decn;
                count++;
            }
            //qDebug() << "estimate ecp_dec" << model.ecp_dec;
            if (!usebest) {
                model.ecp_dec = _avg_ecp_dec;
            }

        }
    } while ((fabs(model.etau - etau_prev) > etau_delta_max) ||
             (fabs(model.paa - paa_prev) > paa_delta_max)  ||
             (fabs(model.paa_dec - paa_dec_prev) > paa_dec_delta_max)  ||
             (fabs(model.ecp_del - ecp_del_prev) > ecp_del_delta_max)  ||
             (fabs(model.ecp_dec - ecp_dec_prev) > ecp_dec_delta_max)
            );

    qDebug() << iteration << "iterations";
    verifyMin(model.paa, paa_min, model.paa_dec, paa_dec_min, model.ecp_dec, ecp_dec_min, model.etau, etau_min);

    model.pMax = model.paa*(1.10-(1.10-1)*exp(-8*(1/60.0)))*exp(model.paa_dec*(1/60.0)) + model.ecp * (1+model.ecp_dec*exp(model.ecp_dec_del/(1/60.0))) * ( 1 * (1-exp(model.ecp_del*(1/60.0))) + pow((1-exp(model.tau_del*(1/60.0))),2) * model.etau/(1/60.0));
    model.mmp60 = model.paa*(1.10-(1.10-1)*exp(-8*60.0))*exp(model.paa_dec*(60.0)) + model.ecp * (1+model.ecp_dec*exp(model.ecp_dec_del/60.0)) * ( 1 * (1-exp(model.ecp_del*60.0)) + pow((1-exp(model.tau_del*(60.0))),2) * model.etau/(60.0));

    qDebug() <<"-> eCP(6.3) " << "paa" << model.paa  << "ecp" << model.ecp << "etau" << model.etau << "paa_dec" << model.paa_dec << "ecp_del" << model.ecp_del << "ecp_dec" << model.ecp_dec << "ecp_dec_del" << model.ecp_dec_del;
    qDebug() <<"-> eCP(6.3) " << "pmax" << model.pMax << "mmp60" << model.mmp60;

    return model;
}

QwtPlotCurve*
ExtendedCriticalPower::getPlotCurveForExtendedCP_6_3(TestModel model)
{
    const int extendedCurve2_points = 1000;

    QVector<double> extended_cp_curve2_power(extendedCurve2_points);
    QVector<double> extended_cp_curve2_time(extendedCurve2_points);
    double tmin = 1.0/60;
    double tmax = 600;

    for (int i = 0; i < extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);
        extended_cp_curve2_time[i] = t;
        extended_cp_curve2_power[i] = model.paa*(1.10-(1.10-1)*exp(-8*t)) * exp(model.paa_dec*(t)) + model.ecp * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 * (1-exp(model.ecp_del*t)) + pow( (1-exp(model.tau_del*t)),2) * model.etau/(t));
    }

    QwtPlotCurve *extendedCPCurve2 = new QwtPlotCurve("eCP_6_3");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        extendedCPCurve2->setRenderHint(QwtPlotItem::RenderAntialiased);
    QPen e2pen(Qt::blue);
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::DashLine);
    extendedCPCurve2->setPen(e2pen);
    extendedCPCurve2->setSamples(extended_cp_curve2_time.data(), extended_cp_curve2_power.data(), extendedCurve2_points);

    return extendedCPCurve2;
}

QwtPlotIntervalCurve*
ExtendedCriticalPower::getPlotCurveForExtendedCP_6_3_WSecond(TestModel model, bool)
{
    const int extendedCurve2_points = 1000;

    QVector<QwtIntervalSample> extended_cp_curve(extendedCurve2_points);
    double tmin = 1.0/60;
    double tmax = 600;

    for (int i = 0; i < extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);

        double power_wsecond = model.paa*(1.10-(1.10-1)*exp(-8*t))*exp(model.paa_dec*(t));
        extended_cp_curve[i] = QwtIntervalSample(t, 0, power_wsecond);
    }

    QwtPlotIntervalCurve *extendedCPCurve2 = new QwtPlotIntervalCurve("eCP_6_3_WSecond");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        extendedCPCurve2->setRenderHint(QwtPlotItem::RenderAntialiased);

    QPen e2pen(GColor(CCADENCE));
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::DashLine);
    extendedCPCurve2->setPen(e2pen);

    QColor color1 = GColor(CCADENCE);
    color1.setAlpha(64);
    QColor color2 = color1.darker();
    QLinearGradient linearGradient(0, 0, 0, 100);
    linearGradient.setColorAt(0.0, color1);
    linearGradient.setColorAt(1.0, color2);
    linearGradient.setSpread(QGradient::PadSpread);
    extendedCPCurve2->setBrush(linearGradient);   // fill below the line

    extendedCPCurve2->setSamples(new QwtIntervalSeriesData(extended_cp_curve));

    return extendedCPCurve2;
}

QwtPlotIntervalCurve*
ExtendedCriticalPower::getPlotCurveForExtendedCP_6_3_WPrime(TestModel model, bool stacked)
{
    const int extendedCurve2_points = 1000;

    QVector<QwtIntervalSample> extended_cp_curve(extendedCurve2_points);

    double tmin = 1.0/60;
    double tmax = 600;

    for (int i = 0; i < extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);

        double power_wsecond = model.paa*(1.10-(1.10-1)*exp(-8*t))*exp(model.paa_dec*(t));
        double power_wprime = model.ecp * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( pow((1-exp(model.tau_del*t)),2) * model.etau/(t) );
        extended_cp_curve[i] = QwtIntervalSample(t, (stacked?power_wsecond:0), (stacked?power_wsecond:0) + power_wprime);
    }

    QwtPlotIntervalCurve *extendedCPCurve2 = new QwtPlotIntervalCurve("eCP_6_3_WPrime");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        extendedCPCurve2->setRenderHint(QwtPlotItem::RenderAntialiased);

    QPen e2pen(GColor(CPOWER));
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::DashLine);
    extendedCPCurve2->setPen(e2pen);

    QColor color1 = GColor(CPOWER);
    color1.setAlpha(64);
    QColor color2 = color1.darker();
    QLinearGradient linearGradient(0, 0, 0, model.ecp);
    linearGradient.setColorAt(0.0, color1);
    linearGradient.setColorAt(1.0, color2);
    linearGradient.setSpread(QGradient::PadSpread);
    extendedCPCurve2->setBrush(linearGradient);   // fill below the line

    extendedCPCurve2->setSamples(new QwtIntervalSeriesData(extended_cp_curve));

    return extendedCPCurve2;
}

QwtPlotIntervalCurve*
ExtendedCriticalPower::getPlotCurveForExtendedCP_6_3_CP(TestModel model, bool stacked)
{
    const int extendedCurve2_points = 1000;

    QVector<QwtIntervalSample> extended_cp_curve(extendedCurve2_points);

    double tmin = 1.0/60;
    double tmax = 600;

    for (int i = 0; i < extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);

        double power_second = model.paa*(1.10-(1.10-1)*exp(-8*t))*exp(model.paa_dec*(t));
        double power_wprime = model.ecp * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( pow((1-exp(model.tau_del*t)),2) * model.etau/(t) );
        double power_wcp =  model.ecp * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 * (1-exp(model.ecp_del*t)) );
        extended_cp_curve[i] = QwtIntervalSample(t, (stacked?power_second+power_wprime:0), (stacked?power_second + power_wprime:0) + power_wcp);
    }

    QwtPlotIntervalCurve *extendedCPCurve2 = new QwtPlotIntervalCurve("eCP_6_3_CP");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        extendedCPCurve2->setRenderHint(QwtPlotItem::RenderAntialiased);

    QPen e2pen(GColor(CHEARTRATE));
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::DashLine);
    extendedCPCurve2->setPen(e2pen);

    QColor color1 = GColor(CHEARTRATE);
    color1.setAlpha(64);
    QColor color2 = color1.darker();
    QLinearGradient linearGradient(0, 0, 0, 1.1*model.ecp);
    linearGradient.setColorAt(0.0, color1);
    linearGradient.setColorAt(1.0, color2);
    linearGradient.setSpread(QGradient::PadSpread);
    extendedCPCurve2->setBrush(linearGradient);   // fill below the line

    extendedCPCurve2->setSamples(new QwtIntervalSeriesData(extended_cp_curve));

    return extendedCPCurve2;
}

/***************************************************************
  Dan's version of 2 component veloclinic model

  P = P1 ( τ1 / t ) ( 1 - exp[-t / τ1] ) + P2 / ( 1 + t / α2 τ2 )α2

 ***************************************************************/
/*QVector<double> &
ExtendedCriticalPower::decimateData(QVector<double> data)
{
   QVector<double> result;

   for (int i=0;i<data.length();i++) {
       if (data.at(i))
   }
}*/

TestModel
ExtendedCriticalPower::deriveDanVeloclinicCP_Parameters(bool usebest, RideFileCache *bests, RideFile::SeriesType series, double sanI1, double sanI2, double anI1, double anI2, double aeI1, double aeI2, double laeI1, double laeI2)
{
    TestModel model;
    model.version = "2 components (Dan v1)";

    // bounds on anaerobic interval in minutes
    const double t1 = sanI1;
    const double t2 = sanI2;

    // bounds on anaerobic interval in minutes
    const double t3 = anI1;
    const double t4 = anI2;

    // bounds on aerobic interval in minutes
    const double t5 = aeI1;
    const double t6 = aeI2;

    // bounds on long aerobic interval in minutes
    const double t7 = laeI1;
    const double t8 = laeI2;

    // bounds of these time values in the data
    int i1, i2, i3, i4, i5, i6, i7, i8;

    // find the indexes associated with the bounds
    // the first point must be at least the minimum for the anaerobic interval, or quit
    for (i1 = 0; i1 < 60 * t1; i1++)
        if (i1 + 1 >= bests->meanMaxArray(series).size())
            return model;
    // the second point is the maximum point suitable for anaerobicly dominated efforts.
    for (i2 = i1; i2 + 1 <= 60 * t2; i2++)
        if (i2 + 1 >= bests->meanMaxArray(series).size())
            return model;

    // the third point is the beginning of the minimum duration for aerobic efforts
    for (i3 = i2; i3 < 60 * t3; i3++)
        if (i3 + 1 >= bests->meanMaxArray(series).size())
            return model;
    for (i4 = i3; i4 + 1 <= 60 * t4; i4++)
        if (i4 + 1 >= bests->meanMaxArray(series).size())
            break;

    // the fifth point is the beginning of the minimum duration for aerobic efforts
    for (i5 = i4; i5 < 60 * t5; i5++)
        if (i5 + 1 >= bests->meanMaxArray(series).size())
            return model;
    for (i6 = i5; i6 + 1 <= 60 * t6; i6++)
        if (i6 + 1 >= bests->meanMaxArray(series).size())
            break;

    // the first point must be at least the minimum for the anaerobic interval, or quit
    for (i7 = i6; i7 < 60 * t7; i7++)
        if (i7 + 1 >= bests->meanMaxArray(series).size())
            return model;
    // the second point is the maximum point suitable for anaerobicly dominated efforts.
    for (i8 = i7; i8 + 1 <= 60 * t8; i8++)
        if (i8 + 1 >= bests->meanMaxArray(series).size())
            break;

    bool withoutEd = false;

    // initial estimate of tau
    if (model.paa == 0)
        model.paa = 300;

    if (model.etau == 0)
        model.etau = 1;

    if (model.ecp == 0)
        model.ecp = 300;

    if (model.paa_dec == 0)
        model.paa_dec = -2;

    if (model.ecp_del == 0)
        model.ecp_del = -0.6;

    if (model.tau_del == 0)
        model.tau_del = -1.2;

    if (model.ecp_dec == 0)
        model.ecp_dec = -1;

    if (model.ecp_dec_del == 0)
        model.ecp_dec_del = -180;

    // lower bound on tau
    const double paa_min = 100;
    //const double paa_max = 2000;
    const double etau_min = 0.5;
    const double paa_dec_max = -0.25;
    const double paa_dec_min = -5;
    const double ecp_dec_min = -5; // Long

    // convergence delta for tau
    //const double ecp_delta_max = 1e-4;
    const double etau_delta_max = 1e-4;
    const double paa_delta_max = 1e-2;
    const double paa_dec_delta_max = 1e-4;
    const double ecp_del_delta_max = 1e-4;
    const double ecp_dec_delta_max  = 1e-8;

    // previous loop value of tau and t0
    double etau_prev;
    double paa_prev;
    double paa_dec_prev;
    double ecp_del_prev;
    double ecp_dec_prev;

    // maximum number of loops
    const int max_loops = 100;

    // loop to convergence
    int iteration = 0;
    do {
        if (iteration ++ > max_loops) {
            qDebug()<<"Maximum number of loops exceeded in ecp2 model";
            break;
        }

        // record the previous version of tau, for convergence
        etau_prev = model.etau;
        paa_prev = model.paa;
        paa_dec_prev = model.paa_dec;
        ecp_del_prev = model.ecp_del;
        ecp_dec_prev = model.ecp_dec;

        if (withoutEd)
            model.ecp_dec = 0;

        // P = P1 ( τ1 / t ) ( 1 - exp[-t / τ1] ) + P2 / ( 1 + t / α2 τ2 )α2
        // P = P1 ( τ1 / t ) ( 1 - exp[-t / τ1] ) + P2 / ( 1 + t / α2 1200 )α2
        // bests->meanMaxArray(series)[i] = p1 * (t1 / (i/60.0)) * ( 1 - exp(-(i/60.0) / t1) ) + p2 / ( 1 + (i/60.0) / (a2 * t2))^a2;

        // estimate ecp
        int i;

        model.ecp = 0;
        double _avg_ecp = 0.0;
        int count=1;
        for (i = i5; i <= i6; i++) {
            double ecpn = (bests->meanMaxArray(series)[i] - model.paa * (1.10-(1.10-1)*exp(-8*(i/60.0))) * exp(model.paa_dec*(i/60.0))) / (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) / ( 1 * (1-exp(model.ecp_del*i/60.0)) + pow((1-exp(model.tau_del*i/60.0)),2) * model.etau/(i/60.0));
            _avg_ecp = (double)((count-1)*_avg_ecp+ecpn)/count;

            if (model.ecp < ecpn)
                model.ecp = ecpn;

            count++;
        }
        //qDebug() << "estimate ecp" << model.ecp;
        if (!usebest) {
            model.ecp = _avg_ecp;
        }

        // if ecp = 0; no valid data; give up
        if (model.ecp == 0.0)
            return model;

        // estimate etau
        model.etau = etau_min;
        double _avg_etau = 0.0;
        count=1;
        for (i = i3; i <= i4; i++) {
            double etaun = ((bests->meanMaxArray(series)[i] - model.paa * (1.10-(1.10-1)*exp(-8*(i/60.0))) * exp(model.paa_dec*(i/60.0))) / model. ecp / (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) - 1 * (1-exp(model.ecp_del*i/60.0))) * (i/60.0) / pow((1-exp(model.tau_del*i/60.0)),2);
            _avg_etau = (double)((count-1)*_avg_etau+etaun)/count;

            if (model.etau < etaun)
                model.etau = etaun;
            count++;
        }
        //qDebug() << "estimate etau" << model.etau;
        if (!usebest) {
            model.etau = _avg_etau;
        }



        model.paa_dec = paa_dec_min;
        double _avg_paa_dec = 0.0;
        count=1;
        for (i = i1; i <= i2; i++) {
            double paa_decn = log((bests->meanMaxArray(series)[i] - model.ecp * (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) * ( 1 * (1-exp(model.ecp_del*i/60.0)) + pow((1-exp(model.tau_del*i/60.0)),2) * model.etau/(i/60.0)) ) / model.paa / (1.10-(1.10-1)*exp(-8*(i/60.0))) ) / (i/60.0);
            _avg_paa_dec = (double)((count-1)*_avg_paa_dec+paa_decn)/count;

            if (model.paa_dec < paa_decn && paa_decn < paa_dec_max) {
                model.paa_dec = paa_decn;
            }
            count++;
        }
        //qDebug() << "estimate paa_dec" << model.paa_dec;
        if (!usebest) {
            model.paa_dec = _avg_paa_dec;
        }

        model.paa = paa_min;
        double _avg_paa = 0.0;
        count=1;
        for (i = 2; i <= 8; i++) {
            double paan = (bests->meanMaxArray(series)[i] - model.ecp * (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) * ( 1 * (1-exp(model.ecp_del*i/60.0)) + pow((1-exp(model.tau_del*i/60.0)),2) * model.etau/(i/60.0))) / exp(model.paa_dec*(i/60.0)) / (1.10-(1.10-1)*exp(-8*(i/60.0)));
            _avg_paa = (double)((count-1)*_avg_paa+paan)/count;

            //qDebug() << "   estimate paan" << paan;
            if (model.paa < paan)
                model.paa = paan;
            count++;
        }
        //qDebug() << "estimate paa" << model.paa;
        if (!usebest  || _avg_paa<0.95*model.paa) {
            model.paa = _avg_paa;
        }

        if (!withoutEd) {
            model.ecp_dec = ecp_dec_min;
            double _avg_ecp_dec = 0.0;
            count=1;
            for (i = i7; i <= i8; i=i+120) {
                double ecp_decn = ((bests->meanMaxArray(series)[i] - model.paa * (1.10-(1.10-1)*exp(-8*(i/60.0))) * exp(model.paa_dec*(i/60.0))) / model.ecp / ( 1 * (1-exp(model.ecp_del*i/60.0)) + pow((1-exp(model.tau_del*i/60.0)),2) * model.etau/(i/60.0)) -1 ) / exp(model.ecp_dec_del/(i / 60.0));
                _avg_ecp_dec = (double)((count-1)*_avg_ecp_dec+ecp_decn)/count;

                if (ecp_decn > 0)
                    ecp_decn = 0;

                if (model.ecp_dec < ecp_decn)
                    model.ecp_dec = ecp_decn;
                count++;
            }
            //qDebug() << "estimate ecp_dec" << model.ecp_dec;
            if (!usebest) {
                model.ecp_dec = _avg_ecp_dec;
            }

        }
    } while ((fabs(model.etau - etau_prev) > etau_delta_max) ||
             (fabs(model.paa - paa_prev) > paa_delta_max)  ||
             (fabs(model.paa_dec - paa_dec_prev) > paa_dec_delta_max)  ||
             (fabs(model.ecp_del - ecp_del_prev) > ecp_del_delta_max)  ||
             (fabs(model.ecp_dec - ecp_dec_prev) > ecp_dec_delta_max)
            );

    //qDebug() << iteration << "iterations";

    model.pMax = model.paa*(1.10-(1.10-1)*exp(-8*(1/60.0)))*exp(model.paa_dec*(1/60.0)) + model.ecp * (1+model.ecp_dec*exp(model.ecp_dec_del/(1/60.0))) * ( 1 * (1-exp(model.ecp_del*(1/60.0))) + pow((1-exp(model.tau_del*(1/60.0))),2) * model.etau/(1/60.0));
    model.mmp60 = model.paa*(1.10-(1.10-1)*exp(-8*60.0))*exp(model.paa_dec*(60.0)) + model.ecp * (1+model.ecp_dec*exp(model.ecp_dec_del/60.0)) * ( 1 * (1-exp(model.ecp_del*60.0)) + pow((1-exp(model.tau_del*(60.0))),2) * model.etau/(60.0));

    qDebug() <<"eCP(DanVeloclinic) " << "paa" << model.paa  << "ecp" << model.ecp << "etau" << model.etau << "paa_dec" << model.paa_dec << "ecp_del" << model.ecp_del << "ecp_dec" << model.ecp_dec << "ecp_dec_del" << model.ecp_dec_del;
    qDebug() <<"eCP(DanVeloclinic) " << "pmax" << model.pMax << "mmp60" << model.mmp60;

    return model;
}

TestModel
ExtendedCriticalPower::deriveExtendedCP_7_3_Parameters(bool usebest, RideFileCache *bests, RideFile::SeriesType series, double sanI1, double sanI2, double anI1, double anI2, double aeI1, double aeI2, double laeI1, double laeI2)
{
    TestModel model;
    model.version = "eCP v7.3";
    qDebug() << model.version;

    // bounds on anaerobic interval in minutes
    const double t1 = sanI1 / 60.0;
    const double t2 = sanI2 / 60.0;

    // bounds on anaerobic interval in minutes
    const double t3 = anI1 / 60.0;
    const double t4 = anI2 / 60.0;

    // bounds on aerobic interval in minutes
    const double t5 = aeI1 / 60.0;
    const double t6 = aeI2 / 60.0;

    // bounds on long aerobic interval in minutes
    const double t7 = laeI1 / 60.0;
    const double t8 = laeI2 / 60.0;

    // bounds of these time values in the data
    int i1, i2, i3, i4, i5, i6, i7, i8;

    // find the indexes associated with the bounds
    // the first point must be at least the minimum for the anaerobic interval, or quit
    for (i1 = 0; i1 < 60 * t1; i1++)
        if (i1 + 1 >= bests->meanMaxArray(series).size())
            return model;
    // the second point is the maximum point suitable for anaerobicly dominated efforts.
    for (i2 = i1; i2 + 1 <= 60 * t2; i2++)
        if (i2 + 1 >= bests->meanMaxArray(series).size())
            return model;

    // the third point is the beginning of the minimum duration for aerobic efforts
    for (i3 = i2; i3 < 60 * t3; i3++)
        if (i3 + 1 >= bests->meanMaxArray(series).size())
            return model;
    for (i4 = i3; i4 + 1 <= 60 * t4; i4++)
        if (i4 + 1 >= bests->meanMaxArray(series).size())
            break;

    // the fifth point is the beginning of the minimum duration for aerobic efforts
    for (i5 = i4; i5 < 60 * t5; i5++)
        if (i5 + 1 >= bests->meanMaxArray(series).size())
            return model;
    for (i6 = i5; i6 + 1 <= 60 * t6; i6++)
        if (i6 + 1 >= bests->meanMaxArray(series).size())
            break;

    // the first point must be at least the minimum for the anaerobic interval, or quit
    for (i7 = i6; i7 < 60 * t7; i7++)
        if (i7 + 1 >= bests->meanMaxArray(series).size())
            return model;
    // the second point is the maximum point suitable for anaerobicly dominated efforts.
    for (i8 = i7; i8 + 1 <= 60 * t8; i8++)
        if (i8 + 1 >= bests->meanMaxArray(series).size())
            break;

    bool withoutEd = false;

    // initial estimate of tau
    if (model.paa == 0)
        model.paa = 300;

    if (model.etau == 0)
        model.etau = 1;

    if (model.ecp == 0)
        model.ecp = 300;

    if (model.paa_dec == 0)
        model.paa_dec = -2;

    if (model.ecp_del == 0)
        model.ecp_del = -0.9;

    if (model.tau_del == 0)
        model.tau_del = -4.8; // -4.8

    if (model.ecp_dec == 0)
        model.ecp_dec = -1;

    if (model.ecp_dec_del == 0)
        model.ecp_dec_del = -180;

    model.paa_pow = 1.05; //1.05

    // lower bound on tau
    const double paa_min = 100;
    //const double paa_max = 2000;
    const double etau_min = 0.5;
    const double paa_dec_max = -0.25;
    const double paa_dec_min = -3;
    const double ecp_dec_min = -5; // Long



    // convergence delta for tau
    //const double ecp_delta_max = 1e-4;
    const double etau_delta_max = 1e-4;
    const double paa_delta_max = 1e-2;
    const double paa_dec_delta_max = 1e-4;
    const double ecp_del_delta_max = 1e-4;
    const double ecp_dec_delta_max  = 1e-8;

    // previous loop value of tau and t0
    double etau_prev;
    double paa_prev;
    double paa_dec_prev;
    double ecp_del_prev;
    double ecp_dec_prev;

    // maximum number of loops
    const int max_loops = 100;

    // loop to convergence
    int iteration = 0;

    qDebug() << "start iteration";
    do {
        if (iteration ++ > max_loops) {
            qDebug()<<"Maximum number of loops exceeded in ecp2 model";
            break;
        }

        // record the previous version of tau, for convergence
        etau_prev = model.etau;
        paa_prev = model.paa;
        paa_dec_prev = model.paa_dec;
        ecp_del_prev = model.ecp_del;
        ecp_dec_prev = model.ecp_dec;

        if (withoutEd)
            model.ecp_dec = 0;

        // P = paa* exp(paa_dec*(x/60)^z) + ecp * (1-exp(tau_del*x/60)) * (1-exp(ecp_del*x/60)) * (1+ecp_dec*exp(-180/x/60) (1 + etau/(x/60))
        // bests->meanMaxArray(series)[i] = paa*exp(paa_dec*(i/60.0)^z)  + ecp * (1-exp(tau_del*x/60)) * (1-exp(ecp*i/60.0)) * (1+2d*exp(180/i/60.0)) * ( 1 + etau/(i/60.0));

        // estimate ecp
        int i;

        model.ecp = 0;
        double _avg_ecp = 0.0;
        int count=1;
        for (i = i5; i <= i6; i++) {
            double ecpn = (bests->meanMaxArray(series)[i] - model.paa * exp(model.paa_dec*pow(i/60.0, model.paa_pow))) / (1-exp(model.tau_del*i/60.0)) / (1-exp(model.ecp_del*i/60.0)) / (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) / ( 1 + model.etau/(i/60.0));
            _avg_ecp = (double)((count-1)*_avg_ecp+ecpn)/count;

            if (model.ecp < ecpn)
                model.ecp = ecpn;

            count++;
        }
        //qDebug() << "estimate ecp" << model.ecp;
        if (!usebest) {
            model.ecp = _avg_ecp;
        }

        // if ecp = 0; no valid data; give up
        if (model.ecp == 0.0)
            return model;

        // estimate etau
        model.etau = etau_min;
        double _avg_etau = 0.0;
        count=1;
        for (i = i3; i <= i4; i++) {
            double etaun = ((bests->meanMaxArray(series)[i] - model.paa * exp(model.paa_dec*pow(i/60.0, model.paa_pow))) /model. ecp / (1-exp(model.tau_del*i/60.0)) / (1-exp(model.ecp_del*i/60.0)) / (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) - 1) * (i/60.0);
            _avg_etau = (double)((count-1)*_avg_etau+etaun)/count;

            if (model.etau < etaun)
                model.etau = etaun;
            count++;
        }
        //qDebug() << "estimate etau" << model.etau;
        if (!usebest) {
            model.etau = _avg_etau;
        }



        model.paa_dec = paa_dec_min;
        double _avg_paa_dec = 0.0;
        count=1;
        for (i = i1; i <= i2; i++) {
            double paa_decn = log((bests->meanMaxArray(series)[i] - model.ecp * (1-exp(model.tau_del*i/60.0)) * (1-exp(model.ecp_del*i/60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) * ( 1 + model.etau/(i/60.0)) ) / model.paa ) / pow(i/60.0, model.paa_pow);
            _avg_paa_dec = (double)((count-1)*_avg_paa_dec+paa_decn)/count;

            if (model.paa_dec < paa_decn && paa_decn < paa_dec_max) {
                model.paa_dec = paa_decn;
            }
            count++;
        }
        //qDebug() << "estimate paa_dec" << model.paa_dec;
        if (!usebest) {
            model.paa_dec = _avg_paa_dec;
        }

        model.paa = paa_min;
        double _avg_paa = 0.0;
        count=1;
        for (i = 2; i <= 8; i++) {
            double paan = (bests->meanMaxArray(series)[i] - model.ecp * (1-exp(model.tau_del*i/60.0)) * (1-exp(model.ecp_del*i/60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/(i/60.0))) * ( 1 + model.etau/(i/60.0))) / exp(model.paa_dec*pow(i/60.0, model.paa_pow));
            _avg_paa = (double)((count-1)*_avg_paa+paan)/count;

            //qDebug() << "   estimate paan" << paan;
            if (model.paa < paan)
                model.paa = paan;
            count++;
        }
        //qDebug() << "estimate paa" << model.paa;
        if (!usebest  || _avg_paa<0.95*model.paa) {
            model.paa = _avg_paa;
        }

        if (!withoutEd) {
            model.ecp_dec = ecp_dec_min;
            double _avg_ecp_dec = 0.0;
            count=1;
            for (i = i7; i <= i8; i=i+120) {
                double ecp_decn = ((bests->meanMaxArray(series)[i] - model.paa * exp(model.paa_dec*pow(i/60.0, model.paa_pow))) / model.ecp / (1-exp(model.tau_del*i/60.0)) / (1-exp(model.ecp_del*i/60.0)) / ( 1 + model.etau/(i/60.0)) -1 ) / exp(model.ecp_dec_del/(i / 60.0));
                _avg_ecp_dec = (double)((count-1)*_avg_ecp_dec+ecp_decn)/count;

                if (ecp_decn > 0)
                    ecp_decn = 0;

                if (model.ecp_dec < ecp_decn)
                    model.ecp_dec = ecp_decn;
                count++;
            }
            //qDebug() << "estimate ecp_dec" << model.ecp_dec;
            if (!usebest) {
                model.ecp_dec = _avg_ecp_dec;
            }

        }
    } while ((fabs(model.etau - etau_prev) > etau_delta_max) ||
             (fabs(model.paa - paa_prev) > paa_delta_max)  ||
             (fabs(model.paa_dec - paa_dec_prev) > paa_dec_delta_max)  ||
             (fabs(model.ecp_del - ecp_del_prev) > ecp_del_delta_max)  ||
             (fabs(model.ecp_dec - ecp_dec_prev) > ecp_dec_delta_max)
            );

    qDebug() << iteration << "iterations";

    verifyMin(model.paa, paa_min, model.paa_dec, paa_dec_min, model.ecp_dec, ecp_dec_min, model.etau, etau_min);

    model.pMax = model.paa*exp(model.paa_dec*pow(1/60.0, model.paa_pow)) + model.ecp * (1-exp(model.tau_del*(1/60.0))) * (1-exp(model.ecp_del*(1/60.0))) * (1+model.ecp_dec*exp(model.ecp_dec_del/(1/60.0))) * ( 1 + model.etau/(1/60.0));
    model.mmp60 = model.paa*exp(model.paa_dec*pow(60.0, model.paa_pow)) + model.ecp * (1-exp(model.tau_del*(60.0))) * (1-exp(model.ecp_del*60.0)) * (1+model.ecp_dec*exp(model.ecp_dec_del/60.0)) * ( 1 + model.etau/(60.0));

    qDebug() <<"-> eCP(7.3) " << "paa" << model.paa  << "ecp" << model.ecp << "etau" << model.etau << "paa_dec" << model.paa_dec << "ecp_del" << model.ecp_del << "ecp_dec" << model.ecp_dec << "ecp_dec_del" << model.ecp_dec_del;
    qDebug() <<"-> eCP(7.3) " << "pmax" << model.pMax << "mmp60" << model.mmp60;

    return model;
}

QwtPlotCurve*
ExtendedCriticalPower::getPlotCurveForExtendedCP_7_3(TestModel model)
{
    const int extendedCurve2_points = 1000;

    QVector<double> extended_cp_curve2_power(extendedCurve2_points);
    QVector<double> extended_cp_curve2_time(extendedCurve2_points);
    double tmin = 1.0/60;
    double tmax = 600;

    for (int i = 0; i < extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);
        extended_cp_curve2_time[i] = t;
        extended_cp_curve2_power[i] = model.paa*exp(model.paa_dec*(pow(t, model.paa_pow))) + model.ecp * (1-exp(model.tau_del*t)) * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 + model.etau/(t));
    }

    QwtPlotCurve *extendedCPCurve2 = new QwtPlotCurve("eCP_7_3");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        extendedCPCurve2->setRenderHint(QwtPlotItem::RenderAntialiased);
    QPen e2pen(Qt::yellow); // Qt::cyan GColor(CCP)
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::DashLine);
    extendedCPCurve2->setPen(e2pen);
    extendedCPCurve2->setSamples(extended_cp_curve2_time.data(), extended_cp_curve2_power.data(), extendedCurve2_points);

    return extendedCPCurve2;
}

QwtPlotCurve*
ExtendedCriticalPower::getPlotCurveForExtendedCP_7_3_balance(TestModel model, int depletion)
{
    const int extendedCurve2_points = 1000;

    QVector<double> extended_cp_curve2_power(extendedCurve2_points);
    QVector<double> extended_cp_curve2_time(extendedCurve2_points);
    double tmin = 1.0/60;
    double tmax = 600;

    for (int i = 0; i < extendedCurve2_points; i ++) {
        double x = (double) i / (extendedCurve2_points - 1);
        double t = pow(tmax, x) * pow(tmin, 1-x);
        extended_cp_curve2_time[i] = t;
        double d = ((model.paa-1.3*model.ecp) * depletion/100.0 + 1.3*model.ecp) / model.paa;
        qDebug() << depletion << d << d * model.paa << model.ecp;
        // (100.0-depletion)/100.0*(model.tau_del/model.paa_dec)
        double value = d*model.paa*exp(model.paa_dec*(pow(t, model.paa_pow))) + model.ecp * (1-exp(model.tau_del*t)) * (1-exp(model.ecp_del*t)) * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) * ( 1 + (depletion/100.0)*model.etau/(t));

        extended_cp_curve2_power[i] = value;
        //extended_cp_curve2_power[i] = (value<model.ecp * (1+model.ecp_dec*exp(model.ecp_dec_del/t)) ? model.ecp * (1+model.ecp_dec*exp(model.ecp_dec_del/t)): value);
    }

    QwtPlotCurve *extendedCPCurve2 = new QwtPlotCurve(QString("eCP_7_3 (%1%)").arg(depletion));
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        extendedCPCurve2->setRenderHint(QwtPlotItem::RenderAntialiased);
    QPen e2pen(Qt::yellow); // Qt::cyan GColor(CCP)
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::DashDotDotLine);
    extendedCPCurve2->setPen(e2pen);
    extendedCPCurve2->setSamples(extended_cp_curve2_time.data(), extended_cp_curve2_power.data(), extendedCurve2_points);

    return extendedCPCurve2;
}


QwtPlotCurve*
ExtendedCriticalPower::getPlotCurveFor10secRollingAverage(RideFileCache *bests, RideFile::SeriesType series) {
    int count = bests->meanMaxArray(series).count();
    if (count == 0)
        return NULL;

    QVector<double> smooth;
    QVector<double> smoothed;
    QVector<double> time;

    double total = 0.0;

    int l = 6;
    int d = 3;
    for (int i = 1; i < 1 + d; i++) {
        double value = bests->meanMaxArray(series)[i];

        smooth.append(value);
        total += value;
    }

    for (int i=1; i < count - d; i++) {
        double newvalue = bests->meanMaxArray(series)[i+d];

        smooth.append(newvalue);
        total += newvalue;

        if (smooth.count() > l) {
            total -= smooth.at(0);
            smooth.remove(0);
        }

        smoothed.append(total/smooth.count());
        time.append(i/60.0f);

    }


    QwtPlotCurve *curve = new QwtPlotCurve("rolling");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        curve->setRenderHint(QwtPlotItem::RenderAntialiased);
    QPen e2pen(Qt::green);
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::DashLine);
    curve->setPen(e2pen);
    curve->setSamples(time.data(), smoothed.data(), smoothed.count());

    return curve;
}

CpPlotCurve*
ExtendedCriticalPower::getPlotCurveForQualityPoint(RideFileCache *bests, RideFile::SeriesType series) {
    int count = bests->meanMaxArray(series).count();

    QVector<double> smooth;
    //QVector<double> smoothed;
    QVector<QwtPoint3D> heatSamples;

    double total = 0.0;

    int l = 6;
    int d = 3;
    for (int i = 1; i < 1 + d; i++) {
        double value = bests->meanMaxArray(series)[i];

        smooth.append(value);
        total += value;
    }

    for (int i=1; i < count - d; i++) {
        double newvalue = bests->meanMaxArray(series)[i+d];

        smooth.append(newvalue);
        total += newvalue;

        if (smooth.count() > l) {
            total -= smooth.at(0);
            smooth.remove(0);
        }

        //smoothed.append(total/smooth.count());

        double value = bests->meanMaxArray(series)[i];
        double heat = 500*((value-total/smooth.count())/value);

        if ((value-total/smooth.count())/value > 0.0005)
            heat = 1000;

        QwtPoint3D add(i/60.00f, value, heat);
        heatSamples << add;

    }


    CpPlotCurve *curve = new CpPlotCurve("rolling");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        curve->setRenderHint(QwtPlotItem::RenderAntialiased);

    curve->setPenWidth(1);
    QwtLinearColorMap *colorMap = new QwtLinearColorMap(Qt::yellow, Qt::green);
    curve->setColorMap(colorMap);
    curve->setSamples(heatSamples);
    return curve;
}

QwtPlotCurve*
ExtendedCriticalPower::getPlotCurveForDerived(RideFileCache *bests, RideFile::SeriesType series) {
    int count = bests->meanMaxArray(series).count();

    QVector<double> xValues;
    QVector<double> yValues;
    QVector<double> smoothed;
    QVector<double> time;

    double total = 0.0;

    int l = 10;
    int d = 5;
    for (int i = 1; i < 1 + d; i++) {
        double newvalue = bests->meanMaxArray(series)[i];

        yValues.append(newvalue);
        xValues.append((i)/60.0f);
        total += newvalue;
    }

    for (int i=1; i < count - d; i++) {
        double newvalue = bests->meanMaxArray(series)[i+d];

        yValues.append(newvalue);
        xValues.append((i+d)/60.0f);
        total += newvalue;

        if (yValues.count() > l) {
            total -= yValues.at(0);
            xValues.remove(0);
            yValues.remove(0);
        }

        // perform linear regression
        Statistic regress(xValues.data(), yValues.data(), yValues.count());

        smoothed.append(regress.slope()+1200);
        time.append(i/60.0f);
        qDebug() << i << regress.slope();

    }


    QwtPlotCurve *curve = new QwtPlotCurve("derived");
    if (appsettings->value(NULL, GC_ANTIALIAS, true).toBool() == true)
        curve->setRenderHint(QwtPlotItem::RenderAntialiased);
    QPen e2pen(Qt::green);
    e2pen.setWidth(1);
    e2pen.setStyle(Qt::DashLine);
    curve->setPen(e2pen);
    curve->setSamples(time.data(), smoothed.data(), smoothed.count());

    return curve;
}

void
ExtendedCriticalPower::verifyMin(double paa, double paa_min, double paa_dec, double paa_dec_min, double ecp_dec, double ecp_dec_min, double etau, double etau_min) {
    if (paa == paa_min)
        qDebug() <<"paa=paa_min";
    if (paa_dec == paa_dec_min)
        qDebug() <<"paa_dec=paa_dec_min";
    if (ecp_dec == ecp_dec_min)
        qDebug() <<"ecp_dec=ecp_dec_min";
    if (etau == etau_min)
        qDebug() <<"etau_dec=etau_dec_min";
}
