#include <QtGui>
#include <cmath>
#include "plotter.h"

// #include <stdio.h>
// #include <iostream>
// using namespace std;

Plotter::Plotter(QWidget *parent)
    : QWidget(parent)
{
    setBackgroundRole(QPalette::Dark);
    setAutoFillBackground(true);
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    setFocusPolicy(Qt::StrongFocus);
    rubberBandIsShown = false;

//     zoomInButton = new QToolButton(this);
    //zoomInButton->setIcon(QIcon(":/images/zoomin.png"));
//     zoomInButton->adjustSize();
//     connect(zoomInButton, SIGNAL(clicked()), this, SLOT(zoomIn()));

//     zoomOutButton = new QToolButton(this);
    //zoomOutButton->setIcon(QIcon(":/images/zoomout.png"));
//     zoomOutButton->adjustSize();
//     connect(zoomOutButton, SIGNAL(clicked()), this, SLOT(zoomOut()));
 
//  	settings = new PlotSettings();
// 	setPlotSettings(settings);
	setPlotSettings(PlotSettings());


}

/**
set min and max values for the X,Y axis
*/
void Plotter::adjustAxis(double &minx, double &maxx,double &miny, double &maxy )
  {
	PlotSettings settings;
        settings.minX = minx;
        settings.maxX = maxx;
        settings.minY = miny;
        settings.maxY = maxy;
	settings.adjust();

        zoomStack.resize(curZoom + 1);
        zoomStack.append(settings);
        zoomIn();
  }


 void Plotter::setPlotSettings(const PlotSettings &settings)
 {
     zoomStack.clear();
     zoomStack.append(settings);
     curZoom = 0;
//      zoomInButton->hide();
//      zoomOutButton->hide();
     refreshPixmap();
}

void Plotter::zoomOut()
{
    if (curZoom > 0) {
        --curZoom;
/*        zoomOutButton->setEnabled(curZoom > 0);
        zoomInButton->setEnabled(true);
        zoomInButton->show();*/
        refreshPixmap();
    }
}

void Plotter::zoomIn()
{
    if (curZoom < zoomStack.count() - 1) {
        ++curZoom;
//         zoomInButton->setEnabled(curZoom < zoomStack.count() - 1);
//         zoomOutButton->setEnabled(true);
//         zoomOutButton->show();
        refreshPixmap();
    }
}

/**
This method seths the curveData for the plotter
@param id
	Selects what kind of plotting should be used.
	0: normal: just lines connecting the points
	1: bezier: Bezier, Not going through the points but it is realistic plot
	2: cubic splines: Going through the points, but not always realistic plot
@param erease
	when erease is true, the old data (from other ID's) will be deleted
	otherwise, more then one graphs are plotted.
*/
void Plotter::setCurveData(int id, const QVector<QPointF> &data, bool erease)
{
if(id==2){
QwtSpline spline;
spline.setSplineType(QwtSpline::Natural);
QPolygonF points(data);

//tried to lower degree of spline by duplicating points...didn't work
// cout<<"data size: "<<data.size()<<endl;
// QPolygonF points;
// for(int i = 0; i<data.size(); i++){
// 	points<<data.at(i);
// 	points<<data.at(i);
// 	}
// cout<<"points size: "<<points.size()<<endl;

spline.setPoints(points);

QPolygonF interpolatedPoints(NB_SPLINES_INTERPOLATION_POINTS);
double deltatoon = (points[points.size()-1].x() - points[0].x())/(NB_SPLINES_INTERPOLATION_POINTS);
if(deltatoon<0) {deltatoon = - deltatoon;}

for(int i=0; i<NB_SPLINES_INTERPOLATION_POINTS;i++)
	{
	const double x = points[0].x() + i * deltatoon;
	interpolatedPoints[i].setX(x);
	interpolatedPoints[i].setY(spline.value(x));
	}
if(erease){
	curveMap.remove(0);
	curveMap.remove(1);
	}
curveMap[id] = interpolatedPoints;
}

if(id==1){
if(erease){
	curveMap.remove(0);
	curveMap.remove(2);
	}
curveMap[id] = data;
}

if(id==0){
if(erease){
	curveMap.remove(2);
	curveMap.remove(1);
	}
curveMap[id] = data;
}

refreshPixmap();
}

void Plotter::clearCurve(int id)
{
    curveMap.remove(id);
    refreshPixmap();
}

QSize Plotter::minimumSizeHint() const
{
    return QSize(6 * Margin, 4 * Margin);
}

QSize Plotter::sizeHint() const
{
    return QSize(12 * Margin, 8 * Margin);
}

void Plotter::paintEvent(QPaintEvent * /* event */)
{
    QStylePainter painter(this);
    painter.drawPixmap(0, 0, pixmap);

    if (rubberBandIsShown) {
        painter.setPen(palette().light().color());
        painter.drawRect(rubberBandRect.normalized()
                                       .adjusted(0, 0, -1, -1));
    }

    if (hasFocus()) {
        QStyleOptionFocusRect option;
        option.initFrom(this);
        option.backgroundColor = palette().dark().color();
        painter.drawPrimitive(QStyle::PE_FrameFocusRect, option);
    }
}

