#include <eismultiplexer.h>
#include <QMessageBox>
#include <QMessageBox>
#include <set>
#include <QFileDialog>
#include <QTextStream>
#include <QFile>
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "triggerwidget.h"

MainWindow::MainWindow(QWidget *parent):
    QMainWindow(parent),
    ui(new Ui::MainWindow),
    codeEditor(this),
    pythonOutput(this),
    currentFilePath(""),
    isFileModified(false),
    pythonRunner(nullptr)
{
        ui->setupUi(this);
        enumerateDevices();

        codeEditor.setAutoIndentation(true);
        codeEditor.setAutoParentheses(true);
        codeEditor.setHighlighter(&highligter);
        codeEditor.setCompleter(&completer);
        QFont font("Monospace");
        font.setStyleHint(QFont::TypeWriter);
        codeEditor.setFont(font);

        ui->codeLayout->addWidget(&codeEditor, 1);

        pythonOutput.setReadOnly(true);
        ui->codeLayout->addWidget(&pythonOutput);

        // Set up keyboard shortcuts
        ui->actionOpen->setShortcut(QKeySequence::Open);
        ui->actionSave->setShortcut(QKeySequence::Save);
        ui->actionSave_As->setShortcut(QKeySequence::SaveAs);
        ui->actionQuit->setShortcut(QKeySequence::Quit);

        connect(ui->actionQuit, &QAction::triggered, this, [this]() {close();});
        connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::onActionOpenTriggered);
        connect(ui->actionSave, &QAction::triggered, this, &MainWindow::onActionSaveTriggered);
        connect(ui->actionSave_As, &QAction::triggered, this, &MainWindow::onActionSaveAsTriggered);

        // Connect text changed signal to track modifications
        connect(&codeEditor, &QTextEdit::textChanged, this, [this]() {
            isFileModified = true;
            updateStatus();
        });

        // Connect Run and Stop buttons
        pythonRunner = new PythonRunner(&pythonOutput, this);
        connect(ui->pushButtonRun, &QPushButton::clicked, this, &MainWindow::onPushButtonRunClicked);
        connect(ui->pushButtonStop, &QPushButton::clicked, this, &MainWindow::onPushButtonStopClicked);
}

MainWindow::~MainWindow()
{
        delete ui;
}

void MainWindow::updateStatus()
{
    if (!currentFilePath.isEmpty()) {
        QString status = "EisMultiplexer-Qt";
        status = QString("%1").arg(currentFilePath);
        if (isFileModified) {
            status += " - [Unsaved Changes]";
        }
        ui->statusbar->showMessage(status);
    }
}

void MainWindow::onActionOpenTriggered()
{
    QString filePath = QFileDialog::getOpenFileName(this, tr("Open Python Script"),
                                                    "",
                                                    tr("Python Files (*.py);;All Files (*)"));

    if (filePath.isEmpty()) {
        return;
    }

    QFile file(filePath);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QMessageBox::warning(this, tr("Error"), tr("Could not open file: %1").arg(filePath));
        return;
    }

    QTextStream in(&file);
    QString content = in.readAll();
    file.close();

    codeEditor.setPlainText(content);
    currentFilePath = filePath;
    isFileModified = false;
    updateStatus();
}

void MainWindow::onActionSaveTriggered()
{
    if (currentFilePath.isEmpty()) {
        onActionSaveAsTriggered();
        return;
    }

    QFile file(currentFilePath);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QMessageBox::warning(this, tr("Error"), tr("Could not save file: %1").arg(currentFilePath));
        return;
    }

    QTextStream out(&file);
    out << codeEditor.toPlainText();
    file.close();

    isFileModified = false;
    updateStatus();
}

void MainWindow::onActionSaveAsTriggered()
{
    QString filePath = QFileDialog::getSaveFileName(this, tr("Save Python Script"),
                                                    "",
                                                    tr("Python Files (*.py);;All Files (*)"));

    if (filePath.isEmpty()) {
        return;
    }

    QFile file(filePath);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QMessageBox::warning(this, tr("Error"), tr("Could not save file: %1").arg(filePath));
        return;
    }

    QTextStream out(&file);
    out << codeEditor.toPlainText();
    file.close();

    currentFilePath = filePath;
    isFileModified = false;
    updateStatus();
}

void MainWindow::enumerateDevices()
{
        size_t count = 0;
        uint16_t* serials = eismultiplexer_list_available_devices(&count);

        if (!serials || count == 0)
        {
                QMessageBox::warning(nullptr, tr("No Devices Found"),
                                     tr("No EIS multiplexer devices were found. Please connect a device and try again."));
                qWarning() << "No EIS multiplexer devices found";
                exit(0);
                return;
        }

        // First pass: create all widgets without connecting signals
        for (size_t i = 0; i < count; i++)
        {
                uint16_t serial = serials[i];
                std::shared_ptr<struct eismultiplexer> multiplexer(new struct eismultiplexer);
                int ret = eismultiplexer_connect(multiplexer.get(), serial);
                if (ret == 0)
                {
                        uint16_t channelCount = 0;
                        qDebug()<<"Adding channels from device "<<serial;
                        if (eismultiplexer_get_channel_count(multiplexer.get(), &channelCount) >= 0)
                        {
                                for (uint16_t channel = 0; channel < channelCount; channel++)
                                {
                                        std::shared_ptr<ChannelWidget> widget(new ChannelWidget(serial, channel, multiplexer));
                                        qDebug()<<"Added widget from device "<<serial<<" channel "<<channel;
                                        channels.push_back(widget);
                                        ui->channelLayout->addWidget(widget.get());
                                }
                        }

                        // Add trigger widgets
                        int triggerCount = eismultiplexer_get_trigger_count(multiplexer.get());
                        if (triggerCount > 0)
                        {
                                qDebug()<<"Adding triggers from device "<<serial;
                                for (int trigger = 0; trigger < triggerCount; trigger++)
                                {
                                        std::shared_ptr<TriggerWidget> triggerWidget(new TriggerWidget(serial, trigger, multiplexer));
                                        qDebug()<<"Added trigger widget from device "<<serial<<" trigger "<<trigger;
                                        triggers.push_back(triggerWidget);
                                        ui->channelLayout->addWidget(triggerWidget.get());
                                }
                        }
                }
                else
                {
                        QMessageBox::warning(this, tr("Connection Failed"),
                                             tr("Failed to connect to device with serial %1").arg(serial));
                        qWarning()<<"Failed to connect to device with serial"<<serial<<"eismultiplexer_connect returned"<<ret;
                }
        }
        ui->channelLayout->addStretch();

        // Second pass: populate gang combos and connect signals for channels
        for (const auto& widget : channels) {
            // Populate gang combo with all other channels
            for (const auto& otherWidget : channels) {
                if (widget->getDeviceSerial() != otherWidget->getDeviceSerial() ||
                    widget->getChannelNumber() != otherWidget->getChannelNumber()) {
                    QString channelText = QString::asprintf("%04u, %u",
                                                           otherWidget->getDeviceSerial(),
                                                           otherWidget->getChannelNumber());
                    widget->getGangCombo()->addItem(channelText);
                }
            }

            // Connect state change signals
            for (const auto& otherWidget : channels) {
                if (widget.get() != otherWidget.get()) {
                    connect(otherWidget.get(), &ChannelWidget::channelStateChanged,
                            widget.get(), &ChannelWidget::onOtherChannelStateChanged);
                }
            }
        }

        ui->statusbar->showMessage("Ready");

        free(serials);
        generateExample();
}

void MainWindow::onPushButtonRunClicked() {
    QString scriptContent = codeEditor.toPlainText();
    pythonRunner->runScript(scriptContent);
    ui->pushButtonRun->setEnabled(false);
    ui->pushButtonStop->setEnabled(true);
}

void MainWindow::onPushButtonStopClicked() {
    pythonRunner->stopScript();
    ui->pushButtonRun->setEnabled(true);
    ui->pushButtonStop->setEnabled(false);
}

void MainWindow::generateExample()
{
    QString example =
        "# This is an example script to show you how\n# to drive eismultiplexer using the python api\n"
        "import eismultiplexer as multi\n"
        "from time import sleep\n\n"
        "# First initalize the device(s)\n";

    std::set<uint16_t> serials;
    for (size_t i = 0; i < channels.size(); ++i)
        serials.insert(channels[i]->getDeviceSerial());

    size_t i = 0;
    for (uint16_t serial : serials)
    {
        example.append(QString("multiplexer_") + QString::number(i) + " multi.Multiplexer(serial=" + QString::number(serial) + ")\n");
        ++i;
    }

    example.append("\nprint('\\nListing the nummber of channels per unit')\n");
    for (size_t i = 0; i < serials.size(); ++i)
    {
        QString printLine = "print(f'Found unit with serial number {" + QString::number(channels[i]->getDeviceSerial()) + "} and {multiplexer_" + QString::number(i) + ".getChannelCount()} channels')\n";
        example.append(printLine);
    }

    example.append("\nprint('Connecting the first and second channel on the first unit')\n");
    example.append("multiplexer_0.connectChannel(multi.Channel.A)\n");
    example.append("multiplexer_0.connectChannel(multi.Channel.B)\n\n");
    example.append("print('Waiting for half a second for something to happen')\n");
    example.append("sleep(0.5)\n\n");
    example.append("print('Disconnect first channel')\n");
    example.append("multiplexer_0.disconnectChannel(multi.Channel.A)\n\n");
    example.append("print('Waiting up to 5000 milliseconds for a trigger')\n");
    example.append("multiplexer_0.setTriggerState(0, multi.TriggerState.INPUT)\n");
    example.append("multiplexer_0.waitTrigger(0, multi.TriggerState.HIGHLEVEL, 5000)\n\n");
    example.append("print('Disconnecting all channels')\n");
    example.append("multiplexer_0.clear()\n");
    codeEditor.setText(example);
}

