/*
 *  $Id: win_stm.c 29611 2026-03-17 10:31:06Z yeti-dn $
 *  Copyright (C) 2014 Jeffrey J. Schwartz.
 *  E-mail: schwartz@physics.ucla.edu
 *
 *  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.
 */

/*
 * This module serves to open WinSTM data files in Gywddion. Currently, multiple data channels are supported with
 * limited meta data import.  No file export is supported; it is assumed that no changes will be saved, or if so then
 * another file format will be used.
 *
 * TODO: Import full meta data list.
 */

/**
 * [FILE-MAGIC-USERGUIDE]
 * WinSTM data
 * .stm
 * Read
 **/

/**
 * [FILE-MAGIC-FREEDESKTOP]
 * <mime-type type="application/x-winstm-spm">
 *   <comment>WinSTM data file</comment>
 *   <magic priority="80">
 *     <match type="string" offset="0" value="WinSTM"/>
 *   </magic>
 * </mime-type>
 **/

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

#define EXTENSION ".stm"

#define MAGIC "WinSTM"
#define MAGIC_SIZE (sizeof(MAGIC) - 1)

enum {
    HEADER_SIZE = 1368,
    DATA_SIZE = 768
};

typedef struct {
    gint32 NChan;
    gint32 NSpec;
    gint32 Xres;
    gint32 Yres;
    gdouble Gain;
    gdouble RangeX;
    gdouble RangeY;
    gdouble Bias;
    gdouble Current;
} WinSTM_File;

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 GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Imports WinSTM (.stm) files."),
    "Jeffrey J. Schwartz <schwartz@physics.ucla.edu>",
    "0.7",
    "Jeffrey J. Schwartz",
    "April 2014",
};

GWY_MODULE_QUERY2(module_info, win_stm)

static gboolean
module_register(void)
{
    gwy_file_func_register("win_stm",
                           N_("WinSTM files (.stm)"),
                           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) ? 20 : 0;
    if (fileinfo->buffer_len > MAGIC_SIZE && memcmp(fileinfo->head, MAGIC, MAGIC_SIZE) == 0)
        return 100;
    return 0;
}

static GwyFile*
load_file(const gchar *filename,
          G_GNUC_UNUSED GwyRunModeFlags mode,
          GError **error)
{
    WinSTM_File stm;
    GwyFile *file = NULL;
    GwyField *dfield;
    const guchar *p, *buffer = NULL;
    GError *err = NULL;
    gsize size;
    guint filemin;
    gint id;

    if (!gwy_map_file_contents(filename, &buffer, &size, &err)) {
        err_GET_FILE_CONTENTS(error, &err);
        return NULL;
    }
    filemin = HEADER_SIZE;
    if (size <= filemin) {
        err_TOO_SHORT(error);
        goto end;
    }
    if (memcmp(buffer, MAGIC, MAGIC_SIZE) != 0) {
        err_FILE_TYPE(error, "WinSTM");
        goto end;
    }
    p = buffer + MAGIC_SIZE;
    p += 926;
    stm.NChan = gwy_get_gint32_le(&p);
    stm.NSpec = gwy_get_gint16_le(&p);
    p += 86;
    stm.RangeX = 1e-10 * gwy_get_gdouble_le(&p);
    stm.RangeY = 1e-10 * gwy_get_gdouble_le(&p);
    p += 328;
    filemin = HEADER_SIZE + DATA_SIZE;
    if (size < filemin) {
        err_TOO_SHORT(error);
        goto end;
    }

    file = gwy_file_new_in_construction();
    for (id = 0; id < stm.NChan; id++) {
        gchar UnitWord[64];
        gchar Title[100];
        const gchar *zunit;
        guint datasize;
        gint power10;

        p += 2;
        get_CHARARRAY0(Title, &p);
        p += 522;
        stm.Gain = gwy_get_gdouble_le(&p);
        get_CHARARRAY0(UnitWord, &p);
        zunit = UnitWord;
        if (strstr(zunit, "ngstroms"))
            zunit = "Å";
        else if (gwy_strequal(zunit, "pAmps"))
            zunit = "pA";

        /* XXX: There was a logic basically treating anything else as ‘V’, but that should be fine as the parser
         * understands things like ‘Volts’ now. */
        stm.Bias = gwy_get_gdouble_le(&p);
        stm.Current = 1e3 * gwy_get_gdouble_le(&p);
        stm.Xres = gwy_get_gint32_le(&p);
        stm.Yres = gwy_get_gint32_le(&p);
        p += 48;
        datasize = sizeof(gint32) * stm.Xres * stm.Yres;
        if (err_SIZE_MISMATCH(error, datasize + (p - buffer), size, FALSE)) {
            g_clear_object(&file);
            goto end;
        }
        dfield = gwy_field_new(stm.Xres, stm.Yres, stm.RangeX, stm.RangeY, FALSE);
        gwy_unit_set_from_string(gwy_field_get_unit_xy(dfield), "m");
        power10 = gwy_unit_set_from_string(gwy_field_get_unit_z(dfield), zunit);
        gwy_convert_raw_data(p, stm.Xres * stm.Yres, 1, GWY_RAW_DATA_SINT32, GWY_BYTE_ORDER_LITTLE_ENDIAN,
                             gwy_field_get_data(dfield), gwy_exp10(power10) * stm.Gain, 0.0);
        p += datasize;

        gwy_file_pass_image(file, id, dfield);
        gwy_file_pass_title(file, GWY_FILE_IMAGE, id, gwy_convert_to_utf8(Title, -1, "ISO-8859-1"));
        GwyDict *meta = gwy_dict_new_in_construction();
        gwy_dict_pass_string_by_name(meta, "Bias", g_strdup_printf("%.3f V", stm.Bias));
        gwy_dict_pass_string_by_name(meta, "Current Set Point", g_strdup_printf("%.3f pA", stm.Current));
        gwy_file_pass_meta(file, GWY_FILE_IMAGE, id, meta);
        gwy_log_add_import(file, GWY_FILE_IMAGE, id, NULL, filename);
    }

end:
    gwy_unmap_file_contents(buffer);

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