/*
 *  $Id: zeiss.c 29611 2026-03-17 10:31:06Z yeti-dn $
 *  Copyright (C) 2011-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]
 * Carl Zeiss SEM scans
 * .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 "\r\nAP_PIXEL_SIZE\r\n"
#define SOMEWHAT_LESS_MAGIC_COMMENT "0\r\n0\r\n0\r\n"

enum {
    ZEISS_HEADER_TAG = 34118,
};

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*    zeiss_load_tiff(const GwyTIFF *tiff,
                                   GError **error);
static void        add_meta       (gpointer hkey,
                                   gpointer hvalue,
                                   gpointer user_data);
static GHashTable* parse_comment  (gchar *text);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    module_register,
    N_("Imports Carl Zeiss SEM images."),
    "Yeti <yeti@gwyddion.net>",
    "0.7",
    "David Nečas (Yeti)",
    "2011",
};

GWY_MODULE_QUERY2(module_info, zeiss)

static gboolean
module_register(void)
{
    gwy_file_func_register("zeiss",
                           N_("Carl Zeiss SEM scans (.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, ZEISS_HEADER_TAG, &comment)) {
        if (strstr(comment, MAGIC_COMMENT))
            score = 100;
        else if (g_str_has_prefix(comment, SOMEWHAT_LESS_MAGIC_COMMENT))
            score = 85;
    }

    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 = zeiss_load_tiff(tiff, error);
    if (file)
        gwy_log_add_import(file, GWY_FILE_IMAGE, 0, NULL, filename);

    gwy_tiff_free(tiff);

    return file;
}

static GwyFile*
zeiss_load_tiff(const GwyTIFF *tiff, GError **error)
{
    GwyFile *file = NULL;
    GwyDict *meta = NULL;
    GwyField *dfield;
    GwyUnit *siunit;
    GwyTIFFImageReader *reader = NULL;
    GHashTable *hash = NULL;
    gint i, power10;
    gchar *value, *end, *comment = NULL;
    gdouble *data;
    gboolean new_file;
    double factor, dx;

    /* Comment with parameters is common for all data fields */
    if (!gwy_tiff_get_string0(tiff, ZEISS_HEADER_TAG, &comment)) {
        err_FILE_TYPE(error, "Carl Zeiss SEM");
        goto fail;
    }

    if (strstr(comment, MAGIC_COMMENT))
        new_file = TRUE;
    else if (g_str_has_prefix(comment, SOMEWHAT_LESS_MAGIC_COMMENT))
        new_file = FALSE;
    else {
        err_FILE_TYPE(error, "Carl Zeiss SEM");
        goto fail;
    }

    /* Read the comment header. */
    if (new_file) {
        hash = parse_comment(comment);
        if ((value = g_hash_table_lookup(hash, "Image Pixel Size"))) {
            gwy_debug("Using dx from Image Pixel Size: %s", value);
        }
        else if ((value = g_hash_table_lookup(hash, "Pixel Size"))) {
            gwy_debug("Using dx from Pixel Size: %s", value);
        }
        else {
            err_MISSING_FIELD(error, "Pixel Size");
            goto fail;
        }
    }
    else {
        /* The first thing is the pixel size, apparently. */
        value = comment + strlen(SOMEWHAT_LESS_MAGIC_COMMENT);
        gwy_debug("Using dx from old-style comment: %s", value);
    }

    dx = g_ascii_strtod(value, &end);
    sanitise_real_size(&dx, "pixel size");
    if (!new_file)
        end = "m";

    /* Request a reader, this ensures dimensions and stuff are defined.
     * NB: Newer versions store the image as RGB. Not useful here; just average the channels.  */
    if (!(reader = gwy_tiff_get_image_reader(tiff, 0, 3, error)))
        goto fail;

    siunit = gwy_unit_new_parse(end, &power10);
    factor = gwy_exp10(power10);
    dfield = gwy_field_new(reader->width, reader->height,
                           reader->width * factor * dx, reader->height * factor * dx,
                           FALSE);
    gwy_unit_assign(gwy_field_get_unit_xy(dfield), siunit);
    g_object_unref(siunit);

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

    file = gwy_file_new_in_construction();
    gwy_file_pass_image(file, 0, dfield);
    gwy_file_set_title(file, GWY_FILE_IMAGE, 0, "Secondary electron count", FALSE);

    if (new_file) {
        meta = gwy_dict_new_in_construction();
        g_hash_table_foreach(hash, add_meta, meta);
        if (gwy_dict_get_n_items(meta))
            gwy_file_pass_meta(file, GWY_FILE_IMAGE, 0, meta);
        else
            g_object_unref(meta);
    }

fail:
    if (hash)
        g_hash_table_destroy(hash);
    g_free(comment);

    return file;
}

static void
add_meta(gpointer hkey, gpointer hvalue, gpointer user_data)
{
    gchar *value = gwy_convert_to_utf8((const gchar*)hvalue, -1, "ISO-8859-1");
    if (value)
        gwy_dict_pass_string_by_name(GWY_DICT(user_data), (gchar*)hkey, value);
}

static GHashTable*
parse_comment(gchar *text)
{
    gchar *line, *value, *p = text;
    GHashTable *hash = g_hash_table_new(g_str_hash, g_str_equal);

    for (line = gwy_str_next_line(&p); line; line = gwy_str_next_line(&p)) {
        if (g_str_has_prefix(line, "Date :") || g_str_has_prefix(line, "Time :"))
            value = line + 6;
        else if (!(value = strchr(line, '=')))
            continue;

        *value = '\0';
        value++;
        g_strstrip(line);
        g_strstrip(value);
        g_hash_table_insert(hash, line, value);
        gwy_debug("<%s> = <%s>", line, value);
    }

    return hash;
}

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