/*
 *  $Id: nanoscope-ii.c 29611 2026-03-17 10:31:06Z yeti-dn $
 *  Copyright (C) 2004-2025 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.
 */

/**
 * [FILE-MAGIC-FREEDESKTOP]
 * <mime-type type="application/x-nanoscope-ii-spm">
 *   <comment>Nanoscope II SPM data</comment>
 *   <magic priority="80">
 *     <match type="string" offset="0" value="Data_File_Type 7\r\n"/>
 *   </magic>
 * </mime-type>
 **/

/**
 * [FILE-MAGIC-FILEMAGIC]
 * # Old Nanoscope II
 * 0 string Data_File_Type\ 7\x0d\x0a Nanoscope II SPM data
 **/

/**
 * [FILE-MAGIC-USERGUIDE]
 * Veeco Nanoscope II
 * .001, .002, etc.
 * Read
 **/

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

#include "err.h"

#define MAGIC "Data_File_Type 7\r\n"
#define MAGIC_SIZE (sizeof(MAGIC)-1)

#define Nanometer (1e-9)

enum { HEADER_SIZE = 2048 };

typedef struct {
    GHashTable *hash;
    GwyField *field;
} NanoscopeData;

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 GwyField*     hash_to_field         (GHashTable *hash,
                                            const guchar *buffer,
                                            gsize size,
                                            GError **error);
static GHashTable*   read_hash             (gchar *buffer,
                                            GError **error);
static GwyDict* nanoscope_get_metadata(GHashTable *hash);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Imports Digital Instruments Nanoscope II data files."),
    "Yeti <yeti@gwyddion.net>",
    "0.4",
    "David Nečas (Yeti) & Petr Klapetek",
    "2007",
};

GWY_MODULE_QUERY2(module_info, nanoscope_ii)

static gboolean
module_register(void)
{
    gwy_file_func_register("nanoscope-ii",
                           N_("Nanoscope II files"),
                           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 0;

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

    return score;
}

static GwyFile*
load_file(const gchar *filename,
          G_GNUC_UNUSED GwyRunModeFlags mode,
          GError **error)
{
    GwyFile *file = NULL;
    GwyDict *meta;
    GwyField *dfield;
    GError *err = NULL;
    const guchar *buffer = NULL;
    gchar *header;
    gsize size = 0;
    GHashTable *hash;

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

    if (size <= HEADER_SIZE || memcmp(buffer, MAGIC, MAGIC_SIZE) != 0) {
        err_FILE_TYPE(error, "Nanoscope II");
        gwy_unmap_file_contents(buffer);
        return NULL;
    }

    header = g_memdup(buffer, HEADER_SIZE + 1);
    header[HEADER_SIZE] = '\0';
    hash = read_hash(header + MAGIC_SIZE, error);
    /* Must keep header allocated, hash items are direct pointers to it */
    if (!hash) {
        g_free(header);
        gwy_unmap_file_contents(buffer);
        return NULL;
    }

    dfield = hash_to_field(hash, buffer + HEADER_SIZE, size - HEADER_SIZE,
                           &err);
    meta = nanoscope_get_metadata(hash);
    gwy_unmap_file_contents(buffer);
    g_hash_table_destroy(hash);
    g_free(header);
    if (!dfield)
        return NULL;

    file = gwy_file_new_in_construction();
    gwy_file_pass_image(file, 0, dfield);
    if (meta)
        gwy_file_pass_meta(file, GWY_FILE_IMAGE, 0, meta);

    gwy_image_title_fall_back(file, 0);
    gwy_log_add_import(file, GWY_FILE_IMAGE, 0, NULL, filename);

    return file;
}

static GwyField*
hash_to_field(GHashTable *hash,
              const guchar *buffer,
              gsize size,
              GError **error)
{
    GwyField *dfield;
    gchar *val;
    gint xres, yres, i;
    gdouble xreal, yreal, q;
    gdouble *data;

    if (!require_keys(hash, error, "num_samp", "scan_sz", "z_scale", NULL))
        return NULL;

    val = g_hash_table_lookup(hash, "num_samp");
    xres = yres = atoi(val);
    if (err_DIMENSION(error, xres))
        return NULL;

    if (err_SIZE_MISMATCH(error, xres*yres*sizeof(gint16), size, FALSE))
        return NULL;

    val = g_hash_table_lookup(hash, "scan_sz");
    xreal = yreal = Nanometer*g_ascii_strtod(val, NULL);
    if (xreal <= 0) {
        err_INVALID(error, "scan_sz");
        return NULL;
    }

    val = g_hash_table_lookup(hash, "z_scale");
    q = Nanometer/16384.0*g_ascii_strtod(val, NULL);
    if (q <= 0.0) {
        err_INVALID(error, "scan_sz");
        return NULL;
    }
    dfield = gwy_field_new(xres, yres, xreal, yreal, FALSE);
    data = gwy_field_get_data(dfield);

    for (i = 0; i < yres; i++) {
        gwy_convert_raw_data(buffer + (yres-1 - i)*xres*sizeof(gint16), xres, 1,
                             GWY_RAW_DATA_SINT16, GWY_BYTE_ORDER_LITTLE_ENDIAN, data + i*xres, q, 0.0);
    }

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

    return dfield;
}

static GHashTable*
read_hash(gchar *buffer,
          GError **error)
{
    GHashTable *hash;
    gchar *line, *value;

    hash = g_hash_table_new(g_str_hash, g_str_equal);
    while ((line = gwy_str_next_line(&buffer)) && line[0] != '\x1a') {
        g_strstrip(line);
        if (!line[0])
            continue;

        gwy_debug("<%s>", line);
        value = strchr(line, '=');
        if (!value) {
            g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_DATA,
                        _("Malformed header line (missing =)."));
            g_hash_table_destroy(hash);

            return NULL;
        }
        *value = '\0';
        g_strstrip(line);

        value++;
        g_strstrip(value);
        if (*value)
            g_hash_table_insert(hash, line, value);
    }

    return hash;
}

static GwyDict*
nanoscope_get_metadata(GHashTable *hash)
{
    static const struct {
        gchar *key;
        gchar *name;
        gchar *unit;
    }
    metadata[] = {
        { "bias_volt[0]", "Bias", " mV" },
        { "scan_rate", "Scan rate", " Hz" },
        { "time", "Date", NULL },
    };

    GwyDict *meta;
    const gchar *val;
    guint i;

    meta = gwy_dict_new_in_construction();
    for (i = 0; i < G_N_ELEMENTS(metadata); i++) {
        if ((val = g_hash_table_lookup(hash, metadata[i].key)))
            gwy_dict_pass_string_by_name(meta, metadata[i].name, g_strconcat(val, metadata[i].unit, NULL));
    }
    if (gwy_dict_get_n_items(meta))
        return meta;

    g_object_unref(meta);
    return NULL;
}

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