/*
 *  $Id: dumbfile.c 28822 2025-11-06 16:01:09Z yeti-dn $
 *  Copyright (C) 2009-2025 David Necas (Yeti).
 *
 *  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.
 */

/**
 * [FILE-MAGIC-FREEDESKTOP]
 * <mime-type type="application/x-gwyddion-dump-spm">
 *   <comment>Gwyddion dumb dump data</comment>
 *   <magic priority="80">
 *     <match type="string" offset="0" value="/0/data/"/>
 *   </magic>
 *   <glob pattern="*.dump"/>
 *   <glob pattern="*.DUMP"/>
 * </mime-type>
 **/

/**
 * [FILE-MAGIC-USERGUIDE]
 * Gwyddion dumb dump data
 * .dump
 * Read
 **/

#include "config.h"
#include <glib/gi18n-lib.h>
#include <string.h>
#include <stdlib.h>
#include <gwy.h>

#include "err.h"

#define MAGIC "/0/data/"
#define MAGIC_SIZE (sizeof(MAGIC)-1)
#define EXTENSION ".dump"

static gboolean module_register (void);
static gint     detect_file     (const GwyFileDetectInfo *fileinfo,
                                 gboolean only_name);
static GwyFile* load_file       (const gchar *filename,
                                 GwyRunModeFlags mode,
                                 GError **error);
static GwyFile* text_dump_import(gchar *buffer,
                                 gsize size,
                                 const gchar *filename,
                                 GError **error);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Reads and exports Gwyddion dumb dump files."),
    "Yeti <yeti@gwyddion.net>",
    "2.0",
    "David Nečas (Yeti)",
    "2011",
};

GWY_MODULE_QUERY2(module_info, dumbfile)

static gboolean
module_register(void)
{
    gwy_file_func_register("dumbfile",
                           N_("Gwyddion dumb dump files (.dump)"),
                           detect_file, load_file, NULL, NULL);
    return TRUE;
}

static gint
detect_file(const GwyFileDetectInfo *fileinfo, gboolean only_name)
{
    if (only_name)
        return g_str_has_suffix(fileinfo->name_lowercase, EXTENSION) ? 15 : 0;

    if (fileinfo->file_size < MAGIC_SIZE || memcmp(fileinfo->head, MAGIC, MAGIC_SIZE) != 0)
        return 0;

    return 100;
}

static GwyFile*
load_file(const gchar *filename,
          G_GNUC_UNUSED GwyRunModeFlags mode,
          GError **error)
{
    GwyFile *file = NULL;
    gchar *buffer = NULL;
    GError *err = NULL;
    gsize size;

    if (!g_file_get_contents(filename, &buffer, &size, &err)) {
        err_GET_FILE_CONTENTS(error, &err);
        return NULL;
    }

    if (size < MAGIC_SIZE || memcmp(buffer, MAGIC, MAGIC_SIZE) != 0) {
        err_FILE_TYPE(error, "Gwyddion dumb dump");
        goto fail;
    }

    file = text_dump_import(buffer, size, filename, error);

fail:
    g_free(buffer);

    return file;
}

static const gchar*
get_item(GHashTable *hash, GString *str, gsize prefixlen, const gchar *suffix)
{
    g_string_truncate(str, prefixlen);
    g_string_append(str, suffix);
    return g_hash_table_lookup(hash, str->str);
}

static GwyFile*
text_dump_import(gchar *buffer,
                 gsize size,
                 const gchar *filename,
                 GError **error)
{
    gchar *val, *pos, *line;
    GwyField *dfield;
    gdouble xreal, yreal;
    gint xres, yres, id;
    const gchar *s, *t, *uxy, *uz;
    gdouble *d;
    gboolean ok = FALSE;
    gsize n, prefixlen;

    GwyFile *file = gwy_file_new_in_construction();
    GHashTable *hash = g_hash_table_new(g_str_hash, g_str_equal);
    GString *str = g_string_new(NULL);

    pos = buffer;
    while ((line = gwy_str_next_line(&pos)) && *line) {
        val = strchr(line, '=');
        if (!val || *line != '/') {
            g_warning("Garbage key: %s", line);
            continue;
        }
        if ((gsize)(val - buffer) + 1 > size) {
            err_TRUNCATED_HEADER(error);
            goto fail;
        }
        *val = '\0';
        val++;
        if (!gwy_strequal(val, "[") || !pos || *pos != '[') {
            gwy_debug("<%s>=<%s>", line, val);
            if (*val)
                g_hash_table_replace(hash, line, val);
            else
                g_hash_table_remove(hash, line);
            continue;
        }

        g_assert(pos && *pos == '[');
        pos++;

        id = 0;
        if (!sscanf(line, "/%d", &id) || id < 0) {
            err_INVALID(error, "dataID");
            goto fail;
        }
        if (!(s = strchr(strchr(line, '/'), '/'))) {
            err_INVALID(error, "dataID");
            goto fail;
        }
        s++;
        if (!gwy_stramong(s, "data", "mask", NULL)) {
            err_INVALID(error, "dataID");
            goto fail;
        }

        g_string_assign(str, line);
        prefixlen = str->len;

        /* Get field parameters. */
        if (!(t = get_item(hash, str, prefixlen, "/xres"))) {
            err_MISSING_FIELD(error, str->str);
            goto fail;
        }
        xres = atoi(t);
        if (err_DIMENSION(error, xres))
            goto fail;

        if (!(t = get_item(hash, str, prefixlen, "/yres"))) {
            err_MISSING_FIELD(error, str->str);
            goto fail;
        }
        yres = atoi(t);
        if (err_DIMENSION(error, yres))
            goto fail;

        xreal = yreal = 1.0;
        if ((t = get_item(hash, str, prefixlen, "/xreal"))) {
            xreal = g_ascii_strtod(t, NULL);
            sanitise_real_size(&xreal, "xreal");
        }
        if ((t = get_item(hash, str, prefixlen, "/yreal"))) {
            yreal = g_ascii_strtod(t, NULL);
            sanitise_real_size(&yreal, "yreal");
        }

        if (!(uxy = get_item(hash, str, prefixlen, "/unit-xy")))
            uxy = "m";
        if (!(uz = get_item(hash, str, prefixlen, "/unit-z")))
            uz = "m";
        t = get_item(hash, str, prefixlen, "/title");

        n = xres*yres*sizeof(gdouble);
        if (err_SIZE_MISMATCH(error, n + 3, size - (pos - buffer), FALSE))
            goto fail;

        dfield = GWY_FIELD(gwy_field_new(xres, yres, xreal, yreal, FALSE));
        gwy_unit_set_from_string(gwy_field_get_unit_xy(dfield), uxy);
        gwy_unit_set_from_string(gwy_field_get_unit_z(dfield), uz);

        d = gwy_field_get_data(dfield);
#if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
        memcpy(d, pos, n);
#else
        gwy_memcpy_byte_swap(pos, (guint8*)d, sizeof(gdouble), xres*yres, sizeof(gdouble)-1);
#endif
        pos += n;
        val = gwy_str_next_line(&pos);
        if (!gwy_strequal(val, "]]")) {
            g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_DATA,
                        _("Missing end of file field marker."));
            g_clear_object(&dfield);
            goto fail;
        }
        gwy_file_pass_image(file, id, dfield);
        if (t)
            gwy_file_set_title(file, GWY_FILE_IMAGE, id, t, FALSE);
        gwy_log_add_import(file, GWY_FILE_IMAGE, id, NULL, filename);

        g_hash_table_remove_all(hash);
    }

    ok = TRUE;

fail:
    g_hash_table_destroy(hash);
    g_string_free(str, TRUE);
    if (!ok)
        g_clear_object(&file);
    return file;
}

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