#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QFileDialog>
#include <QFileInfo>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QIcon>
#include <QMessageBox>
#include <QPushButton>
#include <QSettings>
#include <QVBoxLayout>

#include "Config.h"
#include "ConfigPanel.h"
#include "DataSourceCheckBox.h"
#include "LoggingPanel.h"
#include "TDeviceManager.h"
#include "TextViewer.h"

#define EXISTS_OVERWRITE 0
#define EXISTS_APPEND 1
#define EXISTS_CONFIRM 2

LoggingPanel::LoggingPanel() {

    QSettings settings;

    // Remove old deprecated keys
    settings.remove("logger/interval");
    settings.remove("logger/interval_ms");

    QHBoxLayout *mainLayout = new QHBoxLayout();
    setLayout(mainLayout);
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    // Channel selection

    leftColumn = new QGroupBox(tr("Sources"));
    QVBoxLayout *leftColumnLayout = new QVBoxLayout();
    leftColumn->setLayout(leftColumnLayout);

    // Add "Select All" and "Select None" buttons at the top

    QPushButton *selectAllButton = new QPushButton(tr("Select All"));
    selectAllButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Minimum);
    connect(selectAllButton, &QPushButton::clicked, this, &LoggingPanel::selectAll);

    QPushButton *selectNoneButton = new QPushButton(tr("Select None"));
    selectNoneButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Minimum);
    connect(selectNoneButton, &QPushButton::clicked, this, &LoggingPanel::selectNone);

    QWidget *selectButtonsWidget = new QWidget();
    QHBoxLayout *selectButtonsLayout = new QHBoxLayout();
    selectButtonsWidget->setLayout(selectButtonsLayout);

    selectButtonsLayout->addWidget(selectAllButton);
    selectButtonsLayout->addWidget(selectNoneButton);
    selectButtonsLayout->addStretch();

    leftColumnLayout->addWidget(selectButtonsWidget);

    // Add channels
    QWidget *channelsWidget = new QWidget();
    channelsLayout = new QVBoxLayout();
    channelsWidget->setLayout(channelsLayout);
    leftColumnLayout->addWidget(channelsWidget);

    // Add math channels
    QWidget *mathChannelsWidget = new QWidget();
    mathChannelsLayout = new QVBoxLayout();
    mathChannelsWidget->setLayout(mathChannelsLayout);
    leftColumnLayout->addWidget(mathChannelsWidget);

    TDeviceManager *deviceManager = &(TDeviceManager::instance());
    connect(deviceManager, &TDeviceManager::deviceAdded, this, &LoggingPanel::addDevice);
    connect(deviceManager, &TDeviceManager::deviceRemoved, this, &LoggingPanel::removeDevice);

    QVector<TDevice *> devices = deviceManager->getDevices();
    for (int i = 0; i < devices.size(); i++) {
        TDevice *device = devices[i];
        addDevice(devices[i]);
    }

    leftColumnLayout->addStretch();

    // Right panel

    QWidget *rightColumn = new QWidget();
    QVBoxLayout *rightColumnLayout = new QVBoxLayout();
    rightColumn->setLayout(rightColumnLayout);

    // Settings

    int y = 0;

    settingsBox = new QGroupBox(tr("Output Configuration"));
    QGridLayout *settingsBoxLayout = new QGridLayout();
    settingsBox->setLayout(settingsBoxLayout);

    ConfigComboBox *separatorComboBox = new FieldSeparatorComboBox();
    settingsBoxLayout->addWidget(new QLabel(tr("Field separator:")), y, 0);
    settingsBoxLayout->addWidget(separatorComboBox, y, 1);

    ConfigComboBox *decimalComboBox = new DecimalPointComboBox();
    settingsBoxLayout->addWidget(new QLabel(tr("Decimal point:")), y, 2);
    settingsBoxLayout->addWidget(decimalComboBox, y, 3);
    y++;

    ConfigComboBox *timeFormatComboBox = new TimeFormatComboBox();
    settingsBoxLayout->addWidget(new QLabel(tr("Timestamps:")), y, 0);
    settingsBoxLayout->addWidget(timeFormatComboBox, y, 1, 1, 4);
    y++;

    ConfigComboBox *errorComboBox = new ErrorStringComboBox();
    settingsBoxLayout->addWidget(new QLabel(tr("On error:")), y, 0);
    settingsBoxLayout->addWidget(errorComboBox, y, 1, 1, 4);
    y++;

    existingFileComboBox = new ConfigComboBox("logger/if_file_exists", {
                                                                           "Overwrite file without confirmation",
                                                                           "Append to file without confirmation",
                                                                           "Overwrite file with confirmation",
                                                                           "Append to file with confirmation",
                                                                       });

    settingsBoxLayout->addWidget(new QLabel(tr("If file exists:")));
    settingsBoxLayout->addWidget(existingFileComboBox, y, 1, 1, 4);
    y++;

    intervalSpinBox = new QDoubleSpinBox();
    intervalSpinBox->setSuffix(" s");
    intervalSpinBox->setDecimals(3);
    connect(intervalSpinBox, SIGNAL(valueChanged(double)), this, SLOT(onIntervalSelected(double)));
    settingsBoxLayout->addWidget(new QLabel(tr("Interval:")), y, 0);
    settingsBoxLayout->addWidget(intervalSpinBox, y, 1, 1, 1);
    settingsBoxLayout->addWidget(new QLabel("(multiple of global sampling interval)"), y, 2, 1, 2);

    y++;

    interval = -1;
    setGlobalInterval(ConfigPanel::instance().getInterval());

    connect(&(ConfigPanel::instance()), &ConfigPanel::intervalChanged, this, &LoggingPanel::setGlobalInterval);

    // Output path

    y = 0;

    outputPathBox = new QGroupBox(tr("Output File"));
    QGridLayout *outputPathBoxLayout = new QGridLayout();
    outputPathBox->setLayout(outputPathBoxLayout);

    QPushButton *outputBrowseButton = new QPushButton(QIcon(":fileopen.png"), tr("Browse..."));
    connect(outputBrowseButton, &QPushButton::clicked, this, &LoggingPanel::browseOutputFile);
    outputPathBoxLayout->addWidget(outputBrowseButton, y, 0, 1, 1);

    outputPathLineEdit = new ConfigLineEdit("logger/filename");
    outputPathBoxLayout->addWidget(outputPathLineEdit, y, 1, 1, 4);

    y++;

    outputPathBoxLayout->addWidget(new QLabel(tr("Log comment:")), y, 0);
    commentLineEdit = new ConfigLineEdit("logger/file_comments");
    outputPathBoxLayout->addWidget(commentLineEdit, y, 1, 1, 4);

    y++;

    // Control

    QGroupBox *controlBox = new QGroupBox(tr("Control"));
    QHBoxLayout *controlBoxLayout = new QHBoxLayout();
    controlBox->setLayout(controlBoxLayout);

    startButton = new QPushButton(QIcon(":record.png"), tr("Start"));
    connect(startButton, &QPushButton::clicked, this, &LoggingPanel::startLogging);
    controlBoxLayout->addWidget(startButton);

    stopButton = new QPushButton(QIcon(":stop.png"), tr("Stop"));
    connect(stopButton, &QPushButton::clicked, this, &LoggingPanel::stopLogging);
    stopButton->setEnabled(false);
    controlBoxLayout->addWidget(stopButton);

    QPushButton *viewButton = new QPushButton(QIcon(":view.png"), tr("View File"));
    connect(viewButton, &QPushButton::clicked, this, &LoggingPanel::showLogViewer);
    controlBoxLayout->addWidget(viewButton);

    statusLabel = new QLabel(tr("Not running."));
    controlBoxLayout->addWidget(statusLabel);

    counterLabel = new QLabel();
    controlBoxLayout->addWidget(counterLabel);

    // Messages

    QGroupBox *messageBox = new QGroupBox(tr("Messages"));
    QVBoxLayout *messageBoxLayout = new QVBoxLayout();
    messageBox->setLayout(messageBoxLayout);

    messageTextEdit = new QTextEdit();
    messageTextEdit->setReadOnly(true);
    messageTextEdit->setMaximumHeight(100);
    messageBoxLayout->addWidget(messageTextEdit);

    QWidget *messageControlWidget = new QWidget();
    QHBoxLayout *messageControlLayout = new QHBoxLayout();
    messageControlWidget->setLayout(messageControlLayout);
    messageBoxLayout->addWidget(messageControlWidget);

    QPushButton *clearMessagesButton = new QPushButton(QIcon(":document-close.png"), tr("Clear Messages"));
    connect(clearMessagesButton, &QPushButton::clicked, this, &LoggingPanel::clearMessages);
    messageControlLayout->addWidget(clearMessagesButton);

    QPushButton *saveMessagesButton = new QPushButton(QIcon(":fileopen.png"), tr("Save Messages..."));
    connect(saveMessagesButton, &QPushButton::clicked, this, &LoggingPanel::saveMessages);
    messageControlLayout->addWidget(saveMessagesButton);

    messageBoxLayout->addStretch();

    // Put it all together

    rightColumnLayout->addWidget(settingsBox);
    rightColumnLayout->addWidget(outputPathBox);
    rightColumnLayout->addWidget(controlBox);
    rightColumnLayout->addWidget(messageBox);
    rightColumnLayout->addStretch();

    mainLayout->addWidget(leftColumn);
    mainLayout->addWidget(rightColumn);
}

