#include <QDir>
#include <QDoubleSpinBox>
#include <QEventLoop>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QMutexLocker>
#include <QProcess>
#include <QSettings>
#include <QStringList>
#include <QTimer>
#include <QVBoxLayout>
#include <math.h>

#include "CalibrationEditDialog.h"
#include "ConfigPanel.h"
#include "TDeviceManager.h"
#include "TUSBProvider.h"
#include "TenkinetPanel.h"

CalibrationEditDialog::CalibrationEditDialog(QWidget *parent, const QString &channelID)
    : QDialog(parent), channelID(channelID), calibration_disabled_key("disableCalibration/" + channelID) {

    btn_ok = new QPushButton(tr("OK"));
    btn_ok->setDefault(true);

    btn_cancel = new QPushButton(tr("Cancel"));

    connect(btn_ok, SIGNAL(clicked()), this, SLOT(applyClose()));
    connect(btn_cancel, SIGNAL(clicked()), this, SLOT(close()));

    //
    // +------------------------------------------+
    // |           Sensed    Calibrated           |  0
    // | Point 1  [     ] unit  [      ] unit   (X)      |  1
    // | Point 2  [     ] unit  [      ] unit   (X)      |  2
    // | Point 3  [     ] unti  [      ] unit   (X)      |  3
    // |                                          |  4
    // | [ ] Disable calibration for this channel |
    // |                                          |
    // |                                          |
    // |                              OK  CANCEL  |
    // +------------------------------------------|
    //
    // structure:
    //
    // VBoxLayout
    //   GridLayout
    //      points, labels, line editors, clear buttons...
    //   Spacing
    //   Checkbox
    //   Spacing
    //     -- stretch --
    //   VHBoxLayout
    //      Ok button, cancel button

    QGridLayout *lay_points = new QGridLayout();

    lbl_sensed = new QLabel("Sensed");
    lbl_calibrated = new QLabel("Calibrated");

    lay_points->addWidget(lbl_sensed, 0, 1);
    lay_points->addWidget(lbl_calibrated, 0, 3);
    QDoubleValidator *value_validator = new QDoubleValidator(this);

    for (int i = 0; i < CAL_POINTS_COUNT; i++) {

        QLabel *lbl_pt = new QLabel("Point " + QString::number(i));
        QString val_sensed = "", val_calibrated = "";

        QLineEdit *le_sensed = new QLineEdit(val_sensed);
        le_sensed->setValidator(value_validator);
        lst_le_sensed.append(le_sensed);

        QLineEdit *le_calibrated = new QLineEdit(val_calibrated);
        le_calibrated->setValidator(value_validator);
        lst_le_calibrated.append(le_calibrated);

        EditButton *btn_clear = new EditButton("Delete", "Delete this point", ":edit-delete.png", i);
        connect(btn_clear, SIGNAL(buttonIdClicked(int)), this, SLOT(clearPointClicked(int)));
        lst_clear_buttons.append(btn_clear);

        QLabel *lbl_unit_sensed = new QLabel();
        lbl_units_sensed.append(lbl_unit_sensed);

        QLabel *lbl_unit_calibrated = new QLabel();
        lbl_units_calibrated.append(lbl_unit_calibrated);

        lay_points->addWidget(lbl_pt, 1 + i, 0);
        lay_points->addWidget(le_sensed, 1 + i, 1);
        lay_points->addWidget(lbl_unit_sensed, 1 + i, 2);
        lay_points->addWidget(le_calibrated, 1 + i, 3);
        lay_points->addWidget(lbl_unit_calibrated, 1 + i, 4);
        lay_points->setColumnMinimumWidth(5, 30);
        lay_points->addWidget(btn_clear, 1 + i, 6);
    }

    cb_disable = new QCheckBox("Disable calibration for this channel");

    QHBoxLayout *lay_btns = new QHBoxLayout();
    lay_btns->addStretch();
    lay_btns->addWidget(btn_ok);
    lay_btns->addWidget(btn_cancel);

    QVBoxLayout *lay_vert = new QVBoxLayout();
    lay_vert->addLayout(lay_points);
    lay_vert->addSpacing(10);
    lay_vert->addWidget(cb_disable);
    lay_vert->addSpacing(10);
    lay_vert->addStretch();
    lay_vert->addLayout(lay_btns);

    setLayout(lay_vert);

    setWindowTitle(tr("Calibrate channel"));

    refresh();
}

CalibrationEditDialog::~CalibrationEditDialog() {}

static double textToDouble(const QString &text) {
    QString t = text.trimmed();

    if (t.isEmpty() || t.isNull()) {
        return NAN;
    }

    return text.toDouble();
}

void CalibrationEditDialog::refresh() {
    TChannel *channel = TDeviceManager::instance().getChannel(channelID);
    if (!channel) {
        return;
    }

    unit_t chip_unit = channel->getNativeUnit();
    unit_category_t chip_unit_cat = unit_category(chip_unit);
    unit_t pref_unit = ConfigPanel::instance().getUnit(chip_unit_cat);

    if (pref_unit == UNIT_SENSOR_DEFAULT) {
        pref_unit = chip_unit;
    }

    QString unitString = QString::fromUtf8(unit_to_string(pref_unit, 0));

    TDevice *device = channel->device;
    bool isLocal = device->isLocal();

    for (int i = 0; i < CAL_POINTS_COUNT; i++) {

        lbl_units_sensed[i]->setText(unitString);
        lbl_units_calibrated[i]->setText(unitString);

        QString val_sensed, val_calibrated;

        Point point = channel->calpoints[i];

        if (POINT_IS_VALID(point)) {

            // Convert from native chip unit to preferred unit
            unit_convert(&point.x, chip_unit, pref_unit);
            unit_convert(&point.y, chip_unit, pref_unit);

            val_sensed.sprintf("%g", point.x);
            val_calibrated.sprintf("%g", point.y);
        }

        lst_le_sensed[i]->setText(val_sensed);
        lst_le_sensed[i]->setEnabled(isLocal);

        lst_le_calibrated[i]->setText(val_calibrated);
        lst_le_calibrated[i]->setEnabled(isLocal);

        lst_clear_buttons[i]->setEnabled(isLocal);
    }

    QSettings settings;
    calibration_disabled = settings.value(calibration_disabled_key, false).toBool();
    cb_disable->setChecked(calibration_disabled);
    cb_disable->setEnabled(device->isLocal());
    device->provider->setUserCalibration(device->serialNumber, channel->index, !calibration_disabled);
}

void CalibrationEditDialog::applyClose() {
    TChannel *channel = TDeviceManager::instance().getChannel(channelID);
    if (!channel) {
        close();
        return;
    }

    TDevice *device = channel->device;

    calibration_disabled = cb_disable->isChecked();
    device->provider->setUserCalibration(device->serialNumber, channel->index, !calibration_disabled);

    QSettings settings;
    settings.setValue(calibration_disabled_key, calibration_disabled);

    if (!device->isLocal()) {
        // Not a USB device: disabling the calibration even for local tenkinet.
        close();
        return;
    }

    unit_t chip_unit = channel->getNativeUnit();
    unit_category_t chip_unit_cat = unit_category(chip_unit);
    unit_t pref_unit = ConfigPanel::instance().getUnit(chip_unit_cat);

    if (pref_unit == UNIT_SENSOR_DEFAULT) {
        pref_unit = chip_unit;
    }

    // Convert values in widgets to calpoints

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

        double x = textToDouble(lst_le_sensed.at(i)->text());
        double y = textToDouble(lst_le_calibrated.at(i)->text());

        // Convert display (preference) values back to the native value
        unit_convert(&x, pref_unit, chip_unit);
        unit_convert(&y, pref_unit, chip_unit);

        // Sync calpoint
        TUSBProvider::instance().setUserCalibrationPoint(channel, i, x, y);
    }

    close();
}

void CalibrationEditDialog::clearPointClicked(int id) {
    if (id > lst_le_sensed.size()) {
        return;
    }

    lst_le_sensed.at(id)->setText("");
    lst_le_calibrated.at(id)->setText("");
}

void CalibrationEditDialog::showEvent(QShowEvent *event) {
    refresh();
    QDialog::showEvent(event);
}