/*
 * Copyright (c) 2006 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
 */

#ifndef _GC_AllPlot_h
#define _GC_AllPlot_h 1
#include "GoldenCheetah.h"
#include "Colors.h"
#include "AllPlotSlopeCurve.h"

#include <qwt_plot.h>
#include <qwt_axis_id.h>
#include <qwt_series_data.h>
#include <qwt_plot_zoomer.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_dict.h>
#include <qwt_plot_marker.h>
#include <qwt_plot_intervalcurve.h>
#include <qwt_point_3d.h>
#include <qwt_scale_widget.h>
#include <qwt_compat.h>
#include <QtGui>
#include <QFont>

#include <QTableWidget>
#include <QStackedWidget>
#include <QTextEdit>

// span slider specials
#include <qxtspanslider.h>
#include <QStyleFactory>
#include <QStyle>


#include "RideFile.h"

class QwtPlotCurve;
class QwtPlotIntervalCurve;
class QwtPlotGrid;
class QwtPlotMarker;
class RideItem;
class AllPlotBackground;
class AllPlotZoneLabel;
class AllPlotWindow;
class AllPlot;
struct RideFilePoint;
class IntervalItem;
class IntervalPlotData;
class Context;
class LTMToolTip;
class LTMCanvasPicker;
class QwtAxisId;

class CurveColors : public QObject
{
    Q_OBJECT;

    public:
        CurveColors(QwtPlot *plot, bool allplot=false) : isolated(false), allplot(allplot), plot(plot) {

            // span slider appears when curve isolated
            // to enable zooming in and out
            slider = new QxtSpanSlider(Qt::Vertical, plot);
            slider->setFocusPolicy(Qt::NoFocus);
            slider->setHandleMovementMode(QxtSpanSlider::NoOverlapping);
            slider->setMinimum(0);
            slider->setMaximum(100); // %age
            slider->setShowRail(false); // no rail please

#ifdef Q_OS_MAC
            // BUG in QMacStyle and painting of spanSlider
            // so we use a plain style to avoid it, but only
            // on a MAC, since win and linux are fine
#if QT_VERSION > 0x5000
            QStyle *style = QStyleFactory::create("fusion");
#else
            QStyle *style = QStyleFactory::create("Cleanlooks");
#endif
            slider->setStyle(style);
#endif
            slider->hide();
            connect(slider, SIGNAL(lowerPositionChanged(int)), this, SLOT(zoomChanged()));
            connect(slider, SIGNAL(upperPositionChanged(int)), this, SLOT(zoomChanged()));

            saveState();
        }

        ~CurveColors() {
            delete slider;
        }

        void restoreState() {

            // if a curve is hidden, don't show its labels we hack this
            // by hiding any labels with the same color, a bit of a
            // hack but works largely and avoids having to integrate
            // with the plot object to understand the relationship between
            // labels and curves (which is only really known by LTMPlot)
            QList<QColor> shown;

            // make all the curves visible (that should be)
            // and remember the colors so we can show/hide 
            // the associated labels
            QHashIterator<QwtPlotSeriesItem *, bool> c(state);
            while (c.hasNext()) {
                c.next();
                c.key()->setVisible(c.value());

                if (c.value()) {
                    if (c.key()->rtti() == QwtPlotItem::Rtti_PlotIntervalCurve) {
                        shown << static_cast<QwtPlotIntervalCurve*>(c.key())->pen().color();
                    } else if (c.key()->rtti() == QwtPlotItem::Rtti_PlotCurve) {
                        shown << static_cast<QwtPlotCurve*>(c.key())->pen().color();
                    }
                }
            }

            // show all labels
            QHashIterator<QwtPlotMarker *, QwtScaleWidget*> l(labels);
            while (l.hasNext()) {
                l.next();

                // we cannot hide via legend on allplot so always hide/show labels
                l.key()->setVisible(allplot || shown.contains(l.key()->label().color()));
            }

            // make all the axes have the right color
            QHashIterator<QwtScaleWidget *, QPalette> s(colors);
            while (s.hasNext()) {
                s.next();
                s.key()->setPalette(s.value());
            }

            slider->hide();
            isolated = false;
        }

