/*
 *  $Id: module-file.c 29782 2026-04-13 14:24:42Z yeti-dn $
 *  Copyright (C) 2003-2026 David Necas (Yeti), Petr Klapetek.
 *  E-mail: yeti@gwyddion.net, klapetek@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 <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <glib/gstdio.h>
#include <glib/gi18n-lib.h>

#include "libgwyddion/macros.h"
#include "libgwyddion/utils.h"
#include "libgwyddion/dict.h"
#include "libgwyddion/field.h"
#include "libgwyui/graph-model.h"

#include "libgwyapp/internal.h"
#include "libgwyapp/module-file.h"
#include "libgwyapp/module-internal.h"

/* The file function information. */
typedef struct {
    const gchar *name;
    const gchar *description;
    GwyFileDetectFunc detect_file;
    GwyFileLoadFunc load_file;
    GwyFileSaveFunc save_file;
    GwyFileSaveFunc export_file;
    gboolean is_detectable;
    gboolean is_unfinished;
} GwyFileFuncInfo;

/* Information about current file, passed around during detection */
typedef struct {
    const gchar *winner;
    gint score;
    gboolean only_name;
    GwyFileOperationType mode;
    GwyFileDetectInfo *fileinfo;
} FileDetectData;

static gboolean             gwy_file_detect_fill_info(GwyFileDetectInfo *fileinfo,
                                                      gboolean only_name);
static void                 gwy_file_detect_free_info(GwyFileDetectInfo *fileinfo);
static void                 file_detect_max_score    (gpointer hkey,
                                                      gpointer hvalue,
                                                      gpointer user_data);
static GwyFileOperationType get_operations           (const GwyFileFuncInfo *func_info);

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

static inline void
call_stack_push(GwyFileFuncInfo *func_info)
{
    g_ptr_array_add(call_stack, func_info);
}

static inline void
call_stack_pop(GwyFileFuncInfo *func_info)
{
    gsize stack_len = call_stack->len;
    g_return_if_fail(stack_len);
    g_assert(g_ptr_array_index(call_stack, stack_len-1) == func_info);
    g_ptr_array_set_size(call_stack, stack_len-1);
}

/**
 * gwy_file_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.
 * @description: File type description (will be used in file type selectors).
 * @detect: Detection function.  It may be %NULL, files of such a type can can be then loaded and saved only on
 *          explict user request.
 * @load_file: File load/import function.
 * @save_file: File save function.
 * @export_file: File export function.
 *
 * Registers a file format and defines which file operations it supports.
 *
 * At least one of @load_file, @save_file, and @export_file must be non-%NULL.  See #GwyFileOperationType for
 * differences between save and export.
 *
 * 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 chance to clean-up.
 *
 * It is possible to register the same routines such as @load_file for multiple file types (as distinguished by
 * @name). In such case they must use gwy_file_func_current() to see under which name they are called. It may be
 * easier to register different routines which then call the same function within the module with some
 * distinguishing argument.
 *
 * Returns: Normally %TRUE; %FALSE on failure.
 **/
gboolean
gwy_file_func_register(const gchar *name,
                       const gchar *description,
                       GwyFileDetectFunc detect_file,
                       GwyFileLoadFunc load_file,
                       GwyFileSaveFunc save_file,
                       GwyFileSaveFunc export_file)
{
    GwyFileFuncInfo *func_info;

    g_return_val_if_fail(name, FALSE);
    g_return_val_if_fail(load_file || save_file || export_file, FALSE);
    g_return_val_if_fail(description, FALSE);
    gwy_debug("name = %s, desc = %s, detect = %p, "
              "load = %p, save = %p, export = %p",
              name, description, detect_file, load_file, save_file, export_file);

    if (!file_funcs) {
        gwy_debug("Initializing...");
        file_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(file_funcs, name)) {
        g_warning("Duplicate function %s, keeping only first", name);
        return FALSE;
    }

    func_info = g_new0(GwyFileFuncInfo, 1);
    func_info->name = name;
    func_info->description = description;
    func_info->detect_file = detect_file;
    func_info->load_file = load_file;
    func_info->save_file = save_file;
    func_info->export_file = export_file;
    func_info->is_detectable = !!func_info->detect_file;

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

    return TRUE;
}

