/*
 *  $Id: rmitmd.c 30114 2026-06-22 11:52:09Z 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.
 */

/**
 * [FILE-MAGIC-FREEDESKTOP]
 * <mime-type type="application/x-rmi-tmd-scan-profilometry">
 *   <comment>RMI TMD 3D scan measurement</comment>
 *   <magic priority="80">
 *     <match type="string" offset="0" value="Binary TrueMap Data File 2.0\r\n\x00"/>
 *   </magic>
 *   <glob pattern="*.tmd"/>
 *   <glob pattern="*.TMD"/>
 * </mime-type>
 **/

/**
 * [FILE-MAGIC-USERGUIDE]
 * RMI TMD 3D measurement
 * .tmd
 * Read
 **/

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

#include "err.h"

#define EXTENSION ".tmd"

#define MAGIC "Binary TrueMap Data File v2.0\r\n"
// NB: This is not a mistake. We want to include the terminating nul character because it is also in the file header.
#define MAGIC_SIZE sizeof(MAGIC)

#define Millimetre 1e-3

enum {
    BINARY_HEADER_SIZE = 6*4,
    HEADER_MIN_SIZE = MAGIC_SIZE + BINARY_HEADER_SIZE + 1,
};

typedef struct {
    guint xres;
    guint yres;
    gdouble xreal;
    gdouble yreal;
    gdouble xoffset;
    gdouble yoffset;
    gchar *comment;
} TMDFile;

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 gsize    read_header    (const guchar *buffer,
                                gsize size,
                                TMDFile *tmdfile,
                                GError **error);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Imports RMI TMD 3D measurements."),
    "Yeti <yeti@gwyddion.net>",
    "0.10",
    "David Nečas (Yeti)",
    "2025",
};

GWY_MODULE_QUERY2(module_info, rmitmd)

static gboolean
module_register(void)
{
    gwy_file_func_register("rmitmd",
                           N_("RMI TMD 3D scan files (.tmd)"),
                           &detect_file, &load_file, NULL, NULL);

    return TRUE;
}

static gint
detect_file(const GwyFileDetectInfo *fileinfo, gboolean only_name)
{
    gint score = 0;

    if (only_name)
        return g_str_has_suffix(fileinfo->name_lowercase, EXTENSION) ? 20 : 0;

    if (fileinfo->buffer_len > HEADER_MIN_SIZE
        && memcmp(fileinfo->head, MAGIC, MAGIC_SIZE) == 0)
        score = 100;

    return score;
}

static GwyFile*
load_file(const gchar *filename,
          G_GNUC_UNUSED GwyRunModeFlags mode,
          GError **error)
{
    GwyFile *file = NULL;
    GwyDict *meta;
    const guchar *p, *buffer = NULL;
    gsize pos, size = 0;
    GError *err = NULL;
    TMDFile tmdfile;
    GwyField *field;

    if (!gwy_map_file_contents(filename, &buffer, &size, &err)) {
        err_GET_FILE_CONTENTS(error, &err);
        return NULL;
    }
    gwy_clear1(tmdfile);
    if (!(pos = read_header(buffer, size, &tmdfile, error))) {
        gwy_unmap_file_contents(buffer);
        return NULL;
    }

    p = buffer + pos;
    field = gwy_field_new(tmdfile.xres, tmdfile.yres, tmdfile.xreal, tmdfile.yreal, FALSE);
    gwy_convert_raw_data(p, tmdfile.xres*tmdfile.yres, 1, GWY_RAW_DATA_FLOAT, GWY_BYTE_ORDER_LITTLE_ENDIAN,
                         gwy_field_get_data(field), Millimetre, 0.0);

    gwy_unit_set_from_string(gwy_field_get_unit_xy(field), "m");
    gwy_unit_set_from_string(gwy_field_get_unit_z(field), "m");

    file = gwy_file_new_in_construction();
    gwy_file_pass_image(file, 0, field);
    gwy_file_set_title(file, GWY_FILE_IMAGE, 0, "Topography", FALSE);

    meta = gwy_dict_new_in_construction();
    if (tmdfile.comment) {
        gwy_dict_pass_string_by_name(meta, "Comment", tmdfile.comment);
        tmdfile.comment = NULL;
    }
    gwy_dict_pass_string_by_name(meta, "X offset", g_strdup_printf("%g mm", tmdfile.xoffset));
    gwy_dict_pass_string_by_name(meta, "Y offset", g_strdup_printf("%g mm", tmdfile.yoffset));
    gwy_file_pass_meta(file, GWY_FILE_IMAGE, 0, meta);
    gwy_log_add_import(file, GWY_FILE_IMAGE, 0, NULL, filename);

    gwy_unmap_file_contents(buffer);

    return file;
}

static gsize
read_header(const guchar *buffer, gsize size,
            TMDFile *tmdfile,
            GError **error)
{
    const guchar *p, *pcomment;
    gsize clen;

    if (size <= HEADER_MIN_SIZE) {
        err_TOO_SHORT(error);
        return 0;
    }
    if (memcmp(buffer, MAGIC, MAGIC_SIZE) != 0) {
        err_FILE_TYPE(error, "RMI TMD");
        return 0;
    }

    p = pcomment = buffer + MAGIC_SIZE;
    while (p - buffer < size && *p)
        p++;

    if (p - buffer == size) {
        err_TRUNCATED_PART(error, "comment");
        return 0;
    }
    clen = p - pcomment;
    gwy_debug("comment <%s> len %lu", pcomment, (gulong)clen);
    p++;

    if (p - buffer > size - BINARY_HEADER_SIZE) {
        err_TRUNCATED_HEADER(error);
        return 0;
    }

    tmdfile->xres = gwy_get_guint32_le(&p);
    tmdfile->yres = gwy_get_guint32_le(&p);
    tmdfile->xreal = gwy_get_gfloat_le(&p);
    tmdfile->yreal = gwy_get_gfloat_le(&p);
    tmdfile->xoffset = gwy_get_gfloat_le(&p);
    tmdfile->yoffset = gwy_get_gfloat_le(&p);
    gwy_debug("res %ux%u, real %gx%g", tmdfile->xres, tmdfile->yres, tmdfile->xreal, tmdfile->yreal);

    if (err_DIMENSION(error, tmdfile->xres) || err_DIMENSION(error, tmdfile->yres))
        return 0;

    if (err_SIZE_MISMATCH(error, tmdfile->xres * tmdfile->yres * sizeof(gfloat), size - (p - buffer), FALSE))
        return 0;

    if (clen)
        tmdfile->comment = gwy_convert_to_utf8(pcomment, clen, "ISO-8859-1");

    tmdfile->xreal *= Millimetre;
    tmdfile->yreal *= Millimetre;
    tmdfile->xoffset *= Millimetre;
    tmdfile->yoffset *= Millimetre;

    sanitise_real_size(&tmdfile->xreal, "x size");
    sanitise_real_size(&tmdfile->yreal, "y size");

    return p - buffer;
}

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