        void saveState() {

            // don't save in this state!!
            if (isolated) restoreState();
            else slider->hide();

            state.clear();
            labels.clear();
            colors.clear();
            lims.clear();

            // Labels
            foreach(QwtPlotItem *item, plot->itemList(QwtPlotItem::Rtti_PlotMarker)) {

                // ignore event / interval markers / blanks
                if (static_cast<QwtPlotMarker*>(item)->lineStyle() == QwtPlotMarker::VLine ||
                    static_cast<QwtPlotMarker*>(item)->label().text() == "") continue;

                QwtScaleWidget *x = plot->axisWidget(static_cast<QwtPlotMarker*>(item)->yAxis());
                labels.insert(static_cast<QwtPlotMarker*>(item), x);
            }

            // curves
            foreach(QwtPlotItem *item, plot->itemList(QwtPlotItem::Rtti_PlotCurve)) {

                state.insert(static_cast<QwtPlotSeriesItem*>(item), 
                             static_cast<QwtPlotSeriesItem*>(item)->isVisible());

                QwtScaleWidget *x = plot->axisWidget(static_cast<QwtPlotSeriesItem*>(item)->yAxis());
                colors.insert(x, x->palette());

                QwtPlotCurve *curve = static_cast<QwtPlotCurve*>(item);

                QPointF lim;
                lim.setX(curve->minYValue());
                lim.setY(curve->maxYValue() * 1.1f);

                lims.insert(curve, lim);
            }

            // interval curves
            foreach(QwtPlotItem *item, plot->itemList(QwtPlotItem::Rtti_PlotIntervalCurve)) {

                state.insert(static_cast<QwtPlotSeriesItem*>(item), 
                             static_cast<QwtPlotSeriesItem*>(item)->isVisible());

                QwtScaleWidget *x = plot->axisWidget(static_cast<QwtPlotSeriesItem*>(item)->yAxis());
                colors.insert(x, x->palette());

            }
        }

        // remove curve if being zapped (e.g. reference line)
        void remove(QwtPlotSeriesItem *remove) {
            state.remove(remove);
        }

        void insert(QwtPlotSeriesItem *add) {
            state.insert(add, add->isVisible());
        }

        void isolate(QwtPlotSeriesItem *curve) {

            // make the curve colored but all others go dull
            QHashIterator<QwtPlotSeriesItem *, bool> c(state);
            while (c.hasNext()) {
                c.next();
                if (c.key() == curve) {
                    c.key()->setVisible(c.value());
                } else {

                    // hide others
                    c.key()->setVisible(false);
                }
            }

            isolated = true;
        }