LoggingPanel::~LoggingPanel() {
    // Singleton: lives forever
}

bool LoggingPanel::isRunning() { return (thread != nullptr); }

void LoggingPanel::addDevice(const TDevice *device) {

    if (device->isTicker()) {
        return;
    }

    const QString &serial = device->serialNumber;
    QVector<DataSourceCheckBox *> *checkBoxes = checkBoxesByDevice.value(serial);

    if (checkBoxes) {
        // Device already added
        return;
    }

    QVBoxLayout *layout = device->isMath() ? mathChannelsLayout : channelsLayout;
    const int n = device->channels.size();
    checkBoxes = new QVector<DataSourceCheckBox *>(n);
    checkBoxesByDevice.insert(serial, checkBoxes);

    for (int i = 0; i < n; i++) {
        TChannel *channel = device->channels[i];
        DataSourceCheckBox *cb = new DataSourceCheckBox(channel);
        layout->addWidget(cb);
        checkBoxes->replace(i, cb);
    }
}

void LoggingPanel::removeDevice(const TDevice *device) {

    const QString &serial = device->serialNumber;
    QVector<DataSourceCheckBox *> *checkBoxes = checkBoxesByDevice.value(serial);

    if (!checkBoxes) {
        // Device not found
        return;
    }

    QVBoxLayout *layout = device->isMath() ? mathChannelsLayout : channelsLayout;
    const int n = checkBoxes->size();

    for (int i = 0; i < n; i++) {
        DataSourceCheckBox *cb = checkBoxes->at(i);
        layout->removeWidget(cb);
        cb->setVisible(false);
        delete cb;
    }

    delete checkBoxes;
    checkBoxesByDevice.remove(serial);
}