/**
 * gwy_file_func_run_detect:
 * @name: A file type function name.
 * @filename: A file name to detect.
 * @only_name: Whether to use only file name for a guess, or try to actually access the file.
 *
 * Runs a file type detection function identified by @name.
 *
 * Value of @only_name should be %TRUE if the file doesn't exist (is to be written) so its contents can't be used for
 * file type detection.
 *
 * This is a low-level function, consider using gwy_file_detect() if you simply want to detect a file type.
 *
 * Returns: An integer score expressing the likelyhood of the file being loadable as this type. See #GwyFileDetectFunc
 *          for more details.
 **/
gint
gwy_file_func_run_detect(const gchar *name,
                         const gchar *filename,
                         gboolean only_name)
{
    GwyFileFuncInfo *func_info;
    GwyFileDetectInfo fileinfo;
    gint score = 0;

    g_return_val_if_fail(filename, 0);
    func_info = g_hash_table_lookup(file_funcs, name);
    g_return_val_if_fail(func_info, 0);
    if (!func_info->detect_file)
        return 0;

    fileinfo.name = filename;
    /* File must exist if not only_name */
    if (gwy_file_detect_fill_info(&fileinfo, only_name)) {
        call_stack_push(func_info);
        score = func_info->detect_file(&fileinfo, only_name);
        call_stack_pop(func_info);
        gwy_file_detect_free_info(&fileinfo);
    }

    return score;
}

/**
 * gwy_file_func_run_load:
 * @name: A file load function name.
 * @filename: A file name to load data from.
 * @mode: Run mode.
 * @error: Return location for a #GError (or %NULL).
 *
 * Runs a file load function identified by @name.
 *
 * This is a low-level function, consider using gwy_file_load() if you simply want to load a file.
 *
 * The returned object is never marked as in construction; it is always already finished.
 *
 * Returns: A new #GwyFile with data from @filename, or %NULL.
 **/
GwyFile*
gwy_file_func_run_load(const gchar *name,
                       const gchar *filename,
                       GwyRunModeFlags mode,
                       GError **error)
{
    g_return_val_if_fail(!error || !*error, NULL);
    g_return_val_if_fail(filename, NULL);

    GwyFileFuncInfo *func_info = g_hash_table_lookup(file_funcs, name);
    g_return_val_if_fail(func_info, NULL);
    g_return_val_if_fail(func_info->load_file, NULL);

    call_stack_push(func_info);
    GwyFile *file = func_info->load_file(filename, mode, error);
    if (file)
        _gwy_file_set_info(file, name, filename);
    call_stack_pop(func_info);

    if (file) {
        GwyDict *container = GWY_DICT(file);
        if (gwy_dict_is_being_constructed(container))
            gwy_dict_finish_construction(container);
    }

    return file;
}

/**
 * gwy_file_func_run_save:
 * @name: A file save function name.
 * @file: A #GwyFile to save.
 * @filename: A file name to save @file as.
 * @mode: Run mode.
 * @error: Return location for a #GError (or %NULL).
 *
 * Runs a file save function identified by @name.
 *
 * It guarantees the container lifetime spans through the actual file saving, so the module function doesn't have to
 * care about it.
 *
 * This is a low-level function, consider using gwy_file_save() if you simply want to save a file.
 *
 * Returns: %TRUE if file save succeeded, %FALSE otherwise.
 **/
gboolean
gwy_file_func_run_save(const gchar *name,
                       GwyFile *file,
                       const gchar *filename,
                       GwyRunModeFlags mode,
                       GError **error)
{
    GwyFileFuncInfo *func_info;
    gboolean status;

    g_return_val_if_fail(!error || !*error, FALSE);
    g_return_val_if_fail(filename, FALSE);
    g_return_val_if_fail(GWY_IS_FILE(file), FALSE);
    func_info = g_hash_table_lookup(file_funcs, name);
    g_return_val_if_fail(func_info, FALSE);
    g_return_val_if_fail(func_info->save_file, FALSE);

    g_object_ref(file);
    call_stack_push(func_info);
    status = func_info->save_file(file, filename, mode, error);
    if (status)
        _gwy_file_set_info(file, name, filename);
    g_object_unref(file);
    call_stack_pop(func_info);

    return status;
}

