/*
 *  $Id: module-synth.c 28772 2025-11-03 18:02:12Z yeti-dn $
 *  Copyright (C) 2025 David Necas (Yeti).
 *  E-mail: yeti@gwyddion.net.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any
 *  later version.
 *
 *  This program 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 General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along with this program; if not, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include <string.h>
#include "libgwyddion/macros.h"
#include "libgwyddion/utils.h"
#include "libgwyapp/module-synth.h"
#include "libgwyapp/module-internal.h"

/* The curve map function information */
typedef struct {
    const gchar *name;
    const gchar *menu_path;
    const gchar *icon_name;
    const gchar *tooltip;
    GwyRunModeFlags modes;
    GwySynthFunc func;
} GwySynthFuncInfo;

static GHashTable *synth_funcs = NULL;
static GPtrArray *call_stack = NULL;

/**
 * gwy_synth_func_register:
 * @name: Name of function to register.  It should be a valid identifier and if a module registers only one function,
 *        module and function names should be the same.
 * @func: The function itself.
 * @menu_path: Menu path under Synthetic menu.  The menu path should be marked translatabe, but passed untranslated
 *             (to allow merging of translated and untranslated submenus).
 * @icon_name: Stock icon id for toolbar.
 * @modes: Supported run modes.  Dynthetic data functions can have two run modes: %GWY_RUN_IMMEDIATE (no questions
 *         asked) and %GWY_RUN_INTERACTIVE (a modal dialog with parameters).
 * @tooltip: Tooltip for this function.
 *
 * Registers a synthetic data function.
 *
 * Note: the string arguments are not copied as modules are not expected to vanish.  If they are constructed
 * (non-constant) strings, do not free them. Should modules ever become unloadable they will get a chance to clean-up.
 *
 * Returns: Normally %TRUE; %FALSE on failure.
 **/
gboolean
gwy_synth_func_register(const gchar *name,
                        GwySynthFunc func,
                        const gchar *menu_path,
                        const gchar *icon_name,
                        GwyRunModeFlags modes,
                        const gchar *tooltip)
{
    GwySynthFuncInfo *func_info;

    g_return_val_if_fail(name, FALSE);
    g_return_val_if_fail(func, FALSE);
    g_return_val_if_fail(menu_path, FALSE);
    g_return_val_if_fail(modes & GWY_RUN_MASK, FALSE);
    gwy_debug("name = %s, menu path = %s, modes = %02x, func = %p", name, menu_path, modes, func);

    if (!synth_funcs) {
        gwy_debug("Initializing...");
        synth_funcs = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
        call_stack = g_ptr_array_new();
    }

    if (!gwy_ascii_strisident(name, "_-", NULL))
        g_warning("Function name `%s' is not a valid identifier. It may be rejected in future.", name);
    if (g_hash_table_lookup(synth_funcs, name)) {
        g_warning("Duplicate function `%s', keeping only first", name);
        return FALSE;
    }

    func_info = g_new0(GwySynthFuncInfo, 1);
    func_info->name = name;
    func_info->func = func;
    func_info->menu_path = menu_path;
    func_info->icon_name = icon_name;
    func_info->tooltip = tooltip;
    func_info->modes = modes;

    g_hash_table_insert(synth_funcs, (gpointer)func_info->name, func_info);
    if (!_gwy_module_add_registered_function(GWY_MODULE_PREFIX_SYNTH, name)) {
        g_hash_table_remove(synth_funcs, func_info->name);
        return FALSE;
    }

    return TRUE;
}

/**
 * gwy_synth_func_run:
 * @name: Curve map data processing function name.
 * @data: (transfer none) (nullable):
 *        A file data container, possibly %NULL.
 * @mode: How the function should be run.
 *
 * Runs a synthetic data function identified by @name.
 **/
void
gwy_synth_func_run(const gchar *name,
                   GwyFile *data,
                   GwyRunModeFlags mode)
{
    GwySynthFuncInfo *func_info;

    func_info = g_hash_table_lookup(synth_funcs, name);
    g_return_if_fail(func_info);
    g_return_if_fail(mode & func_info->modes);
    g_ptr_array_add(call_stack, func_info);
    func_info->func(data, mode);
    g_return_if_fail(call_stack->len);
    g_ptr_array_set_size(call_stack, call_stack->len-1);
}

/**
 * gwy_synth_func_foreach:
 * @function: (scope call):
 *            Function to run for each synthetic data function, passed by name.
 * @user_data: Data to pass to @function.
 *
 * Calls a function for each volume function.
 **/
void
gwy_synth_func_foreach(GwyNameFunc function, gpointer user_data)
{
    foreach_func(synth_funcs, function, user_data);
}

