#include <QDebug>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QToolButton>
#include <stdint.h>

#include "BigViewCheckBox.h"
#include "ConfigCheckBox.h"
#include "DashSensorMath.h"
#include "EditButton.h"
#include "GraphViewCheckBox.h"
#include "HTMLViewer.h"
#include "MathProvider.h"
#include "QuantityFormat.h"
#include "SourceAliasEdit.h"
#include "TDeviceManager.h"
#include "chip.h"

// #define RESET_MIN_MAX_ON_EQUATION_EDIT

DashSensorMath::DashSensorMath() {
    int col = 0, i, graph_col, bigview_col;
    title = MathProvider::instance().getDescription();
    setTitle(title);
    setObjectName("source"); // selector for stylesheet

    layout = new QGridLayout();

    layout->setVerticalSpacing(1);
    layout->setHorizontalSpacing(10);

    //	layout->addWidget(new QLabel("<b>Channel</b>"), 0, col++);
    layout->addWidget(new QLabel("<b>Source ID</b>"), 0, col++);
    layout->setColumnMinimumWidth(col, 4);

    layout->setColumnStretch(col, 2);
    layout->addWidget(new QLabel("<b>Expression</b>"), 0, col++);

    // Leave a blank column for the edit button
    col++;

    layout->addWidget(new QLabel("<b>Result</b>"), 0, col++);
    layout->setColumnMinimumWidth(col, 4);

    layout->addWidget(new QLabel("<b>Min.</b>"), 0, col++);
    layout->setColumnMinimumWidth(col, 4);

    layout->addWidget(new QLabel("<b>Max.</b>"), 0, col++);
    layout->setColumnMinimumWidth(col, 4);

    layout->addWidget(new QLabel("<b></b>"), 0, col++);
    layout->setColumnMinimumWidth(col, 4);

    layout->addWidget(new QLabel("<b>Unit</b>"), 0, col++);
    layout->setColumnMinimumWidth(col, 4);

    layout->setColumnStretch(col, 1);
    layout->addWidget(new QLabel("<b>Alias</b>"), 0, col++);

    bigview_col = col;
    layout->addWidget(new QLabel("<b>Big View</b>"), 0, col++);
    layout->setColumnStretch(col, 0);

    graph_col = col;
    layout->addWidget(new QLabel("<b>Graph</b>"), 0, col++);
    layout->setColumnStretch(col, 0);

    QVector<TDevice *> devices = MathProvider::instance().getDevices();
    for (i = 0; i < devices.size(); i++) {
        addDevice(devices[i], i, i + 1);
    }

    helpbtn = new QPushButton(QIcon(":help-about.png"), tr("Help"));
    connect(helpbtn, SIGNAL(clicked(bool)), this, SLOT(helpClicked(bool)));

    cb_all_graph = new QCheckBox();
    cb_all_graph->setToolTip(tr("Select All / None"));
    connect(cb_all_graph, SIGNAL(stateChanged(int)), this, SLOT(cbAllGraphChanged(int)));

    cb_all_bigview = new QCheckBox();
    cb_all_bigview->setToolTip(tr("Select All / None"));
    connect(cb_all_bigview, SIGNAL(stateChanged(int)), this, SLOT(cbAllBigviewChanged(int)));

    layout->addWidget(cb_all_graph, i + 1, graph_col);
    layout->addWidget(cb_all_bigview, i + 1, bigview_col);
    layout->addWidget(helpbtn, i + 1, 0);

    setLayout(layout);

    TDeviceManager &dm = TDeviceManager::instance();
    MathProvider &provider = MathProvider::instance();

    QVector<TDevice *> mathDevices = provider.getDevices();
    for (int i = 0; i < mathDevices.size(); i++) {
        TDevice *mathDevice = mathDevices[i];
        TDeviceSignal *signal = dm.getDeviceSignal(mathDevice->serialNumber);
        if (signal) {
            connect(signal, &TDeviceSignal::deviceConnected, this, &DashSensorMath::refresh);
            connect(signal, &TDeviceSignal::deviceDisconnected, this, &DashSensorMath::refresh);
            connect(signal, &TDeviceSignal::deviceUpdated, this, &DashSensorMath::refresh);
        }
    }
}