void LoggingPanel::setAllSelected(bool selected) {

    QMapIterator<QString, QVector<DataSourceCheckBox *> *> it(checkBoxesByDevice);

    while (it.hasNext()) {
        it.next();
        QVector<DataSourceCheckBox *> *sources = it.value();
        for (int i = 0; i < sources->size(); i++) {
            DataSourceCheckBox *cb = sources->at(i);
            cb->setChecked(selected);
        }
    }
}

void LoggingPanel::setIntervalMultiple(int m) {

    if (m <= 0) {
        m = 1;
    }

    intervalMultiple = m;
    interval = m * globalInterval;

    double s = ((double)interval) / 1000.0;
    intervalSpinBox->setValue(s);

    QSettings settings;
    settings.setValue("logger/sample_interval_multiple", m);
}

void LoggingPanel::updateIntervalSpinBox() {

    double global_s = ((double)globalInterval) / 1000.0;
    double log_s = ((double)interval) / 1000.0;

    intervalSpinBox->setMinimum(global_s);
    intervalSpinBox->setSingleStep(global_s);
    intervalSpinBox->setRange(global_s, global_s * 0x1p14);
    intervalSpinBox->setValue(log_s);

    intervalSpinBox->setEnabled(true);
}

void LoggingPanel::showError(const QString &message, const QString &hint) {

    QMessageBox msgBox;
    msgBox.setWindowTitle("Error");
    msgBox.setText(message);
    msgBox.setInformativeText(hint);
    msgBox.setStandardButtons(QMessageBox::Ok);
    msgBox.setIcon(QMessageBox::Critical);
    msgBox.exec();
}