/**
 * gwy_file_func_run_export:
 * @name: A file save function name.
 * @file: A #GwyFile to save.
 * @filename: A file name to save @file as.
 * @mode: Run mode.
 * @error: Return location for a #GError (or %NULL).
 *
 * Runs a file export function identified by @name.
 *
 * It guarantees the container lifetime spans through the actual file saving, so the module function doesn't have to
 * care about it.
 *
 * This is a low-level function, consider using gwy_file_save() if you simply want to save a file.
 *
 * Returns: %TRUE if file save succeeded, %FALSE otherwise.
 **/
gboolean
gwy_file_func_run_export(const gchar *name,
                         GwyFile *file,
                         const gchar *filename,
                         GwyRunModeFlags mode,
                         GError **error)
{
    GwyFileFuncInfo *func_info;
    gboolean status;

    g_return_val_if_fail(!error || !*error, FALSE);
    g_return_val_if_fail(filename, FALSE);
    g_return_val_if_fail(GWY_IS_FILE(file), FALSE);
    func_info = g_hash_table_lookup(file_funcs, name);
    g_return_val_if_fail(func_info, FALSE);
    g_return_val_if_fail(func_info->export_file, FALSE);

    g_object_ref(file);
    call_stack_push(func_info);
    status = func_info->export_file(file, filename, mode, error);
    g_object_unref(file);
    call_stack_pop(func_info);

    return status;
}

static void
file_detect_max_score(gpointer hkey, gpointer hdata, gpointer user_data)
{
    const gchar *key = (const gchar*)hkey;
    GwyFileFuncInfo *func_info = (GwyFileFuncInfo*)hdata;
    FileDetectData *ddata = (FileDetectData*)user_data;

    g_assert(gwy_strequal(key, func_info->name));

    if (!func_info->detect_file)
        return;
    if ((ddata->mode & GWY_FILE_OPERATION_LOAD) && !func_info->load_file)
        return;
    if ((ddata->mode & GWY_FILE_OPERATION_SAVE) && !func_info->save_file)
        return;
    if ((ddata->mode & GWY_FILE_OPERATION_EXPORT) && !func_info->export_file)
        return;

    call_stack_push(func_info);
    gint score = func_info->detect_file(ddata->fileinfo, ddata->only_name);
    call_stack_pop(func_info);

    if (score > ddata->score) {
        ddata->winner = func_info->name;
        ddata->score = score;
    }
}

/**
 * gwy_file_detect:
 * @filename: A file name to detect type of.
 * @only_name: Whether to use only file name for a guess, or try to actually access the file.
 * @operations: The file operations the file type must support (it must support all of them to be considered).
 * @score: (out) (nullable):
 *         Location to store the maximum score (corresponding to the returned type) to, or %NULL.
 *
 * Detects the type of a file and gives the score.
 *
 * The score is normally between 0 and 100. When an I/O error occurs or the file simply does not exist, the score is
 * set to a negative value.
 *
 * Returns: The type name (i.e., the same name as passed to e.g. gwy_file_func_run_load()) of most probable type of
 *          @filename, or %NULL if there's no probable one.
 **/
const gchar*
gwy_file_detect(const gchar *filename,
                gboolean only_name,
                GwyFileOperationType operations,
                gint *score)
{
    FileDetectData ddata;
    GwyFileDetectInfo fileinfo;

    if (!file_funcs)
        return NULL;

    fileinfo.name = filename;
    /* File must exist if not only_name */
    if (!gwy_file_detect_fill_info(&fileinfo, only_name)) {
        if (score)
            *score = -1;
        return NULL;
    }

    ddata.fileinfo = &fileinfo;
    ddata.winner = NULL;
    ddata.score = 0;
    ddata.only_name = only_name;
    ddata.mode = operations;
    g_hash_table_foreach(file_funcs, file_detect_max_score, &ddata);
    gwy_file_detect_free_info(&fileinfo);

    if (score)
        *score = ddata.score;
    return ddata.score ? ddata.winner : NULL;
}

