/*
  Copyright © 2019 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 
#include 
#include "plotcontrolpanel.h"
#include "ui_plotcontrolpanel.h"
#include "setting_defines.h"
/// Confirm if #samples is being set to a value greater than this
const int NUMSAMPLES_CONFIRM_AT = 1000000;
/// Precision used for channel info table numbers
const int DOUBLESP_PRECISION = 6;
/// Used for scale range selection combobox
struct Range
{
    double rmin;
    double rmax;
};
Q_DECLARE_METATYPE(Range);
/// Used for customizing double precision in tables
class SpinBoxDelegate : public QStyledItemDelegate
{
public:
    QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                          const QModelIndex &index) const Q_DECL_OVERRIDE
        {
            auto w = QStyledItemDelegate::createEditor(
                parent, option, index);
            auto sp = qobject_cast(w);
            if (sp)
            {
                sp->setDecimals(DOUBLESP_PRECISION);
            }
            return w;
        }
};
PlotControlPanel::PlotControlPanel(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::PlotControlPanel),
    resetAct(tr("Reset"), this),
    resetNamesAct(tr("Reset Names"), this),
    resetColorsAct(tr("Reset Colors"), this),
    showAllAct(tr("Show All"), this),
    hideAllAct(tr("Hide All"), this),
    resetGainsAct(tr("Reset All Gain"), this),
    resetOffsetsAct(tr("Reset All Offset"), this),
    resetMenu(tr("Reset Menu"), this)
{
    ui->setupUi(this);
    delegate = new SpinBoxDelegate();
    ui->tvChannelInfo->setItemDelegate(delegate);
    warnNumOfSamples = true;    // TODO: load from settings
    _numOfSamples = ui->spNumOfSamples->value();
    // set limits for axis limit boxes
    ui->spYmin->setRange((-1) * std::numeric_limits::max(),
                         std::numeric_limits::max());
    ui->spYmax->setRange((-1) * std::numeric_limits::max(),
                         std::numeric_limits::max());
    ui->spXmin->setRange((-1) * std::numeric_limits::max(),
                         std::numeric_limits::max());
    ui->spXmax->setRange((-1) * std::numeric_limits::max(),
                         std::numeric_limits::max());
    // connect signals
    connect(ui->spNumOfSamples, SIGNAL(valueChanged(int)),
            this, SLOT(onNumOfSamples(int)));
    connect(ui->cbAutoScale, &QCheckBox::toggled,
            this, &PlotControlPanel::onAutoScaleChecked);
    connect(ui->spYmax, SIGNAL(valueChanged(double)),
            this, SLOT(onYScaleChanged()));
    connect(ui->spYmin, SIGNAL(valueChanged(double)),
            this, SLOT(onYScaleChanged()));
    connect(ui->cbIndex, &QCheckBox::toggled,
            this, &PlotControlPanel::onIndexChecked);
    connect(ui->spXmax, SIGNAL(valueChanged(double)),
            this, SLOT(onXScaleChanged()));
    connect(ui->spXmax, static_cast(&QDoubleSpinBox::valueChanged),
            [this](double v)
            {
                // set limit just a little below
                double step = pow(10, -1 * ui->spXmin->decimals());
                ui->spXmin->setMaximum(v - step);
            });
    connect(ui->spXmin, SIGNAL(valueChanged(double)),
            this, SLOT(onXScaleChanged()));
    connect(ui->spXmin, static_cast(&QDoubleSpinBox::valueChanged),
            [this](double v)
            {
                // set limit just a little above
                double step = pow(10, -1 * ui->spXmax->decimals());
                ui->spXmax->setMinimum(v + step);
            });
    connect(ui->spPlotWidth, SIGNAL(valueChanged(int)),
            this, SLOT(onPlotWidthChanged()));
    ui->cbRangePresets->addItem("-100 to +100", QVariant::fromValue(Range{-100, +100}));
    ui->cbRangePresets->addItem("0 to +100", QVariant::fromValue(Range{0, +100}));
    QObject::connect(ui->cbRangePresets, SIGNAL(activated(int)),
                     this, SLOT(onRangeSelected()));
    // color selector starts disabled until a channel is selected
    ui->pbColorSel->setDisabled(true);
    setSelectorColor(QColor(0,0,0,0));
    connect(ui->pbColorSel, &QPushButton::clicked, this, &PlotControlPanel::onColorSelect);
    // reset buttons
    resetAct.setToolTip(tr("Reset channel names and colors"));
    resetMenu.addAction(&resetNamesAct);
    resetMenu.addAction(&resetColorsAct);
    resetMenu.addAction(&resetGainsAct);
    resetMenu.addAction(&resetOffsetsAct);
    resetAct.setMenu(&resetMenu);
    ui->tbReset->setDefaultAction(&resetAct);
    showAllAct.setToolTip(tr("Show all channels"));
    hideAllAct.setToolTip(tr("Hide all channels"));
    ui->tbShowAll->setDefaultAction(&showAllAct);
    ui->tbHideAll->setDefaultAction(&hideAllAct);
}
PlotControlPanel::~PlotControlPanel()
{
    delete ui;
}
unsigned PlotControlPanel::numOfSamples()
{
    return ui->spNumOfSamples->value();
}
void PlotControlPanel::onNumOfSamples(int value)
{
    if (warnNumOfSamples && value > NUMSAMPLES_CONFIRM_AT)
    {
        // ask confirmation
        if (!askNSConfirmation(value))
        {
            // revert to old value
            disconnect(ui->spNumOfSamples, SIGNAL(valueChanged(int)),
                       this, SLOT(onNumOfSamples(int)));
            ui->spNumOfSamples->setValue(_numOfSamples);
            connect(ui->spNumOfSamples, SIGNAL(valueChanged(int)),
                    this, SLOT(onNumOfSamples(int)));
            return;
        }
    }
    _numOfSamples = value;
    emit numOfSamplesChanged(value);
}
bool PlotControlPanel::askNSConfirmation(int value)
{
    auto text = tr("Setting number of samples to a too big value "
                   "(>%1) can seriously impact the performance of "
                   "the application and cause freezes. Are you sure you "
                   "want to change the number of samples to %2?")
        .arg(QString::number(NUMSAMPLES_CONFIRM_AT), QString::number(value));
    // TODO: parent the mainwindow
    QMessageBox mb(QMessageBox::Warning,
                   tr("Confirm Number of Samples"),
                   text,
                   QMessageBox::Apply | QMessageBox::Cancel,
                   this);
    auto cb = new QCheckBox("Don't show this again.");
    connect(cb, &QCheckBox::stateChanged, [this](int state)
            {
                warnNumOfSamples = (state == Qt::Unchecked);
            });
    mb.setCheckBox(cb);
    return mb.exec() == QMessageBox::Apply;
}
void PlotControlPanel::setSelectorColor(QColor color)
{
    ui->pbColorSel->setStyleSheet(QString("background-color: %1;").arg(color.name()));
}
void PlotControlPanel::onColorSelect()
{
    auto selection = ui->tvChannelInfo->selectionModel()->currentIndex();
    // no selection
    if (!selection.isValid()) return;
    // current color
    auto model = ui->tvChannelInfo->model();
    QColor color = model->data(selection, Qt::ForegroundRole).value();
    // show dialog
    color = QColorDialog::getColor(color, this);
    if (color.isValid())        // color is set to invalid if user cancels
    {
        ui->tvChannelInfo->model()->setData(selection, color, Qt::ForegroundRole);
    }
}
void PlotControlPanel::onAutoScaleChecked(bool checked)
{
    if (checked)
    {
        ui->lYmin->setEnabled(false);
        ui->lYmax->setEnabled(false);
        ui->spYmin->setEnabled(false);
        ui->spYmax->setEnabled(false);
        emit yScaleChanged(true); // autoscale
    }
    else
    {
        ui->lYmin->setEnabled(true);
        ui->lYmax->setEnabled(true);
        ui->spYmin->setEnabled(true);
        ui->spYmax->setEnabled(true);
        emit yScaleChanged(false, ui->spYmin->value(), ui->spYmax->value());
    }
}
void PlotControlPanel::onYScaleChanged()
{
    if (!autoScale())
    {
        emit yScaleChanged(false, ui->spYmin->value(), ui->spYmax->value());
    }
}
bool PlotControlPanel::autoScale() const
{
    return ui->cbAutoScale->isChecked();
}
double PlotControlPanel::yMax() const
{
    return ui->spYmax->value();
}
double PlotControlPanel::yMin() const
{
    return ui->spYmin->value();
}
bool PlotControlPanel::xAxisAsIndex() const
{
    return ui->cbIndex->isChecked();
}
double PlotControlPanel::xMax() const
{
    return ui->spXmax->value();
}
double PlotControlPanel::xMin() const
{
    return ui->spXmin->value();
}
void PlotControlPanel::onRangeSelected()
{
    Range r = ui->cbRangePresets->currentData().value();
    ui->spYmin->setValue(r.rmin);
    ui->spYmax->setValue(r.rmax);
    ui->cbAutoScale->setChecked(false);
}
void PlotControlPanel::onIndexChecked(bool checked)
{
    if (checked)
    {
        ui->lXmin->setEnabled(false);
        ui->lXmax->setEnabled(false);
        ui->spXmin->setEnabled(false);
        ui->spXmax->setEnabled(false);
        emit xScaleChanged(true); // use index
    }
    else
    {
        ui->lXmin->setEnabled(true);
        ui->lXmax->setEnabled(true);
        ui->spXmin->setEnabled(true);
        ui->spXmax->setEnabled(true);
        emit xScaleChanged(false, ui->spXmin->value(), ui->spXmax->value());
    }
    emit plotWidthChanged(plotWidth());
}
void PlotControlPanel::onXScaleChanged()
{
    if (!xAxisAsIndex())
    {
        emit xScaleChanged(false, ui->spXmin->value(), ui->spXmax->value());
        emit plotWidthChanged(plotWidth());
    }
}
double PlotControlPanel::plotWidth() const
{
    double value = ui->spPlotWidth->value();
    if (!xAxisAsIndex())
    {
        // scale by xmin and xmax
        auto xmax = ui->spXmax->value();
        auto xmin = ui->spXmin->value();
        double scale = (xmax - xmin) / _numOfSamples;
        value *= scale;
    }
    return value;
}
void PlotControlPanel::onPlotWidthChanged()
{
    emit plotWidthChanged(plotWidth());
}
void PlotControlPanel::setChannelInfoModel(ChannelInfoModel* model)
{
    ui->tvChannelInfo->setModel(model);
    // channel color selector
    connect(ui->tvChannelInfo->selectionModel(), &QItemSelectionModel::currentRowChanged,
            [this](const QModelIndex ¤t, const QModelIndex &previous)
            {
                // TODO: duplicate with below lambda
                QColor color(0,0,0,0); // transparent
                if (current.isValid())
                {
                    ui->pbColorSel->setEnabled(true);
                    auto model = ui->tvChannelInfo->model();
                    color = model->data(current, Qt::ForegroundRole).value();
                }
                else
                {
                    ui->pbColorSel->setDisabled(true);
                }
                setSelectorColor(color);
            });
    connect(ui->tvChannelInfo->selectionModel(), &QItemSelectionModel::selectionChanged,
            [this](const QItemSelection & selected, const QItemSelection & deselected)
            {
                if (!selected.length())
                {
                    ui->pbColorSel->setDisabled(true);
                    setSelectorColor(QColor(0,0,0,0));
                }
            });
    connect(model, &QAbstractItemModel::dataChanged,
            [this](const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector & roles = QVector ())
            {
                auto current = ui->tvChannelInfo->selectionModel()->currentIndex();
                // no current selection
                if (!current.isValid()) return;
                auto mod = ui->tvChannelInfo->model();
                QColor color = mod->data(current, Qt::ForegroundRole).value();
                setSelectorColor(color);
            });
    // reset actions
    connect(&resetAct, &QAction::triggered, model, &ChannelInfoModel::resetInfos);
    connect(&resetNamesAct, &QAction::triggered, model, &ChannelInfoModel::resetNames);
    connect(&resetColorsAct, &QAction::triggered, model, &ChannelInfoModel::resetColors);
    connect(&resetGainsAct, &QAction::triggered, model, &ChannelInfoModel::resetGains);
    connect(&resetOffsetsAct, &QAction::triggered, model, &ChannelInfoModel::resetOffsets);
    connect(&showAllAct, &QAction::triggered, [model]{model->resetVisibility(true);});
    connect(&hideAllAct, &QAction::triggered, [model]{model->resetVisibility(false);});
}
void PlotControlPanel::saveSettings(QSettings* settings)
{
    settings->beginGroup(SettingGroup_Plot);
    settings->setValue(SG_Plot_NumOfSamples, numOfSamples());
    settings->setValue(SG_Plot_PlotWidth, ui->spPlotWidth->value());
    settings->setValue(SG_Plot_IndexAsX, xAxisAsIndex());
    settings->setValue(SG_Plot_XMax, xMax());
    settings->setValue(SG_Plot_XMin, xMin());
    settings->setValue(SG_Plot_AutoScale, autoScale());
    settings->setValue(SG_Plot_YMax, yMax());
    settings->setValue(SG_Plot_YMin, yMin());
    settings->endGroup();
}
void PlotControlPanel::loadSettings(QSettings* settings)
{
    settings->beginGroup(SettingGroup_Plot);
    ui->spNumOfSamples->setValue(
        settings->value(SG_Plot_NumOfSamples, numOfSamples()).toInt());
    ui->spPlotWidth->setValue(
        settings->value(SG_Plot_PlotWidth, ui->spPlotWidth->value()).toInt());
    ui->cbIndex->setChecked(
        settings->value(SG_Plot_IndexAsX, xAxisAsIndex()).toBool());
    ui->spXmax->setValue(settings->value(SG_Plot_XMax, xMax()).toDouble());
    ui->spXmin->setValue(settings->value(SG_Plot_XMin, xMin()).toDouble());
    ui->cbAutoScale->setChecked(
        settings->value(SG_Plot_AutoScale, autoScale()).toBool());
    ui->spYmax->setValue(settings->value(SG_Plot_YMax, yMax()).toDouble());
    ui->spYmin->setValue(settings->value(SG_Plot_YMin, yMin()).toDouble());
    settings->endGroup();
}