        void isolateAxis(QwtAxisId id, bool showslider = false) {

            isolatedIDpos = id.pos;
            isolatedIDid = id.id;
            isolatedAxis = plot->axisWidget(id);
            isolatedDiv = plot->axisScaleDiv(id);

            // if a curve is hidden, don't show its labels we hack this
            // by hiding any labels with the same color, a bit of a
            // hack but works largely and avoids having to integrate
            // with the plot object to understand the relationship between
            // labels and curves (which is only really known by LTMPlot)
            QList<QColor> shown;

            // hide curves that are not ours
            QHashIterator<QwtPlotSeriesItem *, bool> c(state);
            while (c.hasNext()) {
                c.next();

                // isolate on axis hover (but leave huighlighters alone)
                if (c.key()->yAxis() == id || c.key()->yAxis() == QwtAxisId(QwtAxis::yLeft,2)) {

                    // show and remember color
                    c.key()->setVisible(c.value());
                    if (c.value()) {
                        if (c.key()->rtti() == QwtPlotItem::Rtti_PlotIntervalCurve) {
                            shown << static_cast<QwtPlotIntervalCurve*>(c.key())->pen().color();
                        } else if (c.key()->rtti() == QwtPlotItem::Rtti_PlotCurve) {
                            shown << static_cast<QwtPlotCurve*>(c.key())->pen().color();
                        }
                    }

                    if (showslider && c.key()->yAxis() == id) {

                        if (c.key()->yAxis().pos == QwtAxis::yLeft)
                            slider->move(plot->canvas()->pos().x(), 10);
                        else
                            slider->move(plot->canvas()->pos().x() +
                                         plot->canvas()->width() - slider->width(), 10);

                        slider->setLowerValue(0);
                        slider->setUpperValue(100);
                        slider->setFixedHeight(plot->canvas()->height()-20);
                        slider->show();
                    }

                } else {
                    // hide others
                    c.key()->setVisible(false);
                }
            }

            QwtScaleWidget *ours = plot->axisWidget(id);

            // dull axis that are not ours
            QHashIterator<QwtScaleWidget *, QPalette> s(colors);
            while (s.hasNext()) {
                s.next();

                // isolate on axis hover
                if (s.key() != ours) {
                    QPalette pal = s.value();
                    pal.setColor(QPalette::WindowText, QColor(Qt::gray));
                    pal.setColor(QPalette::Text, QColor(Qt::gray));
                    s.key()->setPalette(pal);
                }
            }

            // hide labels that are not ours
            QHashIterator<QwtPlotMarker *, QwtScaleWidget*> l(labels);
            while (l.hasNext()) {
                l.next();

                if (l.value() != ours) {
                    l.key()->setVisible(false);
                } else {
                    // we cannot hide via legend on allplot so always hide/show labels
                    l.key()->setVisible(allplot || shown.contains(l.key()->label().color()));
                }
            }

            isolated = true;
        }

        bool isolated, allplot;

    public slots:
        void zoomChanged() {
            // change ...
            if (!slider->isHidden()) {

                plot->setAxisScale(QwtAxisId(isolatedIDpos, isolatedIDid),
                isolatedDiv.upperBound() * slider->lowerValue() / 100.00f,
                isolatedDiv.upperBound() * slider->upperValue() / 100.00f);
                
                plot->replot();
            }
        }

    private:
        QxtSpanSlider *slider;
        QwtPlot *plot;
        QHash<QwtPlotSeriesItem *, bool> state;
        QHash<QwtPlotMarker *, QwtScaleWidget*> labels;
        QHash<QwtScaleWidget*, QPalette> colors;
        QHash<QwtPlotSeriesItem *, QPointF> lims;

        int isolatedIDpos, isolatedIDid;
        QwtScaleWidget *isolatedAxis;
        QwtScaleDiv isolatedDiv;

};

class AllPlot;
class MergeAdjust;
class AllPlotObject : public QObject
{
    Q_OBJECT;

    // one set for every ride being plotted, which
    // as standard is just one, its only when we start
    // compare mode that we get more...

    public:

    AllPlotObject(AllPlot*); // construct associate with a plot
    ~AllPlotObject(); // delete and disassociate from a plot

    void setVisible(bool); // show or hide objects
    void setColor(QColor color); // set ALL curves the same color
    void hideUnwanted(); // hide curves we are not interested in
                         // using setVisible ...

    QwtPlotGrid *grid;
    QVector<QwtPlotMarker*> d_mrk;
    QVector<QwtPlotMarker*> cal_mrk;
    QwtPlotMarker curveTitle;
    QwtPlotMarker *allMarker1;
    QwtPlotMarker *allMarker2;
    // reference lines
    QVector<QwtPlotCurve*> referenceLines;
    QVector<QwtPlotCurve*> tmpReferenceLines;