void LoggingPanel::addMessage(const QString &message) {

    QDateTime now = QDateTime::currentDateTime();
    messageTextEdit->append(now.toString("(yyyy-MM-dd hh:mm:ss)  ") + message);
}

void LoggingPanel::selectAll() { setAllSelected(true); }

void LoggingPanel::selectNone() { setAllSelected(false); }

void LoggingPanel::browseOutputFile() {

    QString path = outputPathLineEdit->text().trimmed();

    if (path.isEmpty()) {
        path = QDir::homePath();
    }

    path = QFileDialog::getSaveFileName(this, tr("Save Text Log"), path, "Text Logs (*.csv)");

    if (path.size()) {
        if (!path.endsWith(".csv", Qt::CaseInsensitive)) {
            path += ".csv";
        }
        outputPathLineEdit->setTextAndSave(path);
    }
}

void LoggingPanel::setGlobalInterval(int ms) {

    QSettings settings;
    globalInterval = ms;

    if (interval > 0) {
        intervalMultiple = interval / ms;
        if (intervalMultiple == 0) {
            intervalMultiple = 1;
        }
        settings.setValue("logger/sample_interval_multiple", intervalMultiple);
    } else {
        intervalMultiple = settings.value("logger/sample_interval_multiple", 1).toInt();
    }

    interval = globalInterval * intervalMultiple;

    if (!thread) {
        updateIntervalSpinBox();
    }
}

void LoggingPanel::onIntervalSelected(double s) {

    double ms = s * 1000.0;
    intervalMultiple = ((int)round(ms)) / globalInterval;
    setIntervalMultiple(intervalMultiple);
}

void LoggingPanel::showLogViewer() {

    if (logViewer) {
        return;
    }

    logViewer = new TextViewer(outputPathLineEdit->text(), "Log File");
    connect(logViewer, &QDialog::finished, this, &LoggingPanel::onLogViewerFinished);
    logViewer->show();
}

void LoggingPanel::onLogViewerFinished(int result) {

    delete logViewer;
    logViewer = nullptr;
}

void LoggingPanel::clearMessages() { messageTextEdit->clear(); }

void LoggingPanel::saveMessages() {

    QString path = QFileDialog::getSaveFileName(this, tr("Save Messages"), "", "Text Files (*.txt)");

    if (path.size()) {
        QFile file;
        file.setFileName(path);
        file.open(QIODevice::WriteOnly | QIODevice::Text);
        file.write(messageTextEdit->toPlainText().toUtf8());
        file.close();
    }
}