/**
 * gwy_synth_func_exists:
 * @name: Curve map data processing function name.
 *
 * Checks whether a synthetic data function exists.
 *
 * Returns: %TRUE if function @name exists, %FALSE otherwise.
 **/
gboolean
gwy_synth_func_exists(const gchar *name)
{
    return synth_funcs && g_hash_table_lookup(synth_funcs, name);
}

/**
 * gwy_synth_func_get_run_modes:
 * @name: Curve map data processing function name.
 *
 * Returns run modes supported by a synthetic data function.
 *
 * Returns: The run mode bit mask.
 **/
GwyRunModeFlags
gwy_synth_func_get_run_modes(const gchar *name)
{
    GwySynthFuncInfo *func_info;

    func_info = g_hash_table_lookup(synth_funcs, name);
    g_return_val_if_fail(func_info, 0);

    return func_info->modes;
}

/**
 * gwy_synth_func_get_menu_path:
 * @name: Curve map data processing function name.
 *
 * Returns the menu path of a synthetic data function.
 *
 * The returned menu path is only the tail part registered by the function, i.e., without any leading "/Curve Map".
 *
 * Returns: The menu path.  The returned string is owned by the module.
 **/
const gchar*
gwy_synth_func_get_menu_path(const gchar *name)
{
    GwySynthFuncInfo *func_info;

    func_info = g_hash_table_lookup(synth_funcs, name);
    g_return_val_if_fail(func_info, 0);

    return func_info->menu_path;
}

/**
 * gwy_synth_func_get_icon_name:
 * @name: Curve map data processing function name.
 *
 * Gets icon name of a synthetic data function.
 *
 * Returns: The icon name.  The returned string is owned by the module.
 **/
const gchar*
gwy_synth_func_get_icon_name(const gchar *name)
{
    GwySynthFuncInfo *func_info;

    g_return_val_if_fail(synth_funcs, NULL);
    func_info = g_hash_table_lookup(synth_funcs, name);
    g_return_val_if_fail(func_info, NULL);

    return func_info->icon_name;
}

/**
 * gwy_synth_func_get_tooltip:
 * @name: Curve map data processing function name.
 *
 * Gets tooltip for a synthetic data function.
 *
 * Returns: The tooltip.  The returned string is owned by the module.
 **/
const gchar*
gwy_synth_func_get_tooltip(const gchar *name)
{
    GwySynthFuncInfo *func_info;

    g_return_val_if_fail(synth_funcs, NULL);
    func_info = g_hash_table_lookup(synth_funcs, name);
    g_return_val_if_fail(func_info, NULL);

    return func_info->tooltip;
}

/**
 * gwy_synth_func_current:
 *
 * Obtains the name of currently running synthetic data function.
 *
 * If no synthetic data function is currently running, %NULL is returned.
 *
 * If multiple nested functions are running (which is not usual but technically possible), the innermost function name
 * is returned.
 *
 * Returns: The name of currently running synthetic data function or %NULL.
 **/
const gchar*
gwy_synth_func_current(void)
{
    GwySynthFuncInfo *func_info;

    if (!call_stack || !call_stack->len)
        return NULL;

    func_info = (GwySynthFuncInfo*)g_ptr_array_index(call_stack, call_stack->len-1);
    return func_info->name;
}

gboolean
_gwy_synth_func_remove(const gchar *name)
{
    gwy_debug("%s", name);
    if (!g_hash_table_remove(synth_funcs, name)) {
        g_warning("Cannot remove function %s", name);
        return FALSE;
    }
    return TRUE;
}

/**
 * SECTION: module-synth
 * @title: Synthetic data modules
 * @short_description: Synthetic data modules
 *
 * Synthetic data modules implement functions creating new data from scratch. They often query the current data
 * (generally image data) and can take dimensions and units from or initialise the generator using them. They can also
 * modify the current data. However, they can always be run without any other data loaded.
 **/

/**
 * GwySynthFuncInfo:
 * @name: An unique synthetic data function name.
 * @menu_path: A path under "/Synthetic" where the function should appear. It must start with "/".
 * @func: The function itself.
 * @run: Possible run-modes for this function.
 *
 * Information about one synthetic data function.
 **/

/**
 * GwySynthFunc:
 * @data: The file data container to operate on.
 * @run: Run mode.
 *
 * The type of synthetic data function.
 *
 * Uniquely, @data may be %NULL for synthetic data functions. They need to create a new file then. Helper functions
 * such as gwy_synth_add_result_to_file() exist to simplify and unify the implementation.
 **/

/* vim: set cin columns=120 tw=118 et ts=4 sw=4 cino=>1s,e0,n0,f0,{0,}0,^0,\:1s,=0,g1s,h0,t0,+1s,c3,(0,u0 : */
