#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),
    pythonRunner(&pythonOutput),
    currentFilePath(""),
    isFileModified(false),
    uiBlocked(false)
{
        ui->setupUi(this);

        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 venv preparation signals
        connect(&pythonRunner, &PythonRunner::venvPreparationProgress, &pythonOutput, [this](const QString& message) {
            pythonOutput.append(message);
            pythonOutput.moveCursor(QTextCursor::End);
        });
        connect(&pythonRunner, &PythonRunner::venvPreparationFinished, this, &MainWindow::onVenvPreparationFinished);

        // Connect Run and Stop buttons
        connect(ui->pushButtonRun, &QPushButton::clicked, this, &MainWindow::runScript);
        connect(ui->pushButtonStop, &QPushButton::clicked, this, &MainWindow::stopScript);
        connect(&pythonRunner, &PythonRunner::scriptFinished, this, &MainWindow::stopScript);
        connect(&pythonRunner, &PythonRunner::venvPreparationProgress, this, &MainWindow::onVenvPreparationProgress);

        enumerateDevices();
        generateExample();
}

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

void MainWindow::readState()
{
    for (auto& multiplexer : multiplexers) {
        channel_t channelstate = eismultiplexer_get_connected(multiplexer.second.get());
        for (auto& channel : channels) {
            for(size_t i = 0; i < 16; ++i) {
                channel_t mask = static_cast<channel_t>(1 << i);
                channel->onOtherChannelStateChanged(multiplexer.first, i, channelstate & mask);
            }
        }

        int triggerCount = eismultiplexer_get_trigger_count(multiplexer.second.get());
        for(int i = 0; i < triggerCount; ++i) {
            bool level;
            trigger_state_t state;
            int ret = eismultiplexer_get_trigger_state(multiplexer.second.get(), i, &state, &level);
            if (ret < 0) {
                for (auto& trigger : triggers)
                    trigger->updateSate(multiplexer.first, i, state);
            }
        }
    }
}

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 (ui->scrollArea->layout())
            delete ui->scrollArea->layout();
        QVBoxLayout* channelLayout = new QVBoxLayout(ui->scrollArea);

        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)
                {
                        multiplexers.push_back({serial, multiplexer});
                        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);
                                        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);
                                        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;
                }
        }

        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);
                }
            }
        }

        readState();

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

        free(serials);
}

void MainWindow::runScript() {
    QString scriptContent = codeEditor.toPlainText();

    // Check if we need to prepare the venv
    if (!pythonRunner.isVenvReady()) {
        // Block UI for venv preparation
        blockUIForVenvPreparation();
        pythonRunner.runScript(scriptContent);
    } else {
        // Normal script execution flow
        pythonRunner.runScript(scriptContent);
        ui->pushButtonRun->setEnabled(false);
        ui->pushButtonStop->setEnabled(true);
        codeEditor.setEnabled(false);
        ui->scrollArea->setEnabled(false);
        disconnectDevices();
    }
}

void MainWindow::onVenvPreparationFinished() {
    // Unblock UI when venv preparation is complete
    unblockUI();

    // If venv is ready, enable the run button and run the script if needed
    if (pythonRunner.isVenvReady()) {
        ui->pushButtonRun->setEnabled(true);

        // If we have a script to run (from the first runScript call), run it now
        if (uiBlocked) {
            QString scriptContent = codeEditor.toPlainText();
            pythonRunner.runScript(scriptContent);
            ui->pushButtonRun->setEnabled(false);
            ui->pushButtonStop->setEnabled(true);
            codeEditor.setEnabled(false);
            ui->scrollArea->setEnabled(false);
            disconnectDevices();
        }
    }
}

void MainWindow::stopScript() {
    pythonRunner.stopScript();
    channels.clear();
    triggers.clear();
    ui->pushButtonRun->setEnabled(true);
    ui->pushButtonStop->setEnabled(false);
    codeEditor.setEnabled(true);
    ui->scrollArea->setEnabled(true);
    enumerateDevices();
}

void MainWindow::onVenvPreparationProgress(const QString& message) {
    // Update the output widget with progress
    pythonOutput.append(message);
    pythonOutput.moveCursor(QTextCursor::End);


}

void MainWindow::blockUIForVenvPreparation() {

    uiBlocked = true;
    ui->pushButtonRun->setEnabled(false);
    ui->pushButtonStop->setEnabled(false);
    codeEditor.setEnabled(false);
    ui->scrollArea->setEnabled(false);
    pythonOutput.append("Preparing Python environment. Please wait...\n");
    pythonOutput.moveCursor(QTextCursor::End);
}

void MainWindow::unblockUI() {
    if (uiBlocked) {
        uiBlocked = false;
        // UI will be re-enabled when script execution starts
    }
}

void MainWindow::disconnectDevices()
{
    for(auto& multiplexer : multiplexers)
        eismultiplexer_disconnect(multiplexer.second.get());
    multiplexers.clear();
}

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);
}

