/*
  Copyright © 2017 Hasan Yavuz Özderya
  This file is part of serialplot.
  serialplot 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 3 of the License, or
  (at your option) any later version.
  serialplot 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 serialplot.  If not, see .
*/
#include 
#include 
#include 
#include 
#include "qwt_symbol.h"
#include "plot.h"
#include "plotmanager.h"
#include "utils.h"
#include "setting_defines.h"
PlotManager::PlotManager(QWidget* plotArea, ChannelInfoModel* infoModel, QObject *parent) :
    QObject(parent),
    _plotArea(plotArea),
    showGridAction("&Grid", this),
    showMinorGridAction("&Minor Grid", this),
    unzoomAction("&Unzoom", this),
    darkBackgroundAction("&Dark Background", this),
    showLegendAction("&Legend", this),
    showMultiAction("Multi &Plot", this),
    setSymbolsAction("Symbols", this)
{
    _autoScaled = true;
    _yMin = 0;
    _yMax = 1;
    _xAxisAsIndex = true;
    isDemoShown = false;
    _infoModel = infoModel;
    _numOfSamples = 1;
    showSymbols = Plot::ShowSymbolsAuto;
    emptyPlot = NULL;
    // initalize layout and single widget
    isMulti = false;
    scrollArea = NULL;
    setupLayout(isMulti);
    addPlotWidget();
    // initialize menu actions
    showGridAction.setToolTip("Show Grid");
    showMinorGridAction.setToolTip("Show Minor Grid");
    unzoomAction.setToolTip("Unzoom the Plot");
    darkBackgroundAction.setToolTip("Enable Dark Plot Background");
    showLegendAction.setToolTip("Display the Legend on Plot");
    showMultiAction.setToolTip("Display All Channels Separately");
    setSymbolsAction.setToolTip("Show/Hide symbols");
    showGridAction.setShortcut(QKeySequence("G"));
    showMinorGridAction.setShortcut(QKeySequence("M"));
    showGridAction.setCheckable(true);
    showMinorGridAction.setCheckable(true);
    darkBackgroundAction.setCheckable(true);
    showLegendAction.setCheckable(true);
    showMultiAction.setCheckable(true);
    showGridAction.setChecked(false);
    showMinorGridAction.setChecked(false);
    darkBackgroundAction.setChecked(false);
    showLegendAction.setChecked(true);
    showMultiAction.setChecked(false);
    showMinorGridAction.setEnabled(false);
    // setup symbols menu
    setSymbolsAutoAct = setSymbolsMenu.addAction("Show When Zoomed");
    setSymbolsAutoAct->setCheckable(true);
    setSymbolsAutoAct->setChecked(true);
    connect(setSymbolsAutoAct, SELECT::OVERLOAD_OF(&QAction::triggered),
            [this](bool checked)
            {
                if (checked) setSymbols(Plot::ShowSymbolsAuto);
            });
    setSymbolsShowAct = setSymbolsMenu.addAction("Always Show");
    setSymbolsShowAct->setCheckable(true);
    connect(setSymbolsShowAct, SELECT::OVERLOAD_OF(&QAction::triggered),
            [this](bool checked)
            {
                if (checked) setSymbols(Plot::ShowSymbolsShow);
            });
    setSymbolsHideAct = setSymbolsMenu.addAction("Always Hide");
    setSymbolsHideAct->setCheckable(true);
    connect(setSymbolsHideAct, SELECT::OVERLOAD_OF(&QAction::triggered),
            [this](bool checked)
            {
                if (checked) setSymbols(Plot::ShowSymbolsHide);
            });
    setSymbolsAction.setMenu(&setSymbolsMenu);
    // add symbol actions to same group so that they appear as radio buttons
    auto group = new QActionGroup(this);
    group->addAction(setSymbolsAutoAct);
    group->addAction(setSymbolsShowAct);
    group->addAction(setSymbolsHideAct);
    connect(&showGridAction, SELECT::OVERLOAD_OF(&QAction::triggered),
            this, &PlotManager::showGrid);
    connect(&showGridAction, SELECT::OVERLOAD_OF(&QAction::triggered),
            &showMinorGridAction, &QAction::setEnabled);
    connect(&showMinorGridAction, SELECT::OVERLOAD_OF(&QAction::triggered),
            this, &PlotManager::showMinorGrid);
    connect(&unzoomAction, &QAction::triggered, this, &PlotManager::unzoom);
    connect(&darkBackgroundAction, SELECT::OVERLOAD_OF(&QAction::triggered),
            this, &PlotManager::darkBackground);
    connect(&showLegendAction, SELECT::OVERLOAD_OF(&QAction::triggered),
            this, &PlotManager::showLegend);
    connect(&showLegendAction, SELECT::OVERLOAD_OF(&QAction::triggered),
            this, &PlotManager::showLegend);
    connect(&showMultiAction, SELECT::OVERLOAD_OF(&QAction::triggered),
            this, &PlotManager::setMulti);
    // connect to channel info model
    if (_infoModel != NULL)     // TODO: remove when snapshots have infomodel
    {
        connect(_infoModel, &QAbstractItemModel::dataChanged,
                this, &PlotManager::onChannelInfoChanged);
        connect(_infoModel, &QAbstractItemModel::modelReset,
                [this]()
                {
                    onChannelInfoChanged(_infoModel->index(0, 0), // start
                                         _infoModel->index(_infoModel->rowCount()-1, 0), // end
                                         {}); // roles ignored
                });
    }
}
PlotManager::~PlotManager()
{
    while (curves.size())
    {
        delete curves.takeLast();
    }
    // remove all widgets
    while (plotWidgets.size())
    {
        delete plotWidgets.takeLast();
    }
    if (scrollArea != NULL) delete scrollArea;
    if (emptyPlot != NULL) delete emptyPlot;
}
void PlotManager::onChannelInfoChanged(const QModelIndex &topLeft,
                                       const QModelIndex &bottomRight,
                                       const QVector &roles)
{
    int start = topLeft.row();
    int end = bottomRight.row();
    for (int ci = start; ci <= end; ci++)
    {
        QString name = topLeft.sibling(ci, ChannelInfoModel::COLUMN_NAME).data(Qt::EditRole).toString();
        QColor color = topLeft.sibling(ci, ChannelInfoModel::COLUMN_NAME).data(Qt::ForegroundRole).value();
        bool visible = topLeft.sibling(ci, ChannelInfoModel::COLUMN_VISIBILITY).data(Qt::CheckStateRole).toBool();
        curves[ci]->setTitle(name);
        curves[ci]->setPen(color);
        curves[ci]->setVisible(visible);
        curves[ci]->setItemAttribute(QwtPlotItem::Legend, visible);
        // replot only updated widgets
        if (isMulti)
        {
            plotWidgets[ci]->updateSymbols(); // required for color change
            plotWidgets[ci]->updateLegend(curves[ci]);
            plotWidgets[ci]->setVisible(visible);
            if (visible)
            {
                plotWidgets[ci]->replot();
            }
        }
    }
    checkNoVisChannels();
    // replot single widget
    if (!isMulti)
    {
        plotWidgets[0]->updateSymbols();
        plotWidgets[0]->updateLegend();
        replot();
    }
}
void PlotManager::checkNoVisChannels()
{
    // if all channels are hidden show indicator
    bool allhidden = std::none_of(curves.cbegin(), curves.cend(),
                                  [](QwtPlotCurve* c) {return c->isVisible();});
    plotWidgets[0]->showNoChannel(allhidden);
    if (isMulti)
    {
        plotWidgets[0]->showNoChannel(allhidden);
        plotWidgets[0]->setVisible(true);
    }
}
void PlotManager::setMulti(bool enabled)
{
    if (enabled == isMulti) return;
    isMulti = enabled;
    // detach all curves
    for (auto curve : curves)
    {
        curve->detach();
    }
    // remove all widgets
    while (plotWidgets.size())
    {
        delete plotWidgets.takeLast();
    }
    // setup new layout
    setupLayout(isMulti);
    if (isMulti)
    {
        // add new widgets and attach
        for (auto curve : curves)
        {
            auto plot = addPlotWidget();
            plot->setVisible(curve->isVisible());
            curve->attach(plot);
        }
    }
    else
    {
        // add a single widget
        auto plot = addPlotWidget();
        // attach all curves
        for (auto curve : curves)
        {
            curve->attach(plot);
        }
    }
    checkNoVisChannels();
}
void PlotManager::setupLayout(bool multiPlot)
{
    // delete previous layout if it exists
    if (_plotArea->layout() != 0)
    {
        delete _plotArea->layout();
    }
    if (multiPlot)
    {
        // setup a scroll area
        scrollArea = new QScrollArea();
        auto scrolledPlotArea = new QWidget(scrollArea);
        scrollArea->setWidget(scrolledPlotArea);
        scrollArea->setWidgetResizable(true);
        _plotArea->setLayout(new QVBoxLayout());
        _plotArea->layout()->addWidget(scrollArea);
        _plotArea->layout()->setContentsMargins(0,0,0,0);
        layout = new QVBoxLayout(scrolledPlotArea);
    }
    else
    {
        // delete scrollArea left from multi layout
        if (scrollArea != NULL)
        {
            delete scrollArea;
            scrollArea = NULL;
        }
        layout = new QVBoxLayout(_plotArea);
    }
    layout->setContentsMargins(2,2,2,2);
    layout->setSpacing(1);
}
Plot* PlotManager::addPlotWidget()
{
    auto plot = new Plot();
    plotWidgets.append(plot);
    layout->addWidget(plot);
    plot->darkBackground(darkBackgroundAction.isChecked());
    plot->showGrid(showGridAction.isChecked());
    plot->showMinorGrid(showMinorGridAction.isChecked());
    plot->showLegend(showLegendAction.isChecked());
    plot->showDemoIndicator(isDemoShown);
    plot->setYAxis(_autoScaled, _yMin, _yMax);
    plot->setNumOfSamples(_numOfSamples);
    plot->setSymbols(showSymbols);
    if (_xAxisAsIndex)
    {
        plot->setXAxis(0, _numOfSamples);
    }
    else
    {
        plot->setXAxis(_xMin, _xMax);
    }
    return plot;
}
void PlotManager::addCurve(QString title, FrameBuffer* buffer)
{
    auto curve = new QwtPlotCurve(title);
    auto series = new FrameBufferSeries(buffer);
    series->setXAxis(_xAxisAsIndex, _xMin, _xMax);
    curve->setSamples(series);
    _addCurve(curve);
}
void PlotManager::addCurve(QString title, QVector data)
{
    auto curve = new QwtPlotCurve(title);
    curve->setSamples(data);
    _addCurve(curve);
}
void PlotManager::_addCurve(QwtPlotCurve* curve)
{
    // store and init the curve
    curves.append(curve);
    unsigned index = curves.size()-1;
    auto color = _infoModel->color(index);
    curve->setPen(color);
    // create the plot for the curve if we are on multi display
    Plot* plot;
    if (isMulti)
    {
        // create a new plot widget
        plot = addPlotWidget();
    }
    else
    {
        plot = plotWidgets[0];
    }
    // show the curve
    curve->attach(plot);
    plot->replot();
}
void PlotManager::removeCurves(unsigned number)
{
    for (unsigned i = 0; i < number; i++)
    {
        if (!curves.isEmpty())
        {
            delete curves.takeLast();
            if (isMulti) // delete corresponding widget as well
            {
                delete plotWidgets.takeLast();
            }
        }
    }
}
unsigned PlotManager::numOfCurves()
{
    return curves.size();
}
void PlotManager::setTitle(unsigned index, QString title)
{
    curves[index]->setTitle(title);
    plotWidget(index)->replot();
}
Plot* PlotManager::plotWidget(unsigned curveIndex)
{
    if (isMulti)
    {
        return plotWidgets[curveIndex];
    }
    else
    {
        return plotWidgets[0];
    }
}
void PlotManager::replot()
{
    for (auto plot : plotWidgets)
    {
        plot->replot();
    }
}
QList PlotManager::menuActions()
{
    QList actions;
    actions << &showGridAction;
    actions << &showMinorGridAction;
    actions << &unzoomAction;
    actions << &darkBackgroundAction;
    actions << &showLegendAction;
    actions << &showMultiAction;
    actions << &setSymbolsAction;
    return actions;
}
void PlotManager::showGrid(bool show)
{
    for (auto plot : plotWidgets)
    {
        plot->showGrid(show);
    }
}
void PlotManager::showMinorGrid(bool show)
{
    for (auto plot : plotWidgets)
    {
        plot->showMinorGrid(show);
    }
}
void PlotManager::showLegend(bool show)
{
    for (auto plot : plotWidgets)
    {
        plot->showLegend(show);
    }
}
void PlotManager::showDemoIndicator(bool show)
{
    isDemoShown = show;
    for (auto plot : plotWidgets)
    {
        plot->showDemoIndicator(show);
    }
}
void PlotManager::unzoom()
{
    for (auto plot : plotWidgets)
    {
        plot->unzoom();
    }
}
void PlotManager::darkBackground(bool enabled)
{
    for (auto plot : plotWidgets)
    {
        plot->darkBackground(enabled);
    }
}
void PlotManager::setSymbols(Plot::ShowSymbols shown)
{
    showSymbols = shown;
    for (auto plot : plotWidgets)
    {
        plot->setSymbols(shown);
    }
}
void PlotManager::setYAxis(bool autoScaled, double yAxisMin, double yAxisMax)
{
    _autoScaled = autoScaled;
    _yMin = yAxisMin;
    _yMax = yAxisMax;
    for (auto plot : plotWidgets)
    {
        plot->setYAxis(autoScaled, yAxisMin, yAxisMax);
    }
}
void PlotManager::setXAxis(bool asIndex, double xMin, double xMax)
{
    _xAxisAsIndex = asIndex;
    _xMin = xMin;
    _xMax = xMax;
    for (auto curve : curves)
    {
        // TODO: what happens when addCurve(QVector) is used?
        FrameBufferSeries* series = static_cast(curve->data());
        series->setXAxis(asIndex, xMin, xMax);
    }
    for (auto plot : plotWidgets)
    {
        if (asIndex)
        {
            plot->setXAxis(0, _numOfSamples);
        }
        else
        {
            plot->setXAxis(xMin, xMax);
        }
    }
    replot();
}
void PlotManager::flashSnapshotOverlay()
{
    for (auto plot : plotWidgets)
    {
        plot->flashSnapshotOverlay(darkBackgroundAction.isChecked());
    }
}
void PlotManager::setNumOfSamples(unsigned value)
{
    _numOfSamples = value;
    for (auto plot : plotWidgets)
    {
        plot->setNumOfSamples(value);
        if (_xAxisAsIndex) plot->setXAxis(0, value);
    }
}
PlotViewSettings PlotManager::viewSettings() const
{
    return PlotViewSettings(
        {
            showGridAction.isChecked(),
            showMinorGridAction.isChecked(),
            darkBackgroundAction.isChecked(),
            showLegendAction.isChecked(),
            showMultiAction.isChecked(),
            showSymbols
        });
}
void PlotManager::setViewSettings(const PlotViewSettings& settings)
{
    showGridAction.setChecked(settings.showGrid);
    showGrid(settings.showGrid);
    showMinorGridAction.setChecked(settings.showMinorGrid);
    showMinorGrid(settings.showMinorGrid);
    darkBackgroundAction.setChecked(settings.darkBackground);
    darkBackground(settings.darkBackground);
    showLegendAction.setChecked(settings.showLegend);
    showLegend(settings.showLegend);
    showMultiAction.setChecked(settings.showMulti);
    setMulti(settings.showMulti);
    setSymbols(settings.showSymbols);
    if (showSymbols == Plot::ShowSymbolsAuto)
    {
        setSymbolsAutoAct->setChecked(true);
    }
    else if (showSymbols == Plot::ShowSymbolsShow)
    {
        setSymbolsShowAct->setChecked(true);
    }
    else
    {
        setSymbolsHideAct->setChecked(true);
    }
}
void PlotManager::saveSettings(QSettings* settings)
{
    settings->beginGroup(SettingGroup_Plot);
    settings->setValue(SG_Plot_DarkBackground, darkBackgroundAction.isChecked());
    settings->setValue(SG_Plot_Grid, showGridAction.isChecked());
    settings->setValue(SG_Plot_MinorGrid, showMinorGridAction.isChecked());
    settings->setValue(SG_Plot_Legend, showLegendAction.isChecked());
    settings->setValue(SG_Plot_MultiPlot, showMultiAction.isChecked());
    QString showSymbolsStr;
    if (showSymbols == Plot::ShowSymbolsAuto)
    {
        showSymbolsStr = "auto";
    }
    else if (showSymbols == Plot::ShowSymbolsShow)
    {
        showSymbolsStr = "show";
    }
    else
    {
        showSymbolsStr = "hide";
    }
    settings->setValue(SG_Plot_Symbols, showSymbolsStr);
    settings->endGroup();
}
void PlotManager::loadSettings(QSettings* settings)
{
    settings->beginGroup(SettingGroup_Plot);
    darkBackgroundAction.setChecked(
        settings->value(SG_Plot_DarkBackground, darkBackgroundAction.isChecked()).toBool());
    darkBackground(darkBackgroundAction.isChecked());
    showGridAction.setChecked(
        settings->value(SG_Plot_Grid, showGridAction.isChecked()).toBool());
    showGrid(showGridAction.isChecked());
    showMinorGridAction.setChecked(
        settings->value(SG_Plot_MinorGrid, showMinorGridAction.isChecked()).toBool());
    showMinorGridAction.setEnabled(showGridAction.isChecked());
    showMinorGrid(showMinorGridAction.isChecked());
    showLegendAction.setChecked(
        settings->value(SG_Plot_Legend, showLegendAction.isChecked()).toBool());
    showLegend(showLegendAction.isChecked());
    showMultiAction.setChecked(
        settings->value(SG_Plot_MultiPlot, showMultiAction.isChecked()).toBool());
    setMulti(showMultiAction.isChecked());
    QString showSymbolsStr = settings->value(SG_Plot_Symbols, QString()).toString();
    if (showSymbolsStr == "auto")
    {
        setSymbols(Plot::ShowSymbolsAuto);
        setSymbolsAutoAct->setChecked(true);
    }
    else if (showSymbolsStr == "show")
    {
        setSymbols(Plot::ShowSymbolsShow);
        setSymbolsShowAct->setChecked(true);
    }
    else if (showSymbolsStr == "hide")
    {
        setSymbols(Plot::ShowSymbolsHide);
        setSymbolsHideAct->setChecked(true);
    }
    else
    {
        qCritical() << "Invalid symbol setting:" << showSymbolsStr;
    }
    settings->endGroup();
}