#include <QMessageBox>
#include <QSettings>
#include <stdint.h>

#include "BigView.h"
#include "BigViewCheckBox.h"
#include "CalibrationEditDialog.h"
#include "CalibrationEditDialog_DXC120.h"
#include "CalibrationEditDialog_DXC220.h"
#include "CalibrationEditDialog_DXC320.h"
#include "ConfigPanel.h"
#include "DashSensorDevice.h"
#include "GraphView.h"
#include "GraphViewCheckBox.h"
#include "QuantityFormat.h"
#include "TDeviceManager.h"
#include "TUSBProvider.h"
#include "chip.h"

#define LBL_CAL_NONE "<img src=':cal_none.png'>"
#define LBL_CAL_SUSPENDED "<img src=':cal_suspended.png'>"
#define LBL_CAL_ENABLED "<img src=':cal_active.png'>"
#define TOOLTIP_CAL_SUSPENDED "Warning: Calibration points are present but calibration is disabled"
#define TOOLTIP_CAL_ACTIVE "Calibration points present and calibration active"

DashSensorDevice::DashSensorDevice(const TDevice *inputDevice) : device(new TDevice(*inputDevice)) {

    int col = 0, graph_col, bigview_col;

    baseTitle = device->serialNumber + " : " + device->productName;
    setTitle(baseTitle);

    setObjectName("source"); // selector for stylesheet

    layout = new QGridLayout();
    setLayout(layout);

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

    layout->addWidget(new QLabel("<b>Source ID</b>"), 0, col++);
    layout->setColumnMinimumWidth(col, 4);
    layout->addWidget(new QLabel("<b>Description</b>"), 0, col++);
    layout->setColumnMinimumWidth(col, 4);
    layout->addWidget(new QLabel("<b>Type</b>"), 0, col++);
    layout->setColumnMinimumWidth(col, 4);
    layout->addWidget(new QLabel("<b>Current</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->setColumnStretch(col, 0);
    layout->addWidget(new QLabel("<b>Unit</b>"), 0, col++);
    layout->setColumnMinimumWidth(col, 4);
    layout->addWidget(new QLabel("<b>Alias</b>"), 0, col++);
    layout->setColumnStretch(col, 0);
    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->setColumnMinimumWidth(col, 4);

    layout->addWidget(new QLabel(""), 0, col++);
    layout->setColumnMinimumWidth(col, 0);

    layout->addWidget(new QLabel(""), 0, col++);
    layout->setColumnMinimumWidth(col, 16);

    QVector<TChannel *> &channels = device->channels;
    int i = 0;

    for (; i < channels.size(); i++) {
        addChannel(channels[i]);
    }

    QPushButton *infobtn = new QPushButton(QIcon(":help-about.png"), tr("About"));
    connect(infobtn, SIGNAL(clicked(bool)), this, SLOT(onInfoClicked(bool)));

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

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

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

#ifdef Q_OS_LINUX
    for (TChannel *ch : channels) {
        if (ch->chip_id == USBTENKI_CHIP_CO2_DXC300_COMPENSATED) {
            QLabel *warning = new QLabel(
                "<b>Warning: This DXC320 may be affected by an active serial port connection.</b><br>"
                "&nbsp;&nbsp;&nbsp;&nbsp;• The device may show intermittent or missing data.<br>"
                "&nbsp;&nbsp;&nbsp;&nbsp;• Close the serial connection and restart the application.");
            warning->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
            warning->setTextFormat(Qt::RichText);
            warning->setStyleSheet(
                "color: #7a3b00;"
                "background-color: #ffe0a0;"
                "border: 1px solid #c87000;"
                "border-radius: 4px;"
                "padding: 4px;");
            QWidget *row = new QWidget();
            QHBoxLayout *rowLayout = new QHBoxLayout(row);
            rowLayout->setContentsMargins(0, 0, 0, 0);
            rowLayout->addWidget(warning);
            rowLayout->addStretch();
            layout->addWidget(row, i + 2, 0, 1, col);
            break;
        }
    }
#endif

    onThermocoupleColorChanged(ConfigPanel::instance().isUsingIECThermocoupleColors());
    refreshAttentionLabels();

    setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);

    TDeviceManager &dm = TDeviceManager::instance();
    TDeviceSignal *signal = dm.getDeviceSignal(device->serialNumber);

    if (signal) {
        connect(signal, &TDeviceSignal::deviceConnected, this, &DashSensorDevice::onDeviceUpdated);
        connect(signal, &TDeviceSignal::deviceDisconnected, this, &DashSensorDevice::onDeviceUpdated);
        connect(signal, &TDeviceSignal::deviceUpdated, this, &DashSensorDevice::onDeviceUpdated);
    }

    connect(&ConfigPanel::instance(), &ConfigPanel::thermocoupleColorChanged, this,
            &DashSensorDevice::onThermocoupleColorChanged);
}

DashSensorDevice::~DashSensorDevice() {
    // TODO use Qt's parent/child system to automatically do this...
    // Not super necessary right now because instances of this class live forever
    QLayoutItem *child;
    while ((child = layout->takeAt(0)) != 0) {
        delete child;
    }
    delete layout;

    delete device;
}

void DashSensorDevice::activate() {

    QVector<TChannel *> &channels = device->channels;

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

        TChannel *channel = channels[i];
        ChannelWidgets *w = channelWidgets[i];

        if (w->bigViewCheckBox->isChecked()) {
            BigView::instance().addChannel(channel->id);
        }
        if (w->graphViewCheckBox->isChecked()) {
            GraphView::instance().addChannel(channel->id);
        }
    }

    refresh();
}