void Plotter::resizeEvent(QResizeEvent * /* event */)
{
//     int x = width() - (zoomInButton->width()
//                        + zoomOutButton->width() + 10);
//     zoomInButton->move(x, 5);
//     zoomOutButton->move(x + zoomInButton->width() + 5, 5);
    refreshPixmap();
}

// void Plotter::mousePressEvent(QMouseEvent *event)
// {
//     QRect rect(Margin, Margin,
//                width() - 2 * Margin, height() - 2 * Margin);
// 
//     if (event->button() == Qt::LeftButton) {
//         if (rect.contains(event->pos())) {
//             rubberBandIsShown = true;
//             rubberBandRect.setTopLeft(event->pos());
//             rubberBandRect.setBottomRight(event->pos());
//             updateRubberBandRegion();
//             setCursor(Qt::CrossCursor);
//         }
//     }
// }
// 
// void Plotter::mouseMoveEvent(QMouseEvent *event)
// {
//     if (rubberBandIsShown) {
//         updateRubberBandRegion();
//         rubberBandRect.setBottomRight(event->pos());
//         updateRubberBandRegion();
//     }
// }
// 
// void Plotter::mouseReleaseEvent(QMouseEvent *event)
// {
//     if ((event->button() == Qt::LeftButton) && rubberBandIsShown) {
//         rubberBandIsShown = false;
//         updateRubberBandRegion();
//         unsetCursor();
// 
//         QRect rect = rubberBandRect.normalized();
//         if (rect.width() < 4 || rect.height() < 4)
//             return;
//         rect.translate(-Margin, -Margin);
// 
//        PlotSettings prevSettings = zoomStack[curZoom];
//        PlotSettings settings;
//         double dx = prevSettings.spanX() / (width() - 2 * Margin);
//         double dy = prevSettings.spanY() / (height() - 2 * Margin);
//         settings.minX = prevSettings.minX + dx * rect.left();
//         settings.maxX = prevSettings.minX + dx * rect.right();
//         settings.minY = prevSettings.maxY - dy * rect.bottom();
//         settings.maxY = prevSettings.maxY - dy * rect.top();
//         settings.adjust();
// 
//         zoomStack.resize(curZoom + 1);
//         zoomStack.append(settings);
//         zoomIn();
//     }
// }
// 
// void Plotter::keyPressEvent(QKeyEvent *event)
// {
//     switch (event->key()) {
//     case Qt::Key_Plus:
//         zoomIn();
//         break;
//     case Qt::Key_Minus:
//         zoomOut();
//         break;
//     case Qt::Key_Left:
//         zoomStack[curZoom].scroll(-1, 0);
//         refreshPixmap();
//         break;
//     case Qt::Key_Right:
//         zoomStack[curZoom].scroll(+1, 0);
//         refreshPixmap();
//         break;
//     case Qt::Key_Down:
//         zoomStack[curZoom].scroll(0, -1);
//         refreshPixmap();
//         break;
//     case Qt::Key_Up:
//         zoomStack[curZoom].scroll(0, +1);
//         refreshPixmap();
//         break;
//     default:
//         QWidget::keyPressEvent(event);
//     }
// }
// 
// void Plotter::wheelEvent(QWheelEvent *event)
// {
//     int numDegrees = event->delta() / 8;
//     int numTicks = numDegrees / 15;
// 
//     if (event->orientation() == Qt::Horizontal) {
//         zoomStack[curZoom].scroll(numTicks, 0);
//     } else {
//         zoomStack[curZoom].scroll(0, numTicks);
//     }
//     refreshPixmap();
// }

void Plotter::updateRubberBandRegion()
{
    QRect rect = rubberBandRect.normalized();
    update(rect.left(), rect.top(), rect.width(), 1);
    update(rect.left(), rect.top(), 1, rect.height());
    update(rect.left(), rect.bottom(), rect.width(), 1);
    update(rect.right(), rect.top(), 1, rect.height());
}

void Plotter::refreshPixmap()
{
    pixmap = QPixmap(size());
    pixmap.fill(this, 0, 0);

    QPainter painter(&pixmap);
    painter.initFrom(this);
    drawGrid(&painter);
    drawCurves(&painter);
    update();
}

void Plotter::drawGrid(QPainter *painter)
{
    QRect rect(Margin, Margin,
               width() - 2 * Margin, height() - 2 * Margin);
    if (!rect.isValid())
        return;

    PlotSettings settings = zoomStack[curZoom];
    QPen quiteDark = palette().dark().color().light();
    QPen light = palette().light().color();

    for (int i = 0; i <= settings.numXTicks; ++i) {
        int x = rect.left() + (i * (rect.width() - 1)
                                 / settings.numXTicks);
        double label = settings.minX + (i * settings.spanX()
                                          / settings.numXTicks);
        painter->setPen(quiteDark);
        painter->drawLine(x, rect.top(), x, rect.bottom());
        painter->setPen(light);
        painter->drawLine(x, rect.bottom(), x, rect.bottom() + 5);
        painter->drawText(x - 50, rect.bottom() + 5, 100, 15,
                          Qt::AlignHCenter | Qt::AlignTop,
                          QString::number(label));
    }
    for (int j = 0; j <= settings.numYTicks; ++j) {
        int y = rect.bottom() - (j * (rect.height() - 1)
                                   / settings.numYTicks);
        double label = settings.minY + (j * settings.spanY()
                                          / settings.numYTicks);
        painter->setPen(quiteDark);
        painter->drawLine(rect.left(), y, rect.right(), y);
        painter->setPen(light);
        painter->drawLine(rect.left() - 5, y, rect.left(), y);
        painter->drawText(rect.left() - Margin, y - 10, Margin - 5, 20,
                          Qt::AlignRight | Qt::AlignVCenter,
                          QString::number(label));
    }
    painter->drawRect(rect.adjusted(0, 0, -1, -1));
}

/**
Draw the curves
*/
void Plotter::drawCurves(QPainter *painter)
{
	painter->setRenderHint(QPainter::Antialiasing,true);

    static const QColor colorForIds[6] = {Qt::red, Qt::darkGreen, Qt::blue, Qt::cyan, Qt::magenta, Qt::yellow };
    PlotSettings settings = zoomStack[curZoom];
    QRect rect(Margin, Margin, width() - 2 * Margin, height() - 2 * Margin);
    if (!rect.isValid())return;

    painter->setClipRect(rect.adjusted(+1, +1, -1, -1));

    QMapIterator<int, QVector<QPointF> > i(curveMap);
    while (i.hasNext()) {
        i.next();

        int id = i.key();
        const QVector<QPointF> &data = i.value();

	QPolygonF polyline(data.size());
	QPolygon polylineBezier(data.size());
	
        for (int j = 0; j < data.size(); j++) {
	
            double dx = data[j].x() - settings.minX;
            double dy = data[j].y() - settings.minY;

	
            double x = rect.left() + (dx * (rect.width() - 1)
                                         / settings.spanX());
            double y = rect.bottom() - (dy * (rect.height() - 1)
                                           / settings.spanY());
	    if(id!=1){polyline[j] = QPointF(x, y);}
	    if(id==1){polylineBezier[j] = QPoint(x, y);}
        }

        //Painter->setPen(colorForIds[uint(id) % 6]);
	QPen pen;
 	pen.setStyle(Qt::SolidLine);
 	pen.setWidth(3);
	pen.setColor(colorForIds[uint(id) % 6]);
	pen.setJoinStyle(Qt::RoundJoin);
	painter->setOpacity(0.5);
	painter->setPen(pen);
	if(uint(id)!=1){
        painter->drawPolyline(polyline);
	}
	
	if(uint(id)==1){
	int index=0;
	QPainterPath path;
	while(index+1<polylineBezier.size()){
	if(index+4<=polylineBezier.size()){
		path.moveTo(polylineBezier.at(index));
  		path.cubicTo(polylineBezier.at(index+1),polylineBezier.at(index+2),polylineBezier.at(index+3));
		index+=3;
		}
	else if(index+3<=polylineBezier.size()){
		path.moveTo(polylineBezier.at(index));
		path.quadTo(polylineBezier.at(index+1), polylineBezier.at(index+2));
		index+=2;
		}
	else
		{
		path.moveTo(polylineBezier.at(index));
		path.lineTo(polylineBezier.at(index+1));
		index++;
		}
	}
  	painter->strokePath(path, pen);	
	}

    }
}

 PlotSettings::PlotSettings()
 {
     minX = 0.0;
     maxX = 10.0;
     numXTicks = 5;

     minY = 0.0;
     maxY = 10.0;
     numYTicks = 5;
 }

void PlotSettings::scroll(int dx, int dy)
{
    double stepX = spanX() / numXTicks;
    minX += dx * stepX;
    maxX += dx * stepX;

    double stepY = spanY() / numYTicks;
    minY += dy * stepY;
    maxY += dy * stepY;
}

void PlotSettings::adjust()
{
    adjustAxis(minX, maxX, numXTicks);
    adjustAxis(minY, maxY, numYTicks);
}

void PlotSettings::adjustAxis(double &min, double &max,
                              int &numTicks)
{
    const int MinTicks = 4;
    double grossStep = (max - min) / MinTicks;
	if(grossStep <=0) {grossStep = 1;}
    double step = pow(10.0, floor(log10(grossStep)));

    if (5 * step < grossStep) {
        step *= 5;
    } else if (2 * step < grossStep) {
        step *= 2;
    }

    numTicks = int(ceil(max / step) - floor(min / step));
    if (numTicks < MinTicks)
        numTicks = MinTicks;
    min = floor(min / step) * step;
    max = ceil(max / step) * step;
	if(max == min) {max = min+1;}
}
