/*****************************************************************************
 * $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 "CppHotPlugAction.h"
#include "CppHotPlugActionExtension.h"

#include "Core.h"
#include "ActionWidget.h"
#include "Application.h"
#include "Log.h"

#include <TransformEngine.h>
#include <QFileInfo>
#include <QDir>

namespace camitk {

// ------------------- Constructor -------------------
CppHotPlugAction::CppHotPlugAction(HotPlugActionExtension* extension, const VariantDataModel& data) : HotPlugAction(extension, data) {
    TransformEngine transformEngine;
    QJsonObject dataObject = data.getValue().toJsonObject();
    actionLibName = transformEngine.transformToString("$joinKebabCase(name)$", dataObject);

    // setup UserActionLib functors
    updateSucceeded = false;
    CppHotPlugAction::update();
}

// ------------------- init -------------------
bool CppHotPlugAction::init() {
    // call user-defined init()
    if (initFunctor != nullptr) {
        initFunctor(this);
    }
    return true;
}

// ------------------- needsUpdate -------------------
bool CppHotPlugAction::needsUpdate() {
    if (!updateSucceeded) {
        return true;
    }

    QDateTime lastModified = QFileInfo(actionLib.fileName()).lastModified();
    return (actionLib.fileName().isEmpty() || (lastModified > lastLoaded));
}

// ------------------- update -------------------
bool CppHotPlugAction::update() {
    // Do nothing if the library was not changed since the last update or
    // if it being rebuilt
    if (!CppHotPlugAction::needsUpdate()) {
        updateSucceeded = true;
        return updateSucceeded;
    }

    // force reload
    if (actionLib.isLoaded()) {
        actionLib.unload();
    }

    // reset functors
    processFunctor = nullptr;
    initFunctor = nullptr;
    getUIFunctor = nullptr;
    targetDefinedFunctor = nullptr;
    parameterChangedFunctor = nullptr;

    // location of the UserActionLib is relative to the CamiTK extension file
    QString sourcePath = QFileInfo(getExtension()->getLocation()).absolutePath();
    QDir buildExtensionDir;
    buildExtensionDir.setPath(sourcePath + "/build/lib/" + QString(Core::shortVersion()) + "/actions");

    // There are no prefix for libraries on Windows
    QString libPrefix = "";
    // There might be a debug postfix on Windows
    QString debugPostfix = "";
#if defined(_WIN32) || defined(_WIN64)
    if (Core::isDebugBuild()) {
        debugPostfix = QString(Core::debugPostfix());
    }
#else
    // but on Unix and Apple, all libraries are prefixed with "lib"
    libPrefix = "lib";
#endif
    QString libFilePath = buildExtensionDir.filePath(libPrefix + actionLibName + debugPostfix);

    // QLibrary will look for { "*.so", "*.dll", "*.dylib"} depending on the OS
    actionLib.setFileName(libFilePath);

    // load and update functors
    if (actionLib.load()) {
        // resolve symbols
        processFunctor = (ProcessFunction) actionLib.resolve("process");

        // check
        if (processFunctor == nullptr) {
            CAMITK_WARNING(QString(R"(Error loading %1: %2\nCannot find mandatory \"process(..)\" method for action \"%3\".
            This action will therefore not be able to do anything. Please verify the CamiTKActionImplementation section and check that the method process(..) is defined with the proper signature: \"Action::ApplyStatus process(Action* self)\")").arg(actionLib.fileName()).arg(actionLib.errorString()).arg(getName()));
            updateSucceeded = false;
        }
        else {
            initFunctor = (VoidFunction) actionLib.resolve("init");
            getUIFunctor = (GetUIFunction) actionLib.resolve("getUI");
            targetDefinedFunctor = (VoidFunction) actionLib.resolve("targetDefined");
            parameterChangedFunctor = (ParameterChangedFunction) actionLib.resolve("parameterChanged");
            dynamic_cast<CppHotPlugActionExtension*>(hotPlugExtension)->watchActionLibrary(actionLib.fileName(), this);
            updateSucceeded = true;
            lastLoaded = QDateTime::currentDateTime();
        }
    }
    else {
        CAMITK_INFO(QString("%1 (%2) cannot be loaded:\n%3\nTry to rebuild the extension.").arg(libFilePath).arg(actionLib.fileName()).arg(actionLib.errorString()));
        updateSucceeded = false;
    }

    return updateSucceeded;
}

// ------------------- getWidget -------------------
QWidget* CppHotPlugAction::getWidget() {
    // Get UI, then call targetDefined
    if (actionWidget == nullptr) {
        // first call: check the user defined widget
        if (getUIFunctor != nullptr) {
            actionWidget = getUIFunctor(this);
        }
        if (actionWidget != nullptr) {
            // call UserAction method
            if (targetDefinedFunctor  != nullptr) {
                targetDefinedFunctor(this);
            }
            return actionWidget;
        }
        else {
            // no user defined UI, use default
            QWidget* defaultWidget = Action::getWidget();
            // call UserAction method
            if (targetDefinedFunctor  != nullptr) {
                targetDefinedFunctor(this);
            }
            return defaultWidget;
        }
    }
    else {
        // call UserAction method
        if (targetDefinedFunctor  != nullptr) {
            targetDefinedFunctor(this);
        }
        ActionWidget* defaultActionWidget = dynamic_cast<ActionWidget*>(actionWidget);
        if (defaultActionWidget != nullptr) {
            // this is a default action widget, make sure the widget has updated targets
            defaultActionWidget->update();
        }
        return actionWidget;
    }
}

// ------------------- apply -------------------
Action::ApplyStatus CppHotPlugAction::apply() {
    // set waiting cursor
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

    // call user-defined process() if defined
    Action::ApplyStatus applyStatus = Action::ERROR;
    if (processFunctor != nullptr) {
        applyStatus = processFunctor(this);
    }

    // restore normal cursor
    QApplication::restoreOverrideCursor();

    return applyStatus;
}


// ---------------------- parameterChangedEvent ----------------------------
void CppHotPlugAction::parameterChangedEvent(QString parameterName) {
    // call user-defined parameterChanged()
    if (parameterChangedFunctor != nullptr) {
        parameterChangedFunctor(this, parameterName);
    }
}

} // namespace camitk