void DashSensorMath::addDevice(TDevice *device, int index, int row) {
    QString visible_name;
    QLabel *value_label, *unit_label;
    QLabel *tmp_label;
    QLineEdit *exedit;
    QToolButton *rst;
    EditButton *editbtn;
    MinMaxResettable *min, *max;
    int col = 0;

    indexBySerialNumber.insert(device->serialNumber, index);

    TChannel *channel = device->channels[0];

    tmp_label = new QLabel(channel->id);
    tmp_label->setTextInteractionFlags(Qt::TextSelectableByMouse);
    layout->addWidget(tmp_label, row, col++);

    // Expression editor
    exedit = new QLineEdit(MathProvider::instance().getExpression(index));
    expressions.append(exedit);
    layout->addWidget(exedit, row, col++);
    connect(exedit, SIGNAL(editingFinished()), this, SLOT(expressionEdited()));

    // Edit button
    editbtn = new EditButton("Edit", index);
    editbtn->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
    layout->addWidget(editbtn, row, col++);
    connect(editbtn, SIGNAL(buttonIdClicked(int)), this, SLOT(editClicked(int)));

    // Current value
    QString qtyString = QuantityFormat::instance().toString(channel->calibratedQuantity);
    value_label = new QLabel(qtyString);
    values.append(value_label);
    layout->addWidget(value_label, row, col++);

    // Minimum value
    min = new MinMaxResettable(true); // Minimum tracking mode
    minimums.append(min);
    layout->addWidget(min, row, col++);

    // Maximum value
    max = new MinMaxResettable(false); // Maximum tracking mode
    maximums.append(max);
    layout->addWidget(max, row, col++);

    // Min/Max reset button
    rst = new QToolButton();
    rst->setText("reset");
    rst->setIcon(QIcon(":undo.png"));
    rst->setToolTip("Reset min/max");
    rst->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
    layout->addWidget(rst, row, col++);
    QObject::connect(rst, SIGNAL(clicked()), min, SLOT(reset()));
    QObject::connect(rst, SIGNAL(clicked()), max, SLOT(reset()));

    QString unitString = QString::fromUtf8(unit_to_string(channel->calibratedQuantity.unit, 0));
    unit_label = new QLabel(unitString);
    layout->addWidget(unit_label, row, col++);
    units.append(unit_label);

    // alias
    SourceAliasEdit *se = new SourceAliasEdit(channel->id);
    layout->addWidget(se, row, col++);
    connect(se, &SourceAliasEdit::sourceAliasChanged, &(TDeviceManager::instance()), &TDeviceManager::setChannelAlias);

    // In bigview
    BigViewCheckBox *bvcb = new BigViewCheckBox(channel->id);
    bigviewCheckboxes.append(bvcb);
    layout->addWidget(bvcb, row, col++);

    // In graph
    GraphViewCheckBox *ccb_gr = new GraphViewCheckBox(channel->id);
    graphCheckboxes.append(ccb_gr);
    layout->addWidget(ccb_gr, row, col++);
}

DashSensorMath::~DashSensorMath() {
    QLayoutItem *child;
    while ((child = layout->takeAt(0)) != 0) {
        delete child;
    }
    delete layout;
}

void DashSensorMath::refresh(const TDevice *device) {
    int index = indexBySerialNumber.value(device->serialNumber, -1);

    if (index == -1) {
        return;
    }

    TChannel *channel = device->channels[0];
    const Quantity quantity = channel->calibratedQuantity;

    units.at(index)->setText(unit_to_string(quantity.unit, 0));

    if (quantity.type == QUANTITY_TYPE_ERROR) {
        QString errorString = chip_error_to_string(quantity.value_error);
        values.at(index)->setText(errorString);
        return;
    }

    double value = quantity_value_as_double(&quantity);

    QString v;
    v = QuantityFormat::instance().toString(value);
    values.at(index)->setText(v);

    minimums.at(index)->submitValue(value);
    maximums.at(index)->submitValue(value);
}

// Called when one of the expressions in this DashSensorMath object has potentially been edited
void DashSensorMath::expressionEdited() {
    MathProvider &provider = MathProvider::instance();

    for (int i = 0; i < expressions.size(); i++) {

        QString expr = expressions.at(i)->text();

        if (expr == provider.getExpression(i)) {
            continue;
        }

        provider.setExpression(i, expr);
#ifdef RESET_MIN_MAX_ON_EQUATION_EDIT
        maximums.at(i)->reset();
        minimums.at(i)->reset();
#endif
    }
}

void DashSensorMath::helpClicked(bool checked) {
    HTMLViewer *viewer;
    (void)checked;

    viewer = new HTMLViewer(":mathhelp.html", "Help");
    viewer->exec();

    delete viewer;
}

void DashSensorMath::editClicked(int id) {
    editDialog = new MathEditDialog(this, id);
    editDialog->exec();

    delete editDialog;
    editDialog = NULL;

#ifdef RESET_MIN_MAX_ON_EQUATION_EDIT
    maximums.at(id)->reset();
    minimums.at(id)->reset();
#endif

    const QString &expression = MathProvider::instance().getExpression(id);
    expressions.at(id)->setText(expression);
}

void DashSensorMath::cbAllBigviewChanged(int state) {
    for (int i = 0; i < bigviewCheckboxes.count(); i++) {
        bigviewCheckboxes.at(i)->setChecked(state);
    }
}

void DashSensorMath::cbAllGraphChanged(int state) {
    for (int i = 0; i < graphCheckboxes.count(); i++) {
        graphCheckboxes.at(i)->setChecked(state);
    }
}

void DashSensorMath::activate() {}

void DashSensorMath::deactivate() {}
