#include "chartgraph.h"
#include <QChartView>
#include <QVBoxLayout>
#include <QLineSeries>
#include <QBarSeries>
#include <QBarSet>
#include <QBarCategoryAxis>
#include <QScatterSeries>
#include <QMenu>
#include <QMenuBar>
#include <QValueAxis>
#include <QFileDialog>
#include <QSettings>
#include <QToolBar>
#include <QStyle>

class ChartView : public QChartView
{
    QPointF _mousePos;
    bool _scroll = false;
public:
    ChartView(QWidget *parent) : QChartView(parent)
    {
    }
protected:
    void keyPressEvent(QKeyEvent *event) override
    {
        if(!chart()->isZoomed())chart()->zoom(0.999999);//workaround so zoomReset() reset scroll
        switch(event->key())
        {
        case Qt::Key_Plus:
            chart()->zoomIn();
            break;
        case Qt::Key_Minus:
            chart()->zoomOut();
            break;
        case Qt::Key_Left:
            chart()->scroll(-10, 0);
            break;
        case Qt::Key_Right:
            chart()->scroll(10, 0);
            break;
        case Qt::Key_Up:
            chart()->scroll(0, 10);
            break;
        case Qt::Key_Down:
            chart()->scroll(0, -10);
            break;
        default:
            QGraphicsView::keyPressEvent(event);
            break;
        }
    }
    void mousePressEvent(QMouseEvent *event) override
    {
        if(event->button() == Qt::LeftButton)
        {
            _scroll = true;
            if(!chart()->isZoomed())chart()->zoom(0.999999);//workaround so zoomReset() reset scroll
            _mousePos = event->position();
        }

        QChartView::mousePressEvent(event);
    }
    void mouseMoveEvent(QMouseEvent *event) override
    {
        if(_scroll)
        {
            QPointF pos = event->position();
            chart()->scroll(_mousePos.x() - pos.x(), pos.y() - _mousePos.y());
            _mousePos = pos;
        }
        QChartView::mouseMoveEvent(event);
    }
    void mouseReleaseEvent(QMouseEvent *event) override
    {
        _scroll = false;
        QChartView::mouseReleaseEvent(event);
    }
    void wheelEvent(QWheelEvent *event) override
    {
        if(event->angleDelta().y() > 0)
            chart()->zoomIn();
        if(event->angleDelta().y() < 0)
            chart()->zoomOut();
    }
};

ChartGraph::ChartGraph(QWidget *parent) : QMainWindow(parent)
{
    setAttribute(Qt::WA_DeleteOnClose);
    setWindowTitle(tr("Chart"));

    _chartView = new ChartView(this);
    setCentralWidget(_chartView);

    _chart = new QChart;
    _chartView->setChart(_chart);
    _chartView->setRenderHint(QPainter::Antialiasing);
    resize(1024, 768);

    menuBar()->addAction(tr("Save"), this, &ChartGraph::save);
    menuBar()->addAction(tr("Reset view"), [this](){ _chart->zoomReset(); });
}