void DashSensorDevice::deactivate() {

    QVector<TChannel *> &channels = device->channels;

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

        TChannel *channel = channels[i];
        ChannelWidgets *w = channelWidgets[i];

        if (w->bigViewCheckBox->isChecked()) {
            BigView::instance().removeChannel(channel->id);
        }
        if (w->graphViewCheckBox->isChecked()) {
            GraphView::instance().removeChannel(channel->id);
        }
    }
}

void DashSensorDevice::addChannel(TChannel *channel) {
    const QString &id = channel->id;
    const int index = channel->index;
    const int row = index + 1;
    int col = 0;

    ChannelWidgets *w = new ChannelWidgets();
    channelWidgets.append(w);

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

    w->descriptionLabel = new QLabel(channel->description);
    layout->addWidget(w->descriptionLabel, row, col++);

    w->typeLabel = new QLabel(channel->shortDescription);
    layout->addWidget(w->typeLabel, row, col++);

    // Current value
    double value = quantity_value_as_double(&(channel->calibratedQuantity));
    QString valueString = QuantityFormat::instance().toString(value);
    w->valueLabel = new QLabel(valueString);
    layout->addWidget(w->valueLabel, row, col++);

    // Minimum value
    w->minResettable = new MinMaxResettable(true);
    layout->addWidget(w->minResettable, row, col++);

    // Maximum value
    w->maxResettable = new MinMaxResettable(false);
    layout->addWidget(w->maxResettable, row, col++);

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

    // Unit
    w->unitLabel = new QLabel(QString::fromUtf8(unit_to_string(channel->calibratedQuantity.unit, 0)));
    layout->addWidget(w->unitLabel, row, col++);

    // Alias
    w->aliasEdit = new SourceAliasEdit(id);
    layout->addWidget(w->aliasEdit, row, col++);
    connect(w->aliasEdit, &SourceAliasEdit::sourceAliasChanged, &(TDeviceManager::instance()),
            &TDeviceManager::setChannelAlias);

    // Big View
    w->bigViewCheckBox = new BigViewCheckBox(id);
    layout->addWidget(w->bigViewCheckBox, row, col++);

    // Graph View
    w->graphViewCheckBox = new GraphViewCheckBox(id);
    layout->addWidget(w->graphViewCheckBox, row, col++);

    // Calibration

    EditButton *calibrateButton;
    calibrateButton = new EditButton("Calibrate", "Edit calibration for this channel", index);
    calibrateButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);

    w->calibrationEditDialog = nullptr;
    bool hasCalibrateButton = false;

    if (channel->chip_id == USBTENKI_CHIP_SCD30_CO2_CAL) {
        hasCalibrateButton = true;
        if (device->isLocal()) {
            w->calibrationEditDialog =
                new CalibrationEditDialog_DXC120(this, device->serialNumber, device->isLocalTenkinet());
        }
    }
    // The DXC220 has two channels that don't support normal user calibration
    // USBTENKI_CHIP_CO2_DXC200_INSTANT: which has his own custom calibration dialog
    else if (channel->chip_id == USBTENKI_CHIP_CO2_DXC200_INSTANT) {
        hasCalibrateButton = true;
        if (device->isLocal()) {
            w->calibrationEditDialog =
                new CalibrationEditDialog_DXC220(this, device->serialNumber, device->isLocalTenkinet());
        }
    }
    else if (channel->chip_id == USBTENKI_CHIP_CO2_DXC300_COMPENSATED) {
        hasCalibrateButton = true;
        if (device->isLocal()) {
            w->calibrationEditDialog =
                new CalibrationEditDialog_DXC320(this, device->serialNumber, device->isLocalTenkinet());
        }
    } else if (device->isUserCalibrationSupported(index) && !channel->isVirtual()) {
        hasCalibrateButton = true;
        w->calibrationEditDialog = new CalibrationEditDialog(this, id);
    } else {
        hasCalibrateButton = false;
    }

    if (hasCalibrateButton) {
        w->calibrateWidget = calibrateButton;
        if (w->calibrationEditDialog == nullptr) {
            calibrateButton->setEnabled(false);
            calibrateButton->setToolTip("Calibration is not available over the network.");
        } else {
            calibrateButton->setEnabled(true);
            connect(calibrateButton, SIGNAL(buttonIdClicked(int)), this, SLOT(onCalibrateClicked(int)));
        }
    } else {
        w->calibrateWidget = new QLabel();
        w->calibrateWidget->setFixedSize(calibrateButton->sizeHint());
        delete calibrateButton;
    }

    layout->addWidget(w->calibrateWidget, row, col++);

    QSettings settings;
    bool showCal = settings.value("ui/show_cal_buttons", true).toBool();
    w->calibrateWidget->setVisible(showCal);

    // Attention icon shown when user-calibration is disabled or when it is active.
    // The tool top and text/icon is set in refreshAttentionLabel
    w->attentionLabel = new QLabel(LBL_CAL_NONE);
    layout->addWidget(w->attentionLabel, row, col++, Qt::AlignLeft);
    w->attentionLabel->setVisible(true);
}

void DashSensorDevice::refreshAttentionLabels() {
    QSettings settings;
    bool showCal = settings.value("ui/show_cal_buttons", true).toBool();

    QVector<TChannel *> &channels = device->channels;

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

        ChannelWidgets *w = channelWidgets[i];
        w->calibrateWidget->setVisible(showCal);

        TChannel *channel = channels[i];
        if (channel->isVirtual()) {
            continue;
        }

        if (device->isUserCalibrationSupported(i)) {

            int j = 0;

            if (device->isUserCalibrationEnabled(i)) {
                // User calibration is enabled. Show the enabled icon if there are valid points.
                for (j = 0; j < CAL_POINTS_COUNT; j++) {
                    const Point p = channel->calpoints[j];
                    if (POINT_IS_VALID(p)) {
                        w->attentionLabel->setText(LBL_CAL_ENABLED);
                        w->attentionLabel->setToolTip(TOOLTIP_CAL_ACTIVE);
                        break;
                    }
                }
            } else {
                // User calibration is disabled.
                // If any of the points is set, show a warning.
                // TODO use pre-computed number of valid points, instead of scanning all points
                int j = 0;
                for (; j < CAL_POINTS_COUNT; j++) {
                    const Point p = channel->calpoints[j];
                    if (POINT_IS_VALID(p)) {
                        w->attentionLabel->setText(LBL_CAL_SUSPENDED);
                        w->attentionLabel->setToolTip(TOOLTIP_CAL_SUSPENDED);
                        break;
                    }
                }
            }

            // Don't show a warning when there are no points
            if (j >= CAL_POINTS_COUNT) {
                w->attentionLabel->setText(LBL_CAL_NONE);
                w->attentionLabel->setToolTip("");
            }
        }
    }
}

void DashSensorDevice::refresh() {

    QString new_title = baseTitle;
    setTitle(new_title);

    QVector<TChannel *> &channels = device->channels;

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

        TChannel *channel = channels[i];
        const Quantity &quantity = channel->calibratedQuantity;

        ChannelWidgets *w = channelWidgets[i];

        if (quantity.type == QUANTITY_TYPE_ERROR) {
            QString errorString = chip_error_to_string(quantity.value_error);
            w->valueLabel->setText(errorString);
            continue;
        }

        double value = quantity_value_as_double(&quantity);

        if (channel->chip_id == USBTENKI_VIRTUAL_HEXCOLOR) {
            uint32_t rgb = quantity.value_uint32;
            char tmpbuf[128];
            snprintf(tmpbuf, 128, "<font style='background-color: #%06x'; color: #%06x'><b>#%06x</b></font>", rgb,
                     0x000000, rgb);
            w->valueLabel->setText(tmpbuf);
        } else {
            QString v;
            v = QuantityFormat::instance().toString(value);
            w->valueLabel->setText(v);
        }

        w->minResettable->submitValue(value);
        w->maxResettable->submitValue(value);
        w->unitLabel->setText(QString::fromUtf8(unit_to_string(quantity.unit, 0)));
    }

    refreshAttentionLabels();
}

void DashSensorDevice::onDeviceUpdated(const TDevice *eventDevice) {
    device->update(eventDevice);
    refresh();
}

void DashSensorDevice::onInfoClicked(bool checked) {
    const Version &version = device->version;

    QString text = "<html><body><h1>" + device->productName + "</h1>" +
                   "Firmware version: " + QString::number(version.major) + "." + QString::number(version.minor) +
                   "<br>" + "Serial number: " + device->serialNumber + "<br>" +
                   "Channels: " + QString::number(device->channels.size()) + "</body></html>";

    QMessageBox msgBox;
    msgBox.setWindowTitle("About sensor " + baseTitle);
    msgBox.setText(text);
    msgBox.setStandardButtons(QMessageBox::Ok);
    msgBox.setIcon(QMessageBox::Information);

    msgBox.exec();
}

void DashSensorDevice::onBigViewAllChecked(int state) {
    for (int i = 0; i < channelWidgets.size(); i++) {
        channelWidgets[i]->bigViewCheckBox->setChecked(state);
    }
}

void DashSensorDevice::onGraphViewAllChecked(int state) {
    for (int i = 0; i < channelWidgets.size(); i++) {
        channelWidgets[i]->graphViewCheckBox->setChecked(state);
    }
}

void DashSensorDevice::onCalibrateClicked(int index) {
    if (calibrateInProgress) {
        return;
    }

    calibrateInProgress = true;

    QDialog *dialog = channelWidgets[index]->calibrationEditDialog;

    if (dialog) {
        dialog->exec();
    }

    calibrateInProgress = false;
}

void DashSensorDevice::onThermocoupleColorChanged(bool iec) {
    QVector<TChannel *> &channels = device->channels;

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

        TChannel *channel = channels[i];

        QString d = channel->description; // non-const copy

        // TODO use a constant mapping between type and {background-color, color} to avoid code duplication00
        if (d.startsWith("Type-")) {
            if (iec) {
                d.replace("Type-K",
                          "<font style='background-color: #40ab45; color: white;'><b>&nbsp;Type-K&nbsp;</b></font>");
                d.replace("Type-J",
                          "<font style='background-color: #000000; color: white;'><b>&nbsp;Type-J&nbsp;</b></font>");
                d.replace("Type-N",
                          "<font style='background-color: #fac9e1; color: black;'><b>&nbsp;Type-N&nbsp;</b></font>");
                d.replace("Type-E",
                          "<font style='background-color: #6a3f92; color: white;'><b>&nbsp;Type-E&nbsp;</b></font>");
                d.replace("Type-T",
                          "<font style='background-color: #794205; color: white;'><b>&nbsp;Type-T&nbsp;</b></font>");
                d.replace("Type-B",
                          "<font style='background-color: #808080; color: white;'><b>&nbsp;Type-B&nbsp;</b></font>");
                d.replace("Type-S",
                          "<font style='background-color: #e38538; color: white;'><b>&nbsp;Type-S&nbsp;</b></font>");
                d.replace("Type-R",
                          "<font style='background-color: #e38538; color: white;'><b>&nbsp;Type-R&nbsp;</b></font>");
            } else {
                d.replace("Type-K",
                          "<font style='background-color: #ffff00; color: black;'><b>&nbsp;Type-K&nbsp;</b></font>");
                d.replace("Type-J",
                          "<font style='background-color: #000000; color: white;'><b>&nbsp;Type-J&nbsp;</b></font>");
                d.replace("Type-N",
                          "<font style='background-color: #e2811f; color: white;'><b>&nbsp;Type-N&nbsp;</b></font>");
                d.replace("Type-E",
                          "<font style='background-color: #d9018b; color: white;'><b>&nbsp;Type-E&nbsp;</b></font>");
                d.replace("Type-T",
                          "<font style='background-color: #2350a0; color: white;'><b>&nbsp;Type-T&nbsp;</b></font>");
                d.replace("Type-B",
                          "<font style='background-color: #808080; color: white;'><b>&nbsp;Type-B&nbsp;</b></font>");
                d.replace("Type-S",
                          "<font style='background-color: #3f8d3e; color: white;'><b>&nbsp;Type-S&nbsp;</b></font>");
                d.replace("Type-R",
                          "<font style='background-color: #3f8d3e; color: white;'><b>&nbsp;Type-R&nbsp;</b></font>");
            }
        }

        channelWidgets[i]->descriptionLabel->setText(d);
    }
}