    QwtPlotCurve *wattsCurve;
    QwtPlotCurve *slopeCurve;
    AllPlotSlopeCurve *altSlopeCurve;
    QwtPlotCurve *atissCurve;
    QwtPlotCurve *antissCurve;
    QwtPlotCurve *rvCurve;
    QwtPlotCurve *rcadCurve;
    QwtPlotCurve *rgctCurve;
    QwtPlotCurve *gearCurve;
    QwtPlotCurve *smo2Curve;
    QwtPlotCurve *thbCurve;
    QwtPlotCurve *o2hbCurve;
    QwtPlotCurve *hhbCurve;
    QwtPlotCurve *npCurve;
    QwtPlotCurve *xpCurve;
    QwtPlotCurve *apCurve;
    QwtPlotCurve *hrCurve;
    QwtPlotCurve *tcoreCurve;
    QwtPlotCurve *speedCurve;
    QwtPlotCurve *accelCurve;
    QwtPlotCurve *wattsDCurve;
    QwtPlotCurve *cadDCurve;
    QwtPlotCurve *nmDCurve;
    QwtPlotCurve *hrDCurve;
    QwtPlotCurve *cadCurve;
    QwtPlotCurve *altCurve;
    QwtPlotCurve *tempCurve;
    QwtPlotIntervalCurve *windCurve;
    QwtPlotCurve *torqueCurve;
    QwtPlotCurve *balanceLCurve;
    QwtPlotCurve *balanceRCurve;
    QwtPlotCurve *wCurve;
    QwtPlotCurve *mCurve;
    QwtPlotCurve *lteCurve;
    QwtPlotCurve *rteCurve;
    QwtPlotCurve *lpsCurve;
    QwtPlotCurve *rpsCurve;
    QwtPlotCurve *lpcoCurve;
    QwtPlotCurve *rpcoCurve;
    QwtPlotIntervalCurve *lppCurve;
    QwtPlotIntervalCurve *rppCurve;
    QwtPlotIntervalCurve *lpppCurve;
    QwtPlotIntervalCurve *rpppCurve;

    // source data
    QVector<double> match;
    QVector<double> matchTime;
    QVector<double> matchDist;
    QVector<QwtPlotMarker*> matchLabels;
    QVector<double> wprime;
    QVector<double> wprimeTime;
    QVector<double> wprimeDist;

    QVector<double> hrArray;
    QVector<double> tcoreArray;
    QVector<double> wattsArray;
    QVector<double> atissArray;
    QVector<double> antissArray;
    QVector<double> rvArray;
    QVector<double> rcadArray;
    QVector<double> rgctArray;
    QVector<double> gearArray;
    QVector<double> smo2Array;
    QVector<double> thbArray;
    QVector<double> o2hbArray;
    QVector<double> hhbArray;
    QVector<double> npArray;
    QVector<double> xpArray;
    QVector<double> apArray;
    QVector<double> speedArray;
    QVector<double> accelArray;
    QVector<double> wattsDArray;
    QVector<double> cadDArray;
    QVector<double> nmDArray;
    QVector<double> hrDArray;
    QVector<double> cadArray;
    QVector<double> timeArray;
    QVector<double> distanceArray;
    QVector<double> altArray;
    QVector<double> slopeArray;
    QVector<double> tempArray;
    QVector<double> windArray;
    QVector<double> torqueArray;
    QVector<double> balanceArray;
    QVector<double> lteArray;
    QVector<double> rteArray;
    QVector<double> lpsArray;
    QVector<double> rpsArray;
    QVector<double> lpcoArray;
    QVector<double> rpcoArray;
    QVector<double> lppbArray;
    QVector<double> rppbArray;
    QVector<double> lppeArray;
    QVector<double> rppeArray;
    QVector<double> lpppbArray;
    QVector<double> rpppbArray;
    QVector<double> lpppeArray;
    QVector<double> rpppeArray;

