/*
 *  $Id: magellan.c 29611 2026-03-17 10:31:06Z yeti-dn $
 *  Copyright (C) 2013-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-USERGUIDE]
 * FEI Magellan SEM images
 * .tif
 * Read
 **/

/**
 * [FILE-MAGIC-MISSING]
 * Indistinguishable from TIFF.  Avoding clash with a standard file format.
 **/

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

#define MAGIC_COMMENT "[User]\r\n"

enum {
    MAGELLAN_TIFF_TAG = 34682
};

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*      mgl_load_tiff  (const GwyTIFF *tiff,
                                     const gchar *filename,
                                     GError **error);
static GwyDict* get_meta       (GHashTable *hash);
static void          add_meta       (gpointer hkey,
                                     gpointer hvalue,
                                     gpointer user_data);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    module_register,
    N_("Imports FEI Magellan SEM images."),
    "Yeti <yeti@gwyddion.net>",
    "2.0",
    "David Nečas (Yeti)",
    "2013",
};

GWY_MODULE_QUERY2(module_info, magellan)

static gboolean
module_register(void)
{
    gwy_file_func_register("magellan",
                           N_("FEI Magellan SEM image (.tif)"),
                           detect_file, load_file, NULL, NULL);

    return TRUE;
}

static gint
detect_file(const GwyFileDetectInfo *fileinfo, gboolean only_name)
{
    GwyTIFF *tiff;
    gint score = 0;
    gchar *comment = NULL;
    GwyTIFFVersion version = GWY_TIFF_CLASSIC;
    guint byteorder = G_LITTLE_ENDIAN;

    if (only_name)
        return score;

    /* Weed out non-TIFFs */
    if (!gwy_tiff_detect(fileinfo->head, fileinfo->buffer_len, &version, &byteorder))
        return 0;

    /* Use GwyTIFF for detection to avoid problems with fragile libtiff. */
    if ((tiff = gwy_tiff_load(fileinfo->name, NULL))
        && gwy_tiff_get_string0(tiff, MAGELLAN_TIFF_TAG, &comment)
        && strstr(comment, MAGIC_COMMENT))
        score = 100;

    g_free(comment);
    if (tiff)
        gwy_tiff_free(tiff);

    return score;
}

static GwyFile*
load_file(const gchar *filename,
          G_GNUC_UNUSED GwyRunModeFlags mode,
          GError **error)
{
    GwyTIFF *tiff;
    GwyFile *file = NULL;

    tiff = gwy_tiff_load(filename, error);
    if (!tiff)
        return NULL;

    file = mgl_load_tiff(tiff, filename, error);
    gwy_tiff_free(tiff);

    return file;
}

static GwyFile*
mgl_load_tiff(const GwyTIFF *tiff, const gchar *filename, GError **error)
{
    GwyFile *file = NULL;
    GwyDict *meta;
    GwyField *dfield;
    GwyTIFFImageReader *reader = NULL;
    GwyTextHeaderParser parser;
    GHashTable *hash;
    gint i;
    gchar *comment = NULL;
    const gchar *value;
    GError *err = NULL;
    guint dir_num = 0;
    gdouble *data;
    gdouble xstep, ystep, q;
    GString *key = NULL;

    /* Comment with parameters is common for all data fields */
    if (!gwy_tiff_get_string0(tiff, MAGELLAN_TIFF_TAG, &comment) || !strstr(comment, MAGIC_COMMENT)) {
        g_free(comment);
        err_FILE_TYPE(error, "FEI Magellan");
        return NULL;
    }

    /* Read the comment header. */
    gwy_clear1(parser);
    parser.key_value_separator = "=";
    parser.section_template = "[\x1a]";
    parser.section_accessor = "::";
    hash = gwy_text_header_parse(comment, &parser, NULL, NULL);

    if ((value = g_hash_table_lookup(hash, "EScan::PixelWidth"))
        || (value = g_hash_table_lookup(hash, "Scan::PixelWidth"))) {
        gwy_debug("PixelWidth %s", value);
        xstep = g_strtod(value, NULL);
        sanitise_real_size(&xstep, "pixel width");
    }
    else {
        err_MISSING_FIELD(error, "PixelWidth");
        goto fail;
    }

    if ((value = g_hash_table_lookup(hash, "EScan::PixelHeight"))
        || (value = g_hash_table_lookup(hash, "Scan::PixelHeight"))) {
        gwy_debug("PixelHeight %s", value);
        ystep = g_strtod(value, NULL);
        sanitise_real_size(&ystep, "pixel height");
    }
    else {
        err_MISSING_FIELD(error, "PixelHeight");
        goto fail;
    }

    key = g_string_new(NULL);
    for (dir_num = 0; dir_num < gwy_tiff_get_n_dirs(tiff); dir_num++) {
        const gchar *name, *mode;

        reader = gwy_tiff_image_reader_free(reader);
        /* Request a reader, this ensures dimensions and stuff are defined. */
        reader = gwy_tiff_get_image_reader(tiff, dir_num, 3, &err);
        if (!reader) {
            g_warning("Ignoring directory %u: %s", dir_num, err->message);
            g_clear_error(&err);
            continue;
        }
        q = 1.0/((1 << reader->bits_per_sample) - 1);
        name = g_hash_table_lookup(hash, "Detectors::Name");
        mode = g_hash_table_lookup(hash, "Detectors::Mode");

        dfield = gwy_field_new(reader->width, reader->height,
                               reader->width * xstep, reader->height * ystep,
                               FALSE);
        gwy_unit_set_from_string(gwy_field_get_unit_xy(dfield), "m");

        data = gwy_field_get_data(dfield);
        for (i = 0; i < reader->height; i++)
            gwy_tiff_read_image_row_averaged(tiff, reader, i, q, 0.0, data + i*reader->width);

        if (!file)
            file = gwy_file_new_in_construction();

        gwy_file_pass_image(file, dir_num, dfield);
        if (name && mode) {
            gwy_file_pass_title(file, GWY_FILE_IMAGE, dir_num, g_strconcat(name, " ", mode, NULL));
        }
        if ((meta = get_meta(hash)))
            gwy_file_pass_meta(file, GWY_FILE_IMAGE, dir_num, meta);

        gwy_log_add_import(file, GWY_FILE_IMAGE, dir_num, NULL, filename);
    }

    if (!file)
        err_NO_DATA(error);

fail:
    g_hash_table_destroy(hash);
    g_free(comment);
    if (key)
        g_string_free(key, TRUE);
    if (reader) {
        gwy_tiff_image_reader_free(reader);
        reader = NULL;
    }

    return file;
}

static GwyDict*
get_meta(GHashTable *hash)
{
    GwyDict *meta = gwy_dict_new_in_construction();
    g_hash_table_foreach(hash, add_meta, meta);
    if (gwy_dict_get_n_items(meta))
        return meta;

    g_object_unref(meta);
    return NULL;
}

static void
add_meta(gpointer hkey, gpointer hvalue, gpointer user_data)
{
    gchar *value = hvalue, *skey = hkey;

    if (!*value)
        return;

    if (gwy_strequal(skey, "User::UserTextUnicode") || g_str_has_prefix(skey, "PrivateFei::"))
        return;

    value = gwy_convert_to_utf8(value, -1, "ISO-8859-1");
    if (value)
        gwy_dict_pass_string_by_name(GWY_DICT(user_data), skey, value);
}

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