void ChartGraph::plot(const QVariant &graph)
{
    QVariantMap map = graph.toMap();

    _chart->setTitle(map["title"].toString());
    if(map.contains("legend"))
    {
        QVariantMap legend = map["legend"].toMap();
        if(legend.contains("visible"))
            _chart->legend()->setVisible(legend["visible"].toBool());

        QString align = legend["align"].toString();
        if(align == "top")
            _chart->legend()->setAlignment(Qt::AlignTop);
        else if(align == "left")
            _chart->legend()->setAlignment(Qt::AlignLeft);
        else if(align == "bottom")
            _chart->legend()->setAlignment(Qt::AlignBottom);
        else if(align == "right")
            _chart->legend()->setAlignment(Qt::AlignRight);
    }

    QBarSeries *barSeries = nullptr;

    qreal minX = INFINITY;
    qreal maxX = -INFINITY;
    qreal minY = INFINITY;
    qreal maxY = -INFINITY;
    qreal minY2 = INFINITY;
    qreal maxY2 = -INFINITY;

    QValueAxis *xaxis = new QValueAxis(_chart);
    QBarCategoryAxis *barxaxis = new QBarCategoryAxis(_chart);
    QValueAxis *yaxis = new QValueAxis(_chart);
    QValueAxis *y2axis = new QValueAxis(_chart);
    _chart->addAxis(xaxis, Qt::AlignBottom);
    _chart->addAxis(yaxis, Qt::AlignLeft);
    _chart->addAxis(y2axis, Qt::AlignRight);
    _chart->addAxis(barxaxis, Qt::AlignBottom);
    y2axis->setGridLinePen(Qt::DashDotLine);

    for(auto s : map["series"].toList())
    {
        QVariantMap serie = s.toMap();
        QString type = serie["type"].toString();
        bool y2 = serie["y2"].toBool();

        if(type == "line" || type == "points" || type == "linePoints" || type.isEmpty())
        {
            QXYSeries *series = nullptr;
            if(type == "points")
            {
                QScatterSeries *scatter = new QScatterSeries(_chart);
                series = scatter;
                QString shape = serie["shape"].toString();
                if(shape == "circle")scatter->setMarkerShape(QScatterSeries::MarkerShapeCircle);
                else if(shape == "rectangle")scatter->setMarkerShape(QScatterSeries::MarkerShapeRectangle);
                else if(shape == "triangle")scatter->setMarkerShape(QScatterSeries::MarkerShapeTriangle);
                else if(shape == "star")scatter->setMarkerShape(QScatterSeries::MarkerShapeStar);
                else if(shape == "pentagon")scatter->setMarkerShape(QScatterSeries::MarkerShapePentagon);
            }
            else
            {
                series = new QLineSeries(_chart);
            }

            series->setName(serie["title"].toString());
            QVariantList x = serie["x"].toList();
            QVariantList y = serie["y"].toList();
            if(x.isEmpty())
            {
                for(int i = 0; i < y.size(); i++)
                {
                    qreal val = y[i].toDouble();
                    if(y2)
                    {
                        minY2 = std::min(minY2, val);
                        maxY2 = std::max(maxY2, val);
                    }
                    else
                    {
                        minY = std::min(minY, val);
                        maxY = std::max(maxY, val);
                    }
                    series->append(i, val);
                }
                minX = std::min(minX, 0.0);
                maxX = std::max(maxX, y.size() - 1.0);
            }
            else
            {
                int size = std::min(x.size(), y.size());
                for(int i = 0; i < size; i++)
                {
                    qreal val = y[i].toDouble();
                    if(y2)
                    {
                        minY2 = std::min(minY2, val);
                        maxY2 = std::max(maxY2, val);
                    }
                    else
                    {
                        minY = std::min(minY, val);
                        maxY = std::max(maxY, val);
                    }
                    minX = std::min(minX, x[i].toDouble());
                    maxX = std::max(maxX, x[i].toDouble());
                    series->append(x[i].toDouble(), val);
                }
            }

            _chart->addSeries(series);
            series->attachAxis(xaxis);
            series->attachAxis(y2 ? y2axis : yaxis);

            if(serie.contains("color"))
            {
                QString color = serie["color"].toString();
                if(QColor::isValidColorName(color))series->setColor(QColor::fromString(color));
            }

            if(serie["bestFit"].toBool())
            {
                series->setBestFitLineVisible(true);
                QPen pen = series->bestFitLinePen();
                pen.setColor(series->color());
                pen.setStyle(Qt::DashLine);
                series->setBestFitLinePen(pen);
            }

            if(type == "linePoints")
                series->setPointsVisible(true);

        }
        else if(type == "bar")
        {
            if(!barSeries)
            {
                barSeries = new QBarSeries(_chart);
                _chart->addSeries(barSeries);
                barSeries->attachAxis(yaxis);
                barSeries->attachAxis(barxaxis);
            }
            QBarSet *set = new QBarSet(serie["title"].toString());
            QVariantList y = serie["y"].toList();
            for(int i = 0; i < y.size(); i++)
            {
                qreal val = y[i].toDouble();
                minY = std::min(minY, val);
                maxY = std::max(maxY, val);
                set->append(val);
            }

            barSeries->append(set);
            for(int i = barxaxis->count() + 1; i <= y.size(); i++)
                barxaxis->append(QString::number(i));

            if(serie.contains("color"))
            {
                QString color = serie["color"].toString();
                if(QColor::isValidColorName(color))set->setColor(QColor::fromString(color));
            }
        }
    }

    if(barSeries)
    {
        xaxis->setRange(std::min(minX, -0.5), std::max(maxX, barxaxis->count() - 0.5));
        minY = std::min(minY, 0.0);
    }
    else
    {
        xaxis->setRange(minX, maxX);
    }

    yaxis->setRange(minY, maxY);
    y2axis->setRange(minY2, maxY2);

    show();
}

void ChartGraph::save()
{
    QSettings settings;
    QString dir = settings.value("mainwindow/lastdir").toString();
    QString output = QFileDialog::getSaveFileName(this, tr("Save as"), dir, "PNG (*.png)");

    if(!output.isEmpty())
    {
        QPixmap graph = _chartView->grab();
        graph.toImage().save(output);
    }
}