    // smoothed data
    QVector<double> smoothWatts;
    QVector<double> smoothAT;
    QVector<double> smoothANT;
    QVector<double> smoothRV;
    QVector<double> smoothRCad;
    QVector<double> smoothRGCT;
    QVector<double> smoothGear;
    QVector<double> smoothSmO2;
    QVector<double> smoothtHb;
    QVector<double> smoothO2Hb;
    QVector<double> smoothHHb;
    QVector<double> smoothNP;
    QVector<double> smoothAP;
    QVector<double> smoothXP;
    QVector<double> smoothHr;
    QVector<double> smoothTcore;
    QVector<double> smoothSpeed;
    QVector<double> smoothAccel;
    QVector<double> smoothWattsD;
    QVector<double> smoothCadD;
    QVector<double> smoothNmD;
    QVector<double> smoothHrD;
    QVector<double> smoothCad;
    QVector<double> smoothTime;
    QVector<double> smoothDistance;
    QVector<double> smoothAltitude;
    QVector<double> smoothSlope;
    QVector<double> smoothTemp;
    QVector<double> smoothWind;
    QVector<double> smoothTorque;
    QVector<double> smoothBalanceL;
    QVector<double> smoothBalanceR;
    QVector<double> smoothLTE;
    QVector<double> smoothRTE;
    QVector<double> smoothLPS;
    QVector<double> smoothRPS;
    QVector<double> smoothLPCO;
    QVector<double> smoothRPCO;
    QVector<QwtIntervalSample> smoothLPP;
    QVector<QwtIntervalSample> smoothRPP;
    QVector<QwtIntervalSample> smoothLPPP;
    QVector<QwtIntervalSample> smoothRPPP;
    QVector<QwtIntervalSample> smoothRelSpeed;

    // highlighting intervals
    QwtPlotCurve *intervalHighlighterCurve,  // highlight selected intervals on the Plot
                 *intervalHoverCurve;

    // the plot we work for
    AllPlot *plot;

    // some handy stuff
    double maxSECS, maxKM;
};

class AllPlot : public QwtPlot
{
    Q_OBJECT
    G_OBJECT


    public:

        // you can declare which series to plot, none means do them all
        // wanttext is to say if plot markers should have text
        AllPlot(QWidget *parent, AllPlotWindow *window, Context *context, 
                RideFile::SeriesType series = RideFile::none, RideFile::SeriesType secSeries = RideFile::none, bool wanttext = true);
        ~AllPlot();

        bool eventFilter(QObject *object, QEvent *e);

        // set the curve data e.g. when a ride is selected
        void setDataFromRide(RideItem *_rideItem);
        void setDataFromRideFile(RideFile *ride, AllPlotObject *object); // when plotting lots of rides on fullPlot
        void setDataFromPlot(AllPlot *plot, int startidx, int stopidx);
        void setDataFromPlot(AllPlot *plot); // used for single series plotting
        void setDataFromPlots(QList<AllPlot*>); // user for single series comparing
        void setDataFromObject(AllPlotObject *object, AllPlot *reference); // for allplot when one per ride in a stack
                                                                           // reference is for settings et al
        void setMatchLabels(AllPlotObject *object); // set labels from object

        // convert from time/distance to index in *smoothed* datapoints
        int timeIndex(double) const;
        int distanceIndex(double) const;

        // plot redraw functions
        bool shadeZones() const;
        void refreshZoneLabels();
        void refreshIntervalMarkers();
        void refreshCalibrationMarkers();
        void refreshReferenceLines();
        void refreshReferenceLinesForAllPlots();
        void setAxisTitle(QwtAxisId axis, QString label);

        // refresh data / plot parameters
        void recalc(AllPlotObject *objects);
        void setYMax();
        void setLeftOnePalette(); // color of yLeft,1 axis
        void setRightPalette(); // color of yRight,0 axis
        void setXTitle();
        void setHighlightIntervals(bool);

        void plotTmpReference(int axis, int x, int y);
        void confirmTmpReference(double value, int axis, bool allowDelete);
        QwtPlotCurve* plotReferenceLine(const RideFilePoint *referencePoint);

        virtual void replot();

        // remembering state etc
        CurveColors *curveColors;

    public slots:

