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

/* The format is quite similar to SPIP-ASC, however, there are enough
 * differences to warrant another module. */

/**
 * [FILE-MAGIC-FREEDESKTOP]
 * <mime-type type="application/x-attocube-asc">
 *   <comment>Attocube ASCII data</comment>
 *   <magic priority="80">
 *     <match type="string" offset="0" value="# Daisy "/>
 *   </magic>
 *   <glob pattern="*.asc"/>
 *   <glob pattern="*.ASC"/>
 * </mime-type>
 **/

/**
 * [FILE-MAGIC-FILEMAGIC]
 * # Attocube ASCII data.
 * # There can be, unforutnately, various things after Daisy.  But the next
 * # line starts with a date.
 * 0 string \x23\x20Daisy\x20
 * >&0 search/80 \x23\x20
 * >>&0 regex [0-9]{4}-[0-9]{2}-[0-9]{2} Attocube SPM ASCII data
 **/

/**
 * [FILE-MAGIC-USERGUIDE]
 * Attocube Systems ASC
 * .asc
 * Read
 **/

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

#include "err.h"

// Observed in the wild:
//   Daisy frame view snapshot
//   Daisy saved frame
#define MAGIC "# Daisy "
#define MAGIC_SIZE (sizeof(MAGIC)-1)
#define DATA_MAGIC "# Start of Data:"
#define EXTENSION ".asc"

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 Attocube Systems ASC files."),
    "Yeti <yeti@gwyddion.net>",
    "0.5",
    "David Nečas (Yeti)",
    "2009",
};

GWY_MODULE_QUERY2(module_info, attocube)

static gboolean
module_register(void)
{
    gwy_file_func_register("attocube",
                           N_("Attocube ASCII files (.asc)"),
                           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) ? 10 : 0;

    if (fileinfo->file_size < MAGIC_SIZE || memcmp(fileinfo->head, MAGIC, MAGIC_SIZE) != 0)
        return 0;

    return 100;
}

static GwyFile*
load_file(const gchar *filename,
          G_GNUC_UNUSED GwyRunModeFlags mode,
          GError **error)
{
    GwyFile *file = NULL;
    GwyDict *meta;
    GwyField *dfield = NULL;
    gchar *line, *p, *value, *header, *buffer = NULL;
    GwyTextHeaderParser parser;
    GHashTable *hash = NULL;
    gsize size;
    GError *err = NULL;
    gdouble xreal, yreal, q;
    guint year, month, day, hour, minute, second;
    gint xres, yres, power10;

    if (!g_file_get_contents(filename, &buffer, &size, &err)) {
        err_GET_FILE_CONTENTS(error, &err);
        goto fail;
    }

    p = buffer;
    line = gwy_str_next_line(&p);
    if (!g_str_has_prefix(line, MAGIC)) {
        err_FILE_TYPE(error, "Attocube ASC");
        goto fail;
    }

    line = gwy_str_next_line(&p);
    if (!line || sscanf(line, "# %u-%u-%uT%u:%u:%u", &year, &month, &day, &hour, &minute, &second) != 6) {
        err_FILE_TYPE(error, "Attocube ASC");
        goto fail;
    }

    header = p;
    p = strstr(header, DATA_MAGIC);
    if (!p) {
        err_FILE_TYPE(error, "Attocube ASC");
        goto fail;
    }
    *p = '\0';
    p += strlen(DATA_MAGIC);

    gwy_clear1(parser);
    parser.line_prefix = "#";
    parser.key_value_separator = ":";
    hash = gwy_text_header_parse(header, &parser, NULL, NULL);

    if (!require_keys(hash, error, "x-pixels", "y-pixels", "x-length", "y-length", NULL))
        goto fail;

    xres = atoi(g_hash_table_lookup(hash, "x-pixels"));
    yres = atoi(g_hash_table_lookup(hash, "y-pixels"));
    if (err_DIMENSION(error, xres) || err_DIMENSION(error, yres))
        goto fail;

    xreal = g_ascii_strtod(g_hash_table_lookup(hash, "x-length"), NULL);
    sanitise_real_size(&xreal, "x-length");
    yreal = g_ascii_strtod(g_hash_table_lookup(hash, "y-length"), NULL);
    sanitise_real_size(&yreal, "y-length");

    dfield = gwy_field_new(xres, yres, xreal, yreal, FALSE);

    if ((value = g_hash_table_lookup(hash, "x-unit"))) {
        if ((line = g_hash_table_lookup(hash, "y-unit")) && !gwy_strequal(line, value)) {
            g_warning("X and Y units differ, using X");
        }

        power10 = gwy_unit_set_from_string(gwy_field_get_unit_xy(dfield), value);
        q = gwy_exp10(power10);
        xreal *= q;
        yreal *= q;
        gwy_field_set_xreal(dfield, xreal);
        gwy_field_set_yreal(dfield, yreal);
    }
    else
        q = 1.0;

    if ((value = g_hash_table_lookup(hash, "x-offset")))
        gwy_field_set_xoffset(dfield, q*g_ascii_strtod(value, NULL));
    if ((value = g_hash_table_lookup(hash, "y-offset")))
        gwy_field_set_yoffset(dfield, q*g_ascii_strtod(value, NULL));

    if ((value = g_hash_table_lookup(hash, "z-unit"))) {
        power10 = gwy_unit_set_from_string(gwy_field_get_unit_z(dfield), value);
        q = gwy_exp10(power10);
    }
    else
        q = 1.0;

    if (!(gwy_parse_doubles(p, gwy_field_get_data(dfield), GWY_PARSE_DOUBLES_FREE_FORM,
                            &yres, &xres, NULL, &err))) {
        err_PARSE_DOUBLES(error, &err);
        g_clear_object(&dfield);
        goto fail;
    }
    gwy_field_multiply(dfield, q);

    file = gwy_file_new_in_construction();

    gwy_file_pass_image(file, 0, dfield);

    if ((value = g_hash_table_lookup(hash, "display")))
        gwy_file_set_title(file, GWY_FILE_IMAGE, 0, value, FALSE);

    meta = gwy_dict_new_in_construction();

    value = g_strdup_printf("%04u-%02u-%02u %02u:%02u:%02u",
                            year, month, day, hour, minute, second);
    gwy_dict_pass_string_by_name(meta, "Date", value);

    if ((value = g_hash_table_lookup(hash, "scanspeed")))
        gwy_dict_set_string_by_name(meta, "Scan Speed", value);

    if ((value = g_hash_table_lookup(hash, "x-offset")))
        gwy_dict_set_string_by_name(meta, "X Offset", value);

    if ((value = g_hash_table_lookup(hash, "y-offset")))
        gwy_dict_set_string_by_name(meta, "Y Offset", value);

    gwy_file_pass_meta(file, GWY_FILE_IMAGE, 0, meta);
    gwy_log_add_import(file, GWY_FILE_IMAGE, 0, NULL, filename);

fail:
    g_free(buffer);
    g_hash_table_destroy(hash);

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