// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Loader for external plug-ins.
 *
 * Authors:
 *   Moritz Eberl <moritz@semiodesk.com>
 *
 * Copyright (C) 2016 Authors
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include "loader.h"

#include <cstring>

#include <gmodule.h>

#include "dependency.h"
#include "inkscape-version.h"
#include "xml/document.h"         // for Document
#include "xml/node.h"             // for Node

namespace Inkscape {
namespace Extension {

typedef Implementation::Implementation *(*_getImplementation)();
typedef const gchar *(*_getInkscapeVersion)();

bool Loader::load_dependency(Dependency *dep)
{
    GModule *module = nullptr;
    module = g_module_open(dep->get_name(), (GModuleFlags)0);
    if (module == nullptr) {
        return false;
    }
    return true;
}

/**
 * @brief Load the actual implementation of a plugin supplied by the plugin.
 * @param doc The xml representation of the INX extension configuration.
 * @return The implementation of the extension loaded from the plugin.
 */
Implementation::Implementation *Loader::load_implementation(Inkscape::XML::Document *doc)
{
    try {
        
        Inkscape::XML::Node *repr = doc->root();
        Inkscape::XML::Node *child_repr = repr->firstChild();
        
        // Iterate over the xml content
        while (child_repr != nullptr) {
            char const *chname = child_repr->name();
            if (!strncmp(chname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) {
                chname += strlen(INKSCAPE_EXTENSION_NS);
            }
            
            // Deal with dependencies if we have them
            if (!strcmp(chname, "dependency")) {
                Dependency dep = Dependency(child_repr, nullptr); // TODO: Why is "this" not an extension?
                // try to load it
                bool success = load_dependency(&dep);
                if( !success ){
                    // Could not load dependency, we abort
                    const char *res = g_module_error();
                    g_warning("Unable to load dependency %s of plugin %s.\nDetails: %s\n", dep.get_name(), "<todo>", res);
                    return nullptr;
                }
            } 

            // Found a plugin to load
            if (!strcmp(chname, "plugin")) {
                
                // The name of the plugin is actually the library file we want to load
                if (const gchar *name = child_repr->attribute("name")) {
                    GModule *module = nullptr;
                    _getImplementation GetImplementation = nullptr;
                    _getInkscapeVersion GetInkscapeVersion = nullptr;
                    
                    // build the path where to look for the plugin
                    gchar *path = g_build_filename(_baseDirectory.c_str(), name, (char *) nullptr);
                    module = g_module_open(path, G_MODULE_BIND_LOCAL);
                    g_free(path);
                    
                    if (module == nullptr) {
                        // we were not able to load the plugin, write warning and abort
                        const char *res = g_module_error();
                        g_warning("Unable to load extension %s.\nDetails: %s\n", name, res);
                        return nullptr;
                    }

                    // Get a handle to the version function of the module
                    if (g_module_symbol(module, "GetInkscapeVersion", (gpointer *) &GetInkscapeVersion) == FALSE) {
                        // This didn't work, write warning and abort
                        const char *res = g_module_error();
                        g_warning("Unable to load extension %s.\nDetails: %s\n", name, res);
                        return nullptr;
                    }
                    
                    // Get a handle to the function that delivers the implementation
                    if (g_module_symbol(module, "GetImplementation", (gpointer *) &GetImplementation) == FALSE) {
                        // This didn't work, write warning and abort
                        const char *res = g_module_error();
                        g_warning("Unable to load extension %s.\nDetails: %s\n", name, res);
                        return nullptr;
                    }
                    
                    // Get version and test against this version
                    const gchar* version = GetInkscapeVersion();
                    if( strcmp(version, version_string) != 0) {
                        // The versions are different, display warning.
                        g_warning("Plugin was built against Inkscape version %s, this is %s. The plugin might not be compatible.", version, version_string);
                    }
                    
                    
                    Implementation::Implementation *i = GetImplementation();
                    return i;
                }
            } 

            child_repr = child_repr->next();
        }
    } catch (std::exception &e) {
        g_warning("Unable to load extension.");
    }
    return nullptr;
}

} // namespace Extension
} // namespace Inkscape

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim:filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99:
