/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/

#include "CamiTKExtensionEditor.h"

#include "HotPlugExtensionManager.h"

// CamiTK includes
#include <Application.h>
#include <ExtensionManager.h>
#include <Core.h>
#include <Action.h>
#include <Log.h>

#include <TransformEngine.h>

// Qt
#include <QFileDialog>
#include <QMessageBox>
#include <QPushButton>
#include <QLabel>
#include <QTimer>
#include <QCloseEvent>
#include <QTemporaryDir>

using namespace camitk;

// -------------------- constructor --------------------
CamiTKExtensionEditor::CamiTKExtensionEditor(const QString& camitkFilePath, const QString& newExtensionName, const QString& language, const QString& cppGenerationType, QWidget* parent) : QWidget(parent) {
    this->camitkFilePath = camitkFilePath;
    cmakeProjectManagerPresenter = nullptr;
    hasUnsavedChanges = false;

    // extension editor
    extensionPresenter = new CamiTKExtensionModelPresenter(this->camitkFilePath, newExtensionName, language, cppGenerationType);
    connect(extensionPresenter, &CamiTKExtensionModelPresenter::showMessage, this, &CamiTKExtensionEditor::showMessage);
    connect(extensionPresenter, &CamiTKExtensionModelPresenter::dataModelUpdated, this, &CamiTKExtensionEditor::dataModelUpdated);

    // Check/Save/close button
    QPushButton* saveButton = new QPushButton("Save", this);
    connect(saveButton, &QPushButton::clicked, this, &CamiTKExtensionEditor::saveClicked);

    QPushButton* closeButton = new QPushButton("Close", this);
    connect(closeButton, &QPushButton::clicked, this, &CamiTKExtensionEditor::closeClicked);

    initializeButton = new QPushButton("Initialize", this);
    initializeButton->setToolTip("Click to initialize or update the extension source code and CMake files.");
    connect(initializeButton, &QPushButton::clicked, this, &CamiTKExtensionEditor::initializeClicked);
    initializeButton->setEnabled(false);

    registerButton = new QPushButton("Register", this);
    registerButton->setToolTip("Click to register the extension.");
    connect(registerButton, &QPushButton::clicked, this, &CamiTKExtensionEditor::registerClicked);
    registerButton->setEnabled(false);

    QSpacerItem* spacer = new QSpacerItem(10, 10, QSizePolicy::Expanding, QSizePolicy::Fixed);
    QHBoxLayout* buttonLayout = new QHBoxLayout;

    buttonLayout->addWidget(initializeButton);
    buttonLayout->addWidget(registerButton);
    buttonLayout->addItem(spacer);
    buttonLayout->addWidget(saveButton);
    buttonLayout->addWidget(closeButton);

    statusBarLabel = new QLabel("Ready", this);
    statusBarTimer = new QTimer(this);
    statusBarTimer->setSingleShot(true);
    statusBarTimer->start(5000);

    // Clear the status bar when the timer is out
    connect(statusBarTimer, &QTimer::timeout, [this]() {
        statusBarLabel->setText("");
        statusBarLabel->setStyleSheet(""); // in case it was a warning
    });

    QVBoxLayout* mainLayout = new QVBoxLayout(this);
    mainLayout->addWidget(extensionPresenter);
    mainLayout->addLayout(buttonLayout);
    mainLayout->addWidget(statusBarLabel);

    setLayout(mainLayout);

    setAttribute(Qt::WA_DeleteOnClose);
    setWindowIcon(QIcon(":/camiTKIcon"));
    setWindowTitle("CamiTK Extension Editor [" + this->camitkFilePath + "]");
    resize(900, 800);

    updateButtonStatus(registerButton->isEnabled());
}

// -------------------- Destructor --------------------
CamiTKExtensionEditor::~CamiTKExtensionEditor() {
}

// -------------------- closeEvent --------------------
void CamiTKExtensionEditor::closeEvent(QCloseEvent* event) {
    // check if save is required
    if (hasUnsavedChanges) {
        QMessageBox::StandardButton reply = QMessageBox::warning(this, "Unsaved Changes",
                                            tr("You have unsaved changes in '%1'. Do you want to save them before closing the editor?").arg(camitkFilePath),
                                            QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
        if (reply == QMessageBox::Cancel) {
            // cancel the closure event and stop processing here
            event->ignore();
            return;
        }
        else {
            if (reply == QMessageBox::Yes) {
                saveClicked();
            }
        }
    }

    if (cmakeProjectManagerPresenter) {
        // only close if it was not already closed by the user
        cmakeProjectManagerPresenter->close();
    }

    emit extensionGeneratorPresenterClosed();

    // Ensure the event loop processes events
    // so that the extensionGeneratorPresenterClosed signal is processed
    // by this widget owners before closing the widget
    // (see OpenExtensionFile source code action for example)
    QCoreApplication::processEvents();

    // Accept the event to proceed with closing the widget
    event->accept();

    // this instance will be deleted because of the Qt::WA_DeleteOnClose attribute
}

// -------------------- dataModelUpdated --------------------
void CamiTKExtensionEditor::dataModelUpdated() {
    setWindowTitle("CamiTK Extension Editor [" + camitkFilePath + "] *");
    hasUnsavedChanges = true;
}

// -------------------- showMessage --------------------
void CamiTKExtensionEditor::showMessage(const QString& message, int durationMilliseconds, bool warning) {
    statusBarLabel->setText(message);
    if (warning) {
        // generate the highlighting stylesheet using the user palette and specific role colors
        QPalette palette = statusBarLabel->palette();
        QColor highlightColor = palette.color(QPalette::Highlight);
        QColor backgroundColor = QColor(255, 220, 168); // #ffdca8
        QString stylesheet = QString("border: 2px solid %1; background-color: %2;").arg(highlightColor.name()).arg(backgroundColor.name());
        statusBarLabel->setStyleSheet(stylesheet);
    }
    else {
        statusBarLabel->setStyleSheet("");
    }
    statusBarTimer->start(durationMilliseconds);
}

// -------------------- saveClicked --------------------
void CamiTKExtensionEditor::saveClicked() {
    QFile file(camitkFilePath);

    QMessageBox::StandardButton reply = QMessageBox::Yes;
    QString directory = Application::getLastUsedDirectory().absolutePath();

    //-- Check if the file already exists
    if (camitkFilePath.isEmpty()) {
        reply = QMessageBox::No;
    }
    else if (file.exists()) {
        reply = QMessageBox::warning(this, "File Exists",
                                     tr("The CamiTK extension '%1' already exists. Do you want to overwrite it?").arg(QFileInfo(camitkFilePath).fileName()),
                                     QMessageBox::Yes | QMessageBox::No);
        directory = QFileInfo(camitkFilePath).dir().canonicalPath();
    }

    if (reply == QMessageBox::No) {
        // Ask the user for an alternative name
        QString fileName = QFileDialog::getSaveFileName(this, tr("Save File As..."), directory, tr("CamiTK Extension Files (*.camitk)"));

        if (!fileName.isEmpty()) {
            Application::setLastUsedDirectory(QFileInfo(fileName).absoluteDir());
            camitkFilePath = fileName;
            // Recursively call saveToFile with the alternative name
            saveClicked();
            return;
        }
        else {
            return;
        }
    }

    if (QFile(camitkFilePath).exists()) {
        //-- First create a backup file
        QString backupFileName = camitkFilePath + ".old";
        QDateTime modificationDate = QFileInfo(camitkFilePath).lastModified();

        // Remove the backup file and copy the new version
        if (QFile(backupFileName).exists()) {
            QFile::remove(backupFileName);
        }

        if (QFile::copy(camitkFilePath, backupFileName)) {
            // preserve the modification date of the copied file
            QFile destinationFile(backupFileName);
            if (destinationFile.open(QIODevice::ReadOnly)) {
                // to use setFileTime the file must be opened (see documentation)
                destinationFile.setFileTime(modificationDate, QFileDevice::FileModificationTime);
                destinationFile.close();
            }
            showMessage(tr("Backup file saved in %1").arg(backupFileName));
        }
        else {
            CAMITK_WARNING(tr("Failed to create backup file %1").arg(backupFileName));
        }
    }

    //-- Then write the extension JSON
    extensionPresenter->saveExtensionFile(QFile(camitkFilePath));
    setWindowTitle("CamiTK Extension Editor [" + camitkFilePath + "]");
    hasUnsavedChanges = false;
    showMessage("Extension saved in " + camitkFilePath);
    updateButtonStatus(registerButton->isEnabled());
}

// -------------------- closeClicked --------------------
void CamiTKExtensionEditor::closeClicked() {
    showMessage(tr("Closing..."));
    close();
}

// -------------------- initializeClicked --------------------
void CamiTKExtensionEditor::initializeClicked() {
    if (hasUnsavedChanges) {
        QMessageBox::StandardButton reply = QMessageBox::warning(this, "Unsaved Changes",
                                            tr("You have unsaved changes in '%1'. Do you want to save them before updating the source code?").arg(camitkFilePath),
                                            QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
        if (reply == QMessageBox::Cancel) {
            return;
        }
        else {
            if (reply == QMessageBox::Yes) {
                saveClicked();
                // if the user cancel the save, stop processing here
                if (hasUnsavedChanges) {
                    showMessage(tr("Initialization aborted because of unsaved changes"));
                    return;
                }
            }
        }
    }

    if (cmakeProjectManagerPresenter == nullptr) {
        showMessage(tr("Starting to %1...").arg(initializeButton->text().toLower()));
        cmakeProjectManagerPresenter = new CMakeProjectManagerPresenter(camitkFilePath, {CMakeProjectManager::Check_System, CMakeProjectManager::Generate_Source_Files}, nullptr);

        // update register and open in ide buttons
        connect(cmakeProjectManagerPresenter, &CMakeProjectManagerPresenter::allStagesFinished, this, [ this ](bool success) {
            // if python setup the environment as well
            if (extensionPresenter->isPython()) {
                // success = success && PythonManager::createVirtualEnv(QString virtualEnvRootPath);
                // + install packages
            }
            updateButtonStatus(success);
        });
        connect(cmakeProjectManagerPresenter, &CMakeProjectManagerPresenter::cMakeProjectManagerPresenterClosed, this, [ this ]() {
            // as CMakeProjectManagerPresenter is created using the Qt::WA_DeleteOnClose attribute
            // it will be deleted automatically. Resetting to nullptr will avoid double delete
            // when the ExtensionGeneratorPresenter is closed (see closeClicked())
            cmakeProjectManagerPresenter = nullptr;
        });
    }

    cmakeProjectManagerPresenter->show();
    cmakeProjectManagerPresenter->raise();
}

// -------------------- registerClicked --------------------
void CamiTKExtensionEditor::registerClicked() {
    blockSignals(true); // to avoid multiple click register/unregister pilling up
    showMessage(tr("Starting to %1...").arg(registerButton->text().toLower()));
    Action* registerOrUnregisterExtensionAction = Application::getAction(registerButton->text() + " Extension");
    if (registerOrUnregisterExtensionAction) {
        // should always be there!
        registerOrUnregisterExtensionAction->setParameterValue("CamiTK File", camitkFilePath);
        registerOrUnregisterExtensionAction->trigger();
    }
    showMessage(tr("%1 finished").arg(registerButton->text()));
    updateButtonStatus(registerButton->isEnabled());
    blockSignals(false);
}

// -------------------- updateButtonStatus --------------------
void CamiTKExtensionEditor::updateButtonStatus(bool initializationWasSuccessful) {
    // if empty, buttons can not be clicked
    initializeButton->setEnabled(!camitkFilePath.isEmpty());
    registerButton->setEnabled(!camitkFilePath.isEmpty());

    if (!camitkFilePath.isEmpty()) {
        // For C++ extension, if extension was already initialized, the CMakeLists.txt exists
        // and the button should show "Update" instead of "Initialize"
        // For Python extension, automatically go to the update stage
        QDir dir = QFileInfo(camitkFilePath).absoluteDir();
        QString cmakeFilePath = dir.absoluteFilePath("CMakeLists.txt");
        if (extensionPresenter->isPython() || QFileInfo(cmakeFilePath).exists()) {
            initializeButton->setText("Update");
        }
        else {
            initializeButton->setText("Initialize");
        }

        // icon depends on the initialization state
        QIcon stateIcon = this->style()->standardIcon(initializationWasSuccessful ? QStyle::SP_DialogApplyButton : QStyle::SP_BrowserReload);
        initializeButton->setIcon(stateIcon);

        // check if the extension is already registered
        bool isRegistered = HotPlugExtensionManager::getRegisteredExtensionFiles().contains(camitkFilePath);
        if (isRegistered) {
            registerButton->setText("Unregister");
        }
        else {
            registerButton->setText("Register");
        }

        // button text depends on state
        // register is enabled only if initialization was done successfully and extension is of C++HotPlug generation type or is is a python extension
        registerButton->setEnabled((initializationWasSuccessful && extensionPresenter->isHotPlug()) || extensionPresenter->isPython());
    }
}