static gboolean
gwy_file_detect_fill_info(GwyFileDetectInfo *fileinfo,
                          gboolean only_name)
{
    g_return_val_if_fail(fileinfo && fileinfo->name, FALSE);

    /* FIXME: What if it isn't ASCII-compatible? */
    fileinfo->name_lowercase = g_ascii_strdown(fileinfo->name, -1);
    fileinfo->file_size = 0;
    fileinfo->buffer_len = 0;
    fileinfo->head = NULL;
    fileinfo->tail = NULL;
    if (only_name)
        return TRUE;

    GStatBuf st;
    if (g_stat(fileinfo->name, &st) != 0) {
        gwy_file_detect_free_info(fileinfo);
        return FALSE;
    }
    fileinfo->file_size = st.st_size;
    if (!fileinfo->file_size) {
        gwy_file_detect_free_info(fileinfo);
        return FALSE;
    }

    /* NB: When MS Windows locks the file, this still succeeds, only the actual data reading below fails with
     * permission deined (whereas in Unix usually the fopen() would fail already). */
    FILE *fh;
    if (!(fh = gwy_fopen(fileinfo->name, "rb"))) {
        gwy_file_detect_free_info(fileinfo);
        return FALSE;
    }

    fileinfo->buffer_len = MIN(fileinfo->file_size + 1, GWY_FILE_DETECT_BUFFER_SIZE);
    fileinfo->head = g_new0(guchar, 2*fileinfo->buffer_len);
    fileinfo->tail = fileinfo->head + fileinfo->buffer_len;
    if (fread((gchar*)fileinfo->head, fileinfo->buffer_len - 1, 1, fh) < 1
        || fseek(fh, 1-(gint)fileinfo->buffer_len, SEEK_END) != 0
        || fread((gchar*)fileinfo->tail, fileinfo->buffer_len - 1, 1, fh) < 1) {
        fclose(fh);
        gwy_file_detect_free_info(fileinfo);
        return FALSE;
    }

    fclose(fh);
    ((gchar*)fileinfo->head)[fileinfo->buffer_len - 1] = '\0';
    ((gchar*)fileinfo->tail)[fileinfo->buffer_len - 1] = '\0';

    return TRUE;
}

static void
gwy_file_detect_free_info(GwyFileDetectInfo *fileinfo)
{
    g_free((gpointer)fileinfo->name_lowercase);
    g_free((gpointer)fileinfo->head);
    fileinfo->head = NULL;
    fileinfo->tail = NULL;
    fileinfo->name_lowercase = NULL;
    fileinfo->buffer_len = 0;
}

/**
 * gwy_file_load:
 * @filename: A file name to load data from, in GLib encoding.
 * @mode: Run mode.
 * @error: Return location for a #GError (or %NULL).
 *
 * Loads a data file, autodetecting its type.
 *
 * The returned object is never marked as in construction; it is always already finished.
 *
 * Returns: A new #GwyFile with data from @filename, or %NULL.
 **/
GwyFile*
gwy_file_load(const gchar *filename,
              GwyRunModeFlags mode,
              GError **error)
{
    return gwy_file_load_with_func(filename, mode, NULL, error);
}

/**
 * gwy_file_load_with_func:
 * @filename: A file name to load data from, in GLib encoding.
 * @mode: Run mode.
 * @name: Location to store the name of file load function used to load the file, or %NULL.  If an error occurs
 *        outside the module, e.g. failure to recognise the file type, %NULL is stored to @name.
 * @error: Return location for a #GError (or %NULL).
 *
 * Loads a data file, autodetecting its type.
 *
 * The returned object is never marked as in construction; it is always already finished.
 *
 * Returns: A new #GwyFile with data from @filename, or %NULL.
 **/
GwyFile*
gwy_file_load_with_func(const gchar *filename,
                        GwyRunModeFlags mode,
                        const gchar **name,
                        GError **error)
{
    if (name)
        *name = NULL;

    g_return_val_if_fail(filename, NULL);

    gint score;
    const gchar *winner = gwy_file_detect(filename, FALSE, GWY_FILE_OPERATION_LOAD, &score);
    if (!winner) {
        if (score >= 0) {
            g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_UNIMPLEMENTED,
                        _("No module can load this file type."));
        }
        else {
            g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_IO,
                        _("Cannot open file for reading: %s."), g_strerror(errno));
        }
        return NULL;
    }
    if (name)
        *name = winner;

    return gwy_file_func_run_load(winner, filename, mode, error);
}

/**
 * gwy_file_save:
 * @file: A #GwyFile to save.
 * @filename: A file name to save the data as, in GLib encoding.
 * @mode: Run mode.
 * @error: Return location for a #GError (or %NULL).
 *
 * Saves a data file, deciding to save as what type from the file name.
 *
 * It tries to find a module implementing %GWY_FILE_OPERATION_SAVE first, when it does not succeed, it falls back to
 * %GWY_FILE_OPERATION_EXPORT.
 *
 * Returns: The save operation that was actually realized on success, zero on failure.
 **/
GwyFileOperationType
gwy_file_save(GwyFile *file,
              const gchar *filename,
              GwyRunModeFlags mode,
              GError **error)
{
    return gwy_file_save_with_func(file, filename, mode, NULL, error);
}

/**
 * gwy_file_save_with_func:
 * @file: A #GwyFile to save.
 * @filename: A file name to save the data as, in GLib encoding.
 * @mode: Run mode.
 * @name: Location to store the name of file load function used to save the file, or %NULL.  If an error occurs
 *        outside the module, e.g. failure to recognise the file type, %NULL is stored to @name.
 * @error: Return location for a #GError (or %NULL).
 *
 * Saves a data file, deciding to save as what type from the file name.
 *
 * It tries to find a module implementing %GWY_FILE_OPERATION_SAVE first, when it does not succeed, it falls back to
 * %GWY_FILE_OPERATION_EXPORT.
 *
 * Returns: The save operation that was actually realized on success, zero on failure.
 **/
GwyFileOperationType
gwy_file_save_with_func(GwyFile *file,
                        const gchar *filename,
                        GwyRunModeFlags mode,
                        const gchar **name,
                        GError **error)
{
    FileDetectData ddata;
    GwyFileDetectInfo fileinfo;

    if (name)
        *name = NULL;

    if (!file_funcs)
        goto gwy_file_save_fail;

    fileinfo.name = filename;
    gwy_file_detect_fill_info(&fileinfo, TRUE);

    ddata.fileinfo = &fileinfo;
    ddata.winner = NULL;
    ddata.score = 0;
    ddata.only_name = TRUE;
    ddata.mode = GWY_FILE_OPERATION_SAVE;
    g_hash_table_foreach(file_funcs, file_detect_max_score, &ddata);

    if (ddata.winner) {
        if (name)
            *name = ddata.winner;

        gwy_file_detect_free_info(&fileinfo);
        if (gwy_file_func_run_save(ddata.winner, file, filename, mode, error))
            return ddata.mode;
        return 0;
    }

    ddata.mode = GWY_FILE_OPERATION_EXPORT;
    g_hash_table_foreach(file_funcs, file_detect_max_score, &ddata);
    gwy_file_detect_free_info(&fileinfo);

    if (ddata.winner) {
        if (name)
            *name = ddata.winner;

        if (gwy_file_func_run_export(ddata.winner, file, filename, mode, error))
            return ddata.mode;
        return 0;
    }

gwy_file_save_fail:
    g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_UNIMPLEMENTED,
                _("No module can save to this file type."));

    return 0;
}

/**
 * gwy_file_func_foreach:
 * @function: Function to run for each file function.  It will get function name (constant string owned by module
 *            system) as its first argument, @user_data as the second argument.
 * @user_data: Data to pass to @function.
 *
 * Calls a function for each file function.
 **/
void
gwy_file_func_foreach(GwyNameFunc function,
                      gpointer user_data)
{
    foreach_func(file_funcs, function, user_data);
}

/**
 * gwy_file_func_exists:
 * @name: File type function name.
 *
 * Checks whether a file type function exists.
 *
 * Returns: %TRUE if function @name exists, %FALSE otherwise.
 **/
