/*
 * Copyright (c) 2009 Sean C. Rhea (srhea@srhea.net)
 *
 * 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 "WeeklySummaryWindow.h"
#include "DaysScaleDraw.h"
#include "MainWindow.h"
#include "RideItem.h"
#include "RideMetric.h"
#include "SummaryMetrics.h"
#include "MetricAggregator.h"
#include "Zones.h"
#include "Units.h" // for MILES_PER_KM
#include <QtGui>
#include <qwt_plot.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_canvas.h>
#include <qwt_plot_grid.h>
#include <assert.h>

WeeklySummaryWindow::WeeklySummaryWindow(bool useMetricUnits,
                                         MainWindow *mainWindow) :
    GcWindow(mainWindow), mainWindow(mainWindow),
    useMetricUnits(useMetricUnits)
{
    setInstanceName("Weekly Summary Window");
    setControls(NULL);

    QGridLayout *glayout = new QGridLayout;

    // set up the weekly distance / duration plot:
    weeklyPlot = new QwtPlot();
    weeklyPlot->enableAxis(QwtPlot::yRight, true);
    weeklyPlot->setAxisMaxMinor(QwtPlot::xBottom,0);
    weeklyPlot->setAxisScaleDraw(QwtPlot::xBottom, new DaysScaleDraw());
    QFont weeklyPlotAxisFont = weeklyPlot->axisFont(QwtPlot::yLeft);
    weeklyPlotAxisFont.setPointSize(weeklyPlotAxisFont.pointSize() * 0.9f);
    weeklyPlot->setAxisFont(QwtPlot::xBottom, weeklyPlotAxisFont);
    weeklyPlot->setAxisFont(QwtPlot::yLeft, weeklyPlotAxisFont);
    weeklyPlot->setAxisFont(QwtPlot::yRight, weeklyPlotAxisFont);
    weeklyPlot->canvas()->setFrameStyle(QFrame::NoFrame);
    weeklyPlot->setCanvasBackground(Qt::white);

    weeklyDistCurve = new QwtPlotCurve();
    weeklyDistCurve->setStyle(QwtPlotCurve::Steps);
    QPen pen(Qt::SolidLine);
    weeklyDistCurve->setPen(pen);
    weeklyDistCurve->setBrush(Qt::red);
    weeklyDistCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
    weeklyDistCurve->setCurveAttribute(QwtPlotCurve::Inverted, true); // inverted, right-to-left
    weeklyDistCurve->attach(weeklyPlot);

    weeklyDurationCurve = new QwtPlotCurve();
    weeklyDurationCurve->setStyle(QwtPlotCurve::Steps);
    weeklyDurationCurve->setPen(pen);
    weeklyDurationCurve->setBrush(QColor(255,200,0,255));
    weeklyDurationCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
    weeklyDurationCurve->setCurveAttribute(QwtPlotCurve::Inverted, true); // inverted, right-to-left
    weeklyDurationCurve->setYAxis(QwtPlot::yRight);
    weeklyDurationCurve->attach(weeklyPlot);

    // set up the weekly bike score plot:
    weeklyBSPlot = new QwtPlot();
    weeklyBSPlot->enableAxis(QwtPlot::yRight, true);
    weeklyBSPlot->setAxisMaxMinor(QwtPlot::xBottom,0);
    weeklyBSPlot->setAxisScaleDraw(QwtPlot::xBottom, new DaysScaleDraw());
    QwtText textLabel = QwtText();
    weeklyBSPlot->setAxisFont(QwtPlot::xBottom, weeklyPlotAxisFont);
    weeklyBSPlot->setAxisFont(QwtPlot::yLeft, weeklyPlotAxisFont);
    weeklyBSPlot->setAxisFont(QwtPlot::yRight, weeklyPlotAxisFont);
    weeklyBSPlot->canvas()->setFrameStyle(QFrame::NoFrame);
    weeklyBSPlot->setCanvasBackground(Qt::white);

    weeklyBSCurve = new QwtPlotCurve();
    weeklyBSCurve->setStyle(QwtPlotCurve::Steps);
    weeklyBSCurve->setPen(pen);
    weeklyBSCurve->setBrush(Qt::blue);
    weeklyBSCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
    weeklyBSCurve->setCurveAttribute(QwtPlotCurve::Inverted, true); // inverted, right-to-left
    weeklyBSCurve->attach(weeklyBSPlot);

    weeklyRICurve = new QwtPlotCurve();
    weeklyRICurve->setStyle(QwtPlotCurve::Steps);
    weeklyRICurve->setPen(pen);
    weeklyRICurve->setBrush(Qt::green);
    weeklyRICurve->setRenderHint(QwtPlotItem::RenderAntialiased);
    weeklyRICurve->setCurveAttribute(QwtPlotCurve::Inverted, true); // inverted, right-to-left
    weeklyRICurve->setYAxis(QwtPlot::yRight);
    weeklyRICurve->attach(weeklyBSPlot);

    // set baseline curves to obscure linewidth variations along baseline
    pen.setWidth(2);
    weeklyBaselineCurve   = new QwtPlotCurve();
    weeklyBaselineCurve->setPen(pen);
    weeklyBaselineCurve->attach(weeklyPlot);
    weeklyBSBaselineCurve = new QwtPlotCurve();
    weeklyBSBaselineCurve->setPen(pen);
    weeklyBSBaselineCurve->attach(weeklyBSPlot);

    QwtPlotGrid *grid = new QwtPlotGrid();
    grid->enableX(false);
    QPen gridPen;
    gridPen.setStyle(Qt::DotLine);
    grid->setPen(gridPen);
    grid->attach(weeklyPlot);

    QwtPlotGrid *grid1 = new QwtPlotGrid();
    grid1->enableX(false);
    gridPen.setStyle(Qt::DotLine);
    grid1->setPen(gridPen);
    grid1->attach(weeklyBSPlot);

    weeklySummary = new QTextEdit;
    weeklySummary->setReadOnly(true);
    weeklySummary->setStyleSheet("QTextEdit { border: 0px}");

    QFont defaultFont; // mainwindow sets up the defaults.. we need to apply
    defaultFont.setPointSize(defaultFont.pointSize()-2);
    weeklySummary->setFont(defaultFont);

    glayout->addWidget(weeklySummary,0,0,1,-1); // row, col, rowspan, colspan. -1 == fill to edge
    glayout->addWidget(weeklyPlot,1,0);
    glayout->addWidget(weeklyBSPlot,1,1);

    glayout->setRowStretch(0, 3);           // stretch factor of summary
    glayout->setRowStretch(1, 2);           // stretch factor of weekly plots
    glayout->setColumnStretch(0, 1);        // stretch first column
    glayout->setColumnStretch(1, 1);        // stretch second column
    glayout->setRowMinimumHeight(0, 180);   // minimum height of weekly summary
    glayout->setRowMinimumHeight(1, 120);   // minimum height of weekly plots

    setLayout(glayout);

    //connect(mainWindow, SIGNAL(rideSelected()), this, SLOT(refresh()));
    connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(refresh()));
    connect(mainWindow, SIGNAL(zonesChanged()), this, SLOT(refresh()));
    connect(mainWindow, SIGNAL(rideAdded(RideItem*)), this, SLOT(refresh()));
    connect(mainWindow, SIGNAL(rideDeleted(RideItem*)), this, SLOT(refresh()));
}

void
WeeklySummaryWindow::refresh()
{
    // not active, or null ride item then do nothing
    if (!amVisible()) return;
    const RideItem *ride = myRideItem;
    if (!ride) return;

    // work through the rides
    const QTreeWidgetItem *allRides = mainWindow->allRideItems();

    // start / end of week
    QDate wstart = ride->dateTime.date();
    wstart = wstart.addDays(Qt::Monday - wstart.dayOfWeek());
    assert(wstart.dayOfWeek() == Qt::Monday);

    // weekly values
    QVector<double> time_in_zone; // max 10 zones supported
    QVector<double> time_in_hrzone; // max 10 zones supported

    double weeklyRides=0;
    double weeklySeconds=0;
    double weeklyDistance=0;
    double weeklyW=0;
    double weeklyBS=0;
    double weeklyCS=0;
    double weeklyRI=0;

    // daily values
    double dailyRides[7];
    double dailySeconds[7];
    double dailyDistance[7];
    double dailyW[7];
    double dailyBS[7];
    double dailyXP[7];
    double dailyRI[7];

    // init daily values to zero
    for(int i=0; i<7; i++)
        dailyRides[i] =
        dailySeconds[i] =
        dailyDistance[i] =
        dailyW[i] =
        dailyBS[i] =
        dailyXP[i] =
        dailyRI[i] = 0;

    // how many zones did we see?
    int num_zones = -1;
    int zone_range = -1;

    int num_hrzones = -1;
    int hrzone_range = -1;

    for (int i = 0; i < allRides->childCount(); ++i) {

	QTreeWidgetItem *twi = allRides->child(i);
        RideItem *item = static_cast<RideItem*>(twi);

	int day;

        if (((day = wstart.daysTo(item->dateTime.date())) >= 0) && (day < 7)) {

            // one more ride for this day
            dailyRides[day]++;
            weeklyRides++;

            // get metrics from metricDB (always in metric units)
            SummaryMetrics metrics = mainWindow->metricDB->getRideMetrics(item->fileName);

            // increment daily values
            dailySeconds[day] += metrics.getForSymbol("time_riding");
            dailyDistance[day] += metrics.getForSymbol("total_distance") * (useMetricUnits ? 1 : MILES_PER_KM);
            dailyW[day] += metrics.getForSymbol("total_work");
            dailyBS[day] += metrics.getForSymbol("skiba_bike_score");
            dailyXP[day] += metrics.getForSymbol("skiba_power");

            // average RI for the day
            if (dailyRides[day] > 1)
                dailyRI[day] = ((dailyRI[day] * (dailyRides[day]-1))
                                + metrics.getForSymbol("skiba_relative_intensity")) / dailyRides[day];
            else
                dailyRI[day] = metrics.getForSymbol("skiba_relative_intensity");

            // increment weekly values
            weeklySeconds += metrics.getForSymbol("time_riding");
            weeklyDistance += metrics.getForSymbol("total_distance") * (useMetricUnits ? 1 : MILES_PER_KM);
            weeklyCS += metrics.getForSymbol("daniels_points");
            weeklyW += metrics.getForSymbol("total_work");
            weeklyBS += metrics.getForSymbol("skiba_bike_score");

            // average RI for the week
            if (weeklyRides > 1)
                weeklyRI = ((weeklyRI * (weeklyRides-1))
                                + metrics.getForSymbol("skiba_relative_intensity")) / weeklyRides;
            else
                weeklyRI = metrics.getForSymbol("skiba_relative_intensity");

            // compute time in zones
            // Power
            if (num_zones < item->numZones()) {
                num_zones = item->numZones();
                time_in_zone.resize(num_zones);
            }
            zone_range = item->zoneRange();

            for (int j=0; j<item->numZones(); j++) {
                QString symbol = QString("time_in_zone_L%1").arg(j+1);
                time_in_zone[j] += metrics.getForSymbol(symbol);
	        }
            // HR
            if (num_hrzones < item->numHrZones()) {
                num_hrzones = item->numHrZones();
                time_in_hrzone.resize(num_hrzones);
            }
            hrzone_range = item->hrZoneRange();

            for (int j=0; j<item->numHrZones(); j++) {
                QString symbol = QString("time_in_zone_H%1").arg(j+1);
                time_in_hrzone[j] += metrics.getForSymbol(symbol);
	        }
        }
    }

    // break duration into hours, mins and seconds
    int seconds = round(weeklySeconds);
    int minutes = seconds / 60;
    seconds %= 60;
    int hours = minutes / 60;
    minutes %= 60;

    QString summary;
    summary = tr(
	   "<center>"
	   "<h3>Week of %1 through %2</h3>"
	   "<h3>Summary</h3>"
	   "<p>"
	   "<table align=\"center\" width=\"60%\" border=0>"
	   "<tr><td>Total time riding:</td>"
	   "    <td align=\"right\">%3:%4:%5</td></tr>"
	   "<tr><td>Total distance (%6):</td>"
	   "    <td align=\"right\">%7</td></tr>"
	   "<tr><td>Total work (kJ):</td>"
	   "    <td align=\"right\">%8</td></tr>"
	   "<tr><td>Daily Average work (kJ):</td>"
	   "    <td align=\"right\">%9</td></tr>")
	.arg(wstart.toString(tr("MM/dd/yyyy")))
	.arg(wstart.addDays(6).toString(tr("MM/dd/yyyy")))
	.arg(hours)
	.arg(minutes, 2, 10, QLatin1Char('0'))
	.arg(seconds, 2, 10, QLatin1Char('0'))
	.arg(useMetricUnits ? "km" : "miles")
	.arg(weeklyDistance, 0, 'f', 1)
	.arg((unsigned) round(weeklyW))
	.arg((unsigned) round(weeklyW/ 7));

    bool useBikeScore = (weeklyBS > 0);

    if (num_zones != -1) {
	if (useBikeScore)
	    summary += tr("<tr><td>Total BikeScore:</td>"
		   "    <td align=\"right\">%1</td></tr>"
		   "<tr><td>Total Daniels Points:</td>"
		   "    <td align=\"right\">%2</td></tr>"
		   "<tr><td>Net Relative Intensity:</td>"
		   "    <td align=\"right\">%3</td></tr>")
		.arg((unsigned) round(weeklyBS))
                .arg(weeklyCS, 0, 'f', 1)
		.arg(weeklyRI, 0, 'f', 3);

        if (zone_range >= 0) {
            summary += tr( "</table>" "<h3>Power Zones</h3>");
            summary += mainWindow->zones()->summarize(zone_range, time_in_zone);
        } else {
            summary += "No zones configured - zone summary not available.";
        }
        if (hrzone_range >= 0) {
            summary += tr( "</table>" "<h3>Heart Rate Zones</h3>");
            summary += mainWindow->hrZones()->summarize(hrzone_range, time_in_hrzone);
        }
    }

    summary += "</center>";

    // set the axis labels of the weekly plots
    QwtText textLabel = QwtText(useMetricUnits ? "km" : "miles");
    QFont weeklyPlotAxisTitleFont = textLabel.font();
    weeklyPlotAxisTitleFont.setPointSize(10);
    weeklyPlotAxisTitleFont.setBold(true);
    textLabel.setFont(weeklyPlotAxisTitleFont);
    weeklyPlot->setAxisTitle(QwtPlot::yLeft, textLabel);
    textLabel.setText("Minutes");
    weeklyPlot->setAxisTitle(QwtPlot::yRight, textLabel);
    textLabel.setText(useBikeScore ? "BikeScore" : "kJoules");
    weeklyBSPlot->setAxisTitle(QwtPlot::yLeft, textLabel);
    textLabel.setText(useBikeScore ? "Intensity" : "xPower");
    weeklyBSPlot->setAxisTitle(QwtPlot::yRight, textLabel);

    // for the daily distance/duration and bikescore plots:
    // first point: establish zero position
    // points 2N, 2N+1: Nth day (N from 1 to 7), up then down
    // 16th point: move to draw baseline off right of plot

    double xdist[16];
    double xdur[16];
    double xbsorw[16];
    double xriorxp[16];

    double ydist[16];   // daily distance
    double ydur[16];    // daily total time
    double ybsorw[16];     // daily minutes
    double yriorxp[16];     // daily relative intensity

    // data for a "baseline" curve to draw a baseline
    double xbaseline[] = {0, 8};
    double ybaseline[] = {0, 0};
    weeklyBaselineCurve->setData(xbaseline, ybaseline, 2);
    weeklyBSBaselineCurve->setData(xbaseline, ybaseline, 2);

    const double bar_width = 0.3;

    int i = 0;
    xdist[i] =
	xdur[i] =
	xbsorw[i] =
	xriorxp[i] =
	0;

    ydist[i] =
	ydur[i] =
	ybsorw[i] =
	yriorxp[i] =
	0;

    for(int day = 0; day < 7; day++){
	double x;

	i++;
        xdist[i]         = x = day + 1 - bar_width;
        xdist[i + 1]     = x += bar_width;
        xdur[i]          = x;
        xdur[i + 1]      = x += bar_width;

        xbsorw[i]        = x = day + 1 - bar_width;
        xbsorw[i + 1]    = x += bar_width;
        xriorxp[i]       = x;
	xriorxp[i + 1]   = x += bar_width;

	ydist[i]         = dailyDistance[day];
	ydur[i]          = dailySeconds[day] / 60;
	ybsorw[i]        = useBikeScore ? dailyBS[day] : dailyW[day] / 1000;
	yriorxp[i]       = useBikeScore ? dailyRI[day] : dailyXP[day];

	i++;
	ydist[i] = 0;
	ydur[i]  = 0;
	ybsorw[i]   = 0;
	yriorxp[i]   = 0;
    }

    // sweep a baseline off the right of the plot
    i++;
    xdist[i] =
	xdur[i] =
	xbsorw[i] =
	xriorxp[i] =
	8;

    ydist[i] =
	ydur[i] =
	ybsorw[i] =
	yriorxp[i] =
	0;

    // Distance/Duration plot:
    weeklyDistCurve->setData(xdist, ydist, 16);
    weeklyPlot->setAxisScale(QwtPlot::yLeft, 0, weeklyDistCurve->maxYValue()*1.1, 0);
    weeklyPlot->setAxisScale(QwtPlot::xBottom, 0.5, 7.5, 0);
    weeklyPlot->setAxisTitle(QwtPlot::yLeft, useMetricUnits ? "Kilometers" : "Miles");

    weeklyDurationCurve->setData(xdur, ydur, 16);
    weeklyPlot->setAxisScale(QwtPlot::yRight, 0, weeklyDurationCurve->maxYValue()*1.1, 0);
    weeklyPlot->replot();

    // BikeScore/Relative Intensity plot
    weeklyBSCurve->setData(xbsorw, ybsorw, 16);
    weeklyBSPlot->setAxisScale(QwtPlot::yLeft, 0, weeklyBSCurve->maxYValue()*1.1, 0);
    weeklyBSPlot->setAxisScale(QwtPlot::xBottom, 0.5, 7.5, 0);

    // set axis minimum for relative intensity
    double RImin = -1;
    for(int i = 1; i < 16; i += 2)
        if (yriorxp[i] > 0 && ((RImin < 0) || (yriorxp[i] < RImin)))
	    RImin = yriorxp[i];
    if (RImin < 0)
	RImin = 0;
    RImin *= 0.8;
    for (int i = 0; i < 16; i ++)
	if (yriorxp[i] < RImin)
	    yriorxp[i] = RImin;
    weeklyRICurve->setBaseline(RImin);
    weeklyRICurve->setData(xriorxp, yriorxp, 16);
    weeklyBSPlot->setAxisScale(QwtPlot::yRight, RImin, weeklyRICurve->maxYValue()*1.1, 0);

    weeklyBSPlot->replot();

    weeklySummary->setHtml(summary);
}