        void setShow(RideFile::SeriesType, bool);
        void setShowAccel(bool show);
        void setShowPowerD(bool show);
        void setShowCadD(bool show);
        void setShowTorqueD(bool show);
        void setShowHrD(bool show);
        void setShowPower(int id);
        void setShowAltSlope(int id);
        void setShowSlope(bool show);
        void setShowNP(bool show);
        void setShowATISS(bool show);
        void setShowANTISS(bool show);
        void setShowXP(bool show);
        void setShowAP(bool show);
        void setShowHr(bool show);
        void setShowTcore(bool show);
        void setShowSpeed(bool show);
        void setShowCad(bool show);
        void setShowAlt(bool show);
        void setShowTemp(bool show);
        void setShowWind(bool show);
        void setShowRV(bool show);
        void setShowRGCT(bool show);
        void setShowRCad(bool show);
        void setShowSmO2(bool show);
        void setShowtHb(bool show);
        void setShowO2Hb(bool show);
        void setShowHHb(bool show);
        void setShowGear(bool show);
        void setShowW(bool show);
        void setShowTorque(bool show);
        void setShowBalance(bool show);
        void setShowTE(bool show);
        void setShowPS(bool show);
        void setShowPCO(bool show);
        void setShowDC(bool show);
        void setShowPPP(bool show);
        void setShowGrid(bool show);
        void setPaintBrush(int state);
        void setShadeZones(bool x) { shade_zones=x; }
        void setSmoothing(int value);
        void setByDistance(int value);
        void setWantAxis(bool x, bool y=false) { wantaxis = x; wantxaxis = y;}
        void configChanged(qint32);

        // for tooltip
        void pointHover(QwtPlotCurve*, int);
        void intervalHover(IntervalItem *h);

    protected:

        friend class ::AllPlotBackground;
        friend class ::AllPlotZoneLabel;
        friend class ::AllPlotWindow;
        friend class ::MergeAdjust;
        friend class ::IntervalPlotData;

        // cached state
        RideItem *rideItem;
        AllPlotBackground *bg;
        QSettings *settings;
        IntervalItem *hovered;

        // controls
        bool shade_zones;
        int showPowerState;
        int showAltSlopeState;
        bool showATISS;
        bool showANTISS;
        bool showNP;
        bool showXP;
        bool showAP;
        bool showHr;
        bool showTcore;
        bool showSpeed;
        bool showAccel;
        bool showPowerD;
        bool showCadD;
        bool showTorqueD;
        bool showHrD;
        bool showCad;
        bool showAlt;
        bool showSlope;
        bool showTemp;
        bool showWind;
        bool showW;
        bool showTorque;
        bool showBalance;
        bool showTE;
        bool showPS;
        bool showPCO;
        bool showDC;
        bool showPPP;
        bool showRV;
        bool showRGCT;
        bool showRCad;
        bool showSmO2;
        bool showtHb;
        bool showO2Hb;
        bool showHHb;
        bool showGear;

        // plot objects
        AllPlotObject *standard;
        QList<QwtPlotCurve*> compares; // when plotting single series in compare mode

        QList <AllPlotZoneLabel *> zoneLabels;

        // array / smooth state
        int smooth;
        bool bydist;
        bool fill;

        // scope of plot (none means all, or just for a specific series
        RideFile::SeriesType scope;
        RideFile::SeriesType secondaryScope;

    protected:
        friend class ::AllPlotObject;
        Context *context;

    private:

        AllPlot *referencePlot;
        QWidget *parent;
        AllPlotWindow *window;
        bool wanttext, wantaxis, wantxaxis;
        bool isolation;
        LTMToolTip *tooltip;
        LTMCanvasPicker *_canvasPicker; // allow point selection/hover
        QFont labelFont;

        void setAltSlopePlotStyle (AllPlotSlopeCurve *curve);
        static void nextStep( int& step );

};

// Produce Labels for X-Axis
class ScaleScaleDraw: public QwtScaleDraw
{
    public:

        ScaleScaleDraw(double factor=1.0f) : factor(factor), decimals(0) {}

        void setFactor(double f) { factor=f; }
        void setDecimals(int d) { decimals=d; }

        virtual QwtText label(double v) const {
            return QString("%1").arg(v * factor, 0, 'f', decimals);
        }

    private:

        double factor;
        int decimals;
};
#endif // _GC_AllPlot_h