gboolean
gwy_file_func_exists(const gchar *name)
{
    return file_funcs && g_hash_table_lookup(file_funcs, name);
}

/**
 * gwy_file_func_get_operations:
 * @name: File type function name.
 *
 * Returns operations supported by a file type function.
 *
 * Returns: The file operation bit mask, zero if @name does not exist.
 **/
GwyFileOperationType
gwy_file_func_get_operations(const gchar *name)
{
    GwyFileFuncInfo *func_info;

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

    return get_operations(func_info);
}

static GwyFileOperationType
get_operations(const GwyFileFuncInfo *func_info)
{
    GwyFileOperationType capable = 0;

    if (!func_info)
        return capable;

    capable |= func_info->load_file ? GWY_FILE_OPERATION_LOAD : 0;
    capable |= func_info->save_file ? GWY_FILE_OPERATION_SAVE : 0;
    capable |= func_info->export_file ? GWY_FILE_OPERATION_EXPORT : 0;
    capable |= func_info->detect_file ? GWY_FILE_OPERATION_DETECT : 0;

    return capable;
}

/**
 * gwy_file_func_get_description:
 * @name: File type function name.
 *
 * Gets file function description.
 *
 * That is, the @description argument of gwy_file_func_register() .
 *
 * Returns: File function description, as a string owned by module loader.
 **/
const gchar*
gwy_file_func_get_description(const gchar *name)
{
    GwyFileFuncInfo *func_info;

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

    return func_info->description;
}

/**
 * gwy_file_func_get_is_detectable:
 * @name: File type function name.
 *
 * Reports if the file format is reasonably detectable.
 *
 * This is %TRUE for all file types that define a detection method unless they explicitly call
 * gwy_file_func_set_is_detectable() to set the file format non-detectable in spite of providing a detection method.
 *
 * If files that can be actually loaded as a given type form a subset of files that are detected as this format, which
 * is normaly the case, it makes no sense to let the user explicitly choose between these formats.  Hence, detectable
 * formats normally are not explicitly offered.
 *
 * Returns: If the file format is detectable.
 **/
gboolean
gwy_file_func_get_is_detectable(const gchar *name)
{
    GwyFileFuncInfo *func_info;

    g_return_val_if_fail(file_funcs, FALSE);
    func_info = g_hash_table_lookup(file_funcs, name);
    g_return_val_if_fail(func_info, FALSE);

    return func_info->is_detectable;
}

/**
 * gwy_file_func_set_is_detectable:
 * @name: File type function name.
 * @is_detectable: %TRUE to define format as detectable, %FALSE as non-detectable.
 *
 * Sets the detectability status of a file format.
 *
 * See gwy_file_func_get_is_detectable() for details.  The only rare case when it makes sense to call this function is
 * when a detection function is provided for some reason, however, this function is not really able to detect the
 * format.  For instance, the fallback detection method of the Gwyddion rawfile module.
 **/
void
gwy_file_func_set_is_detectable(const gchar *name,
                                gboolean is_detectable)
{
    GwyFileFuncInfo *func_info;

    g_return_if_fail(file_funcs);
    func_info = g_hash_table_lookup(file_funcs, name);
    g_return_if_fail(func_info);
    func_info->is_detectable = is_detectable;
}

/**
 * gwy_file_func_get_is_unfinished:
 * @name: File type function name.
 *
 * Reports if the file format implementation is unfinished.
 *
 * Returns: %TRUE if the file format is considered unfinished.
 **/
gboolean
gwy_file_func_get_is_unfinished(const gchar *name)
{
    GwyFileFuncInfo *func_info;

    g_return_val_if_fail(file_funcs, FALSE);
    func_info = g_hash_table_lookup(file_funcs, name);
    g_return_val_if_fail(func_info, FALSE);

    return func_info->is_unfinished;
}

/**
 * gwy_file_func_set_is_unfinished:
 * @name: File type function name.
 * @is_unfinished: %TRUE if the file format implementation should be considered unfinished.
 *
 * Sets the unfinished status of a file format.
 *
 * Marking a file format as unfinished is informative.
 *
 * It should be done under two conditions. The file module can actually read the files and create some images (or
 * other data); if it cannot it should not register the file loading function. But the data it creates are wrong and
 * possibly misleading. For example it is known the conversion from raw to physical values just does not work. It
 * should not be marked when the support for some features is simply missing or the import mostly works fine, but it
 * might not in some specific cases due to the file format complexity.
 *
 * Gwyddion can then warn when users are opening such files.
 **/
void
gwy_file_func_set_is_unfinished(const gchar *name,
                                gboolean is_unfinished)
{
    GwyFileFuncInfo *func_info;

    g_return_if_fail(file_funcs);
    func_info = g_hash_table_lookup(file_funcs, name);
    g_return_if_fail(func_info);
    func_info->is_unfinished = is_unfinished;
}

/**
 * gwy_file_func_current:
 *
 * Obtains the name of currently running file type function.
 *
 * All file functions are taken into account: detect, load, save and export.
 *
 * If no file type function is currently running, %NULL is returned.
 *
 * If multiple nested file functions are running (which is not usual but technically possible), the innermost function
 * name is returned. If nested function of different types are running (which is not usual but technically possible)
 * the other types do not affect the returned value; only file functions are taken into account.
 *
 * Returns: The name of the currently running file type function or %NULL.
 **/
const gchar*
gwy_file_func_current(void)
{
    GwyFileFuncInfo *func_info;

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

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

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

/**
 * gwy_module_file_error_quark:
 *
 * Returns error domain for file module functions.
 *
 * See and use %GWY_MODULE_FILE_ERROR.
 *
 * Returns: The error domain.
 **/
GQuark
gwy_module_file_error_quark(void)
{
    static GQuark error_domain = 0;

    if (!error_domain)
        error_domain = g_quark_from_static_string("gwy-module-file-error-quark");

    return error_domain;
}

/**
 * SECTION: module-file
 * @title: File modules
 * @short_description: File loading and saving modules
 *
 * File modules implement file loading, saving and file type detection functions.  Not all fuctions has to be
 * implemented, a file module can be import-only or export-only.  If it does not implement file type detection, files
 * of this type can be read/written only on user's explicite request.
 *
 * For file module writers, the only useful function here is the registration function gwy_file_func_register() and
 * the signatures of particular file operations: #GwyFileDetectFunc, #GwyFileLoadFunc, and #GwyFileSaveFunc.
 **/

/**
 * GwyFileDetectFunc:
 * @fileinfo: Information about file to detect the filetype of, see #GwyFileDetectInfo.
 * @only_name: Whether the type should be guessed only from file name.
 *
 * The type of file type detection function.
 *
 * When called with %TRUE @only_name it should not try to access the file.
 *
 * A basic scale for the returned values is:
 * <itemizedlist>
 * <listitem>0 for not recognised</listitem>
 * <listitem>20 for very a basic check or a relatively unique file extension</listitem>
 * <listitem>60 to 80 in probable but not clear cut cases when the file may be something else or it is a generic
 * format which may have more specific subformats</listitem>
 * <listitem>100 is the standard value for a solid check beyond some 4byte magic header; the modue should pretty sure
 * the file is actually of this type.</listitem>
 * </itemizedlist>
 *
 * Returns: An integer likelyhood score.
 **/

/**
 * GwyFileLoadFunc:
 * @filename: A file name to load data from.
 * @mode: Run mode, this is either @GWY_RUN_NONINTERACTIVE or @GWY_RUN_INTERACTIVE.
 * @error: Return location for a #GError (or %NULL).
 *
 * The type of file loading function.
 *
 * The function can create the file as being in construction with gwy_file_new_in_construction() to avoid signals
 * being emitted during construction. If the returned object is still marked in construction, it will be finished by
 * the module system.
 *
 * Returns: (transfer full):
 *          A newly created file data container, or %NULL on failure.
 **/

/**
 * GwyFileSaveFunc:
 * @file: A #GwyFile to save.
 * @filename: A file name to save @file as.
 * @mode: Run mode, this is either @GWY_RUN_NONINTERACTIVE or @GWY_RUN_INTERACTIVE.
 * @error: Return location for a #GError (or %NULL).
 *
 * The type of file saving function.
 *
 * Returns: %TRUE if file save succeeded, %FALSE otherwise.
 **/

/**
 * GwyFileOperationType:
 * @GWY_FILE_OPERATION_DETECT: Posibility to detect files are of this file type,
 * @GWY_FILE_OPERATION_LOAD: Posibility to load files of this type.
 * @GWY_FILE_OPERATION_SAVE: Posibility to save files of this type.
 * @GWY_FILE_OPERATION_EXPORT: Posibility to export files of this type.
 * @GWY_FILE_OPERATION_MASK: The mask for all the flags.
 *
 * File type function file operations (capabilities).
 *
 * The difference between save and export is that save is supposed to create a file containing fairly complete
 * representation of the container, while export is the possibility to write some information to given file type.
 * Generally only native file format module implements %GWY_FILE_OPERATION_SAVE, all others implement
 * %GWY_FILE_OPERATION_EXPORT.
 **/

/**
 * GwyFileDetectInfo:
 * @name: File name, in GLib filename encoding.
 * @name_lowercase: File name in lowercase (for eventual case-insensitive name check).
 * @file_size: File size in bytes.  Undefined if @only_name.
 * @buffer_len: The size of @head and @tail in bytes.  Normally it's @GWY_FILE_DETECT_BUFFER_SIZE except when file is
 *              shorter than that.  Undefined if @only_name.
 * @head: Initial part of file.  Undefined if @only_name.
 * @tail: Final part of file.  Undefined if @only_name.
 *
 * File detection data for #GwyFileDetectFunc.
 *
 * It contains the common information file type detection routines need to obtain.  It is shared between file
 * detection functions and they must not modify its contents.  Some fields are set only when the detection routines
 * are to check the file contents, these are marked `Undefined if @only_name'.
 *
 * The @head and @tail buffers are always nul-terminated and thus safely usable with string functions.  When file is
 * shorter than @GWY_FILE_DETECT_BUFFER_SIZE bytes, <literal>'\0'</literal> is appended to the end (therefore
 * @buffer_len = @file_size + 1), otherwise the last byte is overwritten with <literal>'\0'</literal>.  In either case
 * the last byte of @head and @tail cannot be assumed to be identical as in the file (or being a part of the file at
 * all).
 **/

/**
 * GWY_FILE_DETECT_BUFFER_SIZE:
 *
 * The size of #GwyFileDetectInfo buffer for initial part of file.  It should be enough for any normal kind of magic
 * header test.
 *
 * This is the maximum size. If the file is tiny the buffer contains less data. Always use @buffer_len in the info
 * structure for the actual size.
 **/

/**
 * GWY_MODULE_FILE_ERROR:
 *
 * Error domain for file module operations.  Errors in this domain will be from the #GwyModuleFileError enumeration.
 * See #GError for information on error domains.
 **/

/**
 * GwyModuleFileError:
 * @GWY_MODULE_FILE_ERROR_CANCELED: Interactive operation was cancelled by user.
 * @GWY_MODULE_FILE_ERROR_CANCELLED: Alias for %GWY_MODULE_FILE_ERROR_CANCELED.
 * @GWY_MODULE_FILE_ERROR_UNIMPLEMENTED: No module implements requested operation.
 * @GWY_MODULE_FILE_ERROR_IO: Input/output error occured.
 * @GWY_MODULE_FILE_ERROR_DATA: Data is corrupted or in an unsupported format.
 * @GWY_MODULE_FILE_ERROR_INTERACTIVE: Operation requires user input, but it was run as %GWY_RUN_NONINTERACTIVE.
 * @GWY_MODULE_FILE_ERROR_SPECIFIC: Specific module errors that do not fall into any other category (such as the
 *                                  failure to initialize a library used to read the data).  Seldom used.
 *
 * Error codes returned by file module operations.
 *
 * File module functions can return any of these codes, except @GWY_MODULE_FILE_ERROR_UNIMPLEMENTED which is normally
 * only returned by high-level functions gwy_file_load() and gwy_file_save().  Module functions can return it only
 * when they are called with a wrong function name.
 **/

/* 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 : */