void LoggingPanel::startLogging() {

    if (thread != nullptr) {
        return;
    }

    QVector<QString> channelIds;

    QMapIterator<QString, QVector<DataSourceCheckBox *> *> it(checkBoxesByDevice);
    while (it.hasNext()) {
        it.next();
        QVector<DataSourceCheckBox *> *checkBoxes = it.value();
        for (int i = 0; i < checkBoxes->size(); i++) {
            DataSourceCheckBox *cb = checkBoxes->at(i);
            if (cb->isChecked()) {
                channelIds.append(cb->getChannelId());
            }
        }
    }

    if (channelIds.isEmpty()) {
        showError(tr("No sources selected."), tr("Select at least one data source."));
        return;
    }

    QString path = outputPathLineEdit->text().trimmed();

    if (path.isEmpty()) {
        showError(tr("No file selected."), tr("You must select an output file."));
        return;
    }

    int existingFileAction = existingFileComboBox->currentIndex();
    bool append = existingFileAction % 2;
    QFileInfo qf = QFileInfo(path);

    const bool preventSleep = loadEnablePreventSleepDuringLogging();

    if (preventSleep) {
#if defined(Q_OS_WIN)
        addMessage("System will stay awake during logging, but screen may turn off");
#elif defined(Q_OS_OSX)
        addMessage("System will stay awake during logging, but screen may turn off");
#else
        addMessage("Sleep prevention not supported on this OS. Please disable automatic sleep in system settings.");
#endif
    }

    addMessage("Logging started: " + path);

    if (qf.isFile()) {

        if (!qf.isWritable()) {
            addMessage("ERROR: Cannot write to log file");
            showError(tr("File is not writeable."), tr("Select another file or check permissions."));
            return;
        }

        addMessage("Warning: File already exists");

        if (existingFileAction & EXISTS_CONFIRM) {
            QMessageBox msgBox;
            msgBox.setText("File already exists. Continue?");
            msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
            msgBox.setIcon(QMessageBox::Warning);
            int selection = msgBox.exec();
            if (selection != QMessageBox::Yes) {
                addMessage("Logging cancelled by user");
                return;
            }
        }
    }

    startButton->setEnabled(false);
    stopButton->setEnabled(true);

    thread = new LoggingThread(channelIds, path, commentLineEdit->text(), interval, append);
    connect(thread, &LoggingThread::error, this, &LoggingPanel::onThreadError);
    connect(thread, &LoggingThread::written, this, &LoggingPanel::onThreadWrite);
    connect(thread, &LoggingThread::finished, this, &LoggingPanel::onThreadFinished);
    connect(thread, &LoggingThread::finished, thread, &QObject::deleteLater);

    leftColumn->setEnabled(false);
    settingsBox->setEnabled(false);
    outputPathBox->setEnabled(false);

    thread->start();

    statusLabel->setText(tr("Lines written: "));
    counterLabel->setText("0");

    emit loggingStatusChanged(true);
}

void LoggingPanel::stopLogging() {

    if (thread == nullptr) {
        return;
    }

    const bool preventSleep = loadEnablePreventSleepDuringLogging();

    if (preventSleep) {
#if defined(Q_OS_WIN)
        addMessage("System sleep prevention disabled - normal power settings restored");
#elif defined(Q_OS_OSX)
        addMessage("System sleep prevention disabled - normal power settings restored");
#endif
    }

    thread->shutdown();
}

void LoggingPanel::onThreadError(int error) {

    addMessage("ERROR: Cannot write to log file");
    showError("Cannot write to log file.", QString());
}

void LoggingPanel::onThreadWrite(qulonglong n) { counterLabel->setText(QString::number(n)); }

void LoggingPanel::onThreadFinished() {

    addMessage("Logging stopped");

    thread = nullptr;

    counterLabel->clear();
    statusLabel->setText(tr("Not running"));

    stopButton->setEnabled(false);
    startButton->setEnabled(true);

    leftColumn->setEnabled(true);
    settingsBox->setEnabled(true);
    outputPathBox->setEnabled(true);

    updateIntervalSpinBox();

    emit loggingStatusChanged(false);
}
