/*
 *  $Id: nanotop.c, v 1.2 2006/04/06 16:32:48
 *  Copyright (C) 2006 Alexander Kovalev, Metal-Polymer Research Institute
 *  E-mail: av_kov@tut.by
 *
 *  Partially based on apefile.c,
 *  Copyright (C) 2005-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-nanotop-spm">
 *   <comment>Nanotop SPM data</comment>
 *   <glob pattern="*.spm"/>
 *   <glob pattern="*.SPM"/>
 * </mime-type>
 **/

/**
 * [FILE-MAGIC-USERGUIDE]
 * Nanotop SPM
 * .spm
 * Read
 **/

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

#include "get.h"
#include "err.h"

#define EXTENSION ".spm"
#define nanometer (1e-9)

enum {
    PING_SIZE = 4*sizeof(guint16),
    HEADER_SIZE = 512,
};

/* =============== header of SPM file======================================= */
typedef struct {
  guint16 tx, mx;       /* number of points for one step and full X-size in points; tx - not used */
  guint16 ty, my;       /* number of points for one step and full Y-size in points; ty - not used */
  gdouble Kx, Ky, Kz;   /* scale factor for X,Y and Z axes (nm/point)                             */
  gchar ZUnit[6];       /* label of z-axis                                                        */
  gchar XYUnit[6];      /* label of scanning plane                                                */
  guint16 min;          /* min of data                                                            */
  guint16 max;          /* max of data                                                            */
  guint16 timeline;     /* time of scanning line                                                  */
  gchar date[8];        /* date of creation data                                                  */
  gchar time[5];        /* time of creation data                                                  */
  gchar note[301];      /* notes                                                                  */
  gchar void_field[94]; /* reserved                                                               */
  gchar Version[64];    /* version of SPM "Nanotop"                                               */
} SPMFile;
/* it's consists of 512 bytes                                             */
/* ========================================================================= */

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* read_field     (SPMFile *spmfile,
                                 const guchar *ptr);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Imports NANOTOP AFM files"),
    "Alexander Kovalev <av_kov@tut.by>",
    "2.0",
    "Alexander Kovalev, Metal-Polymer Research Institute",
    "2006",
};

GWY_MODULE_QUERY2(module_info, nanotop)

static gboolean
module_register(void)
{
    gwy_file_func_register("nanotop",
                           N_("Nanotop files (.spm)"),
                           detect_file, load_file, NULL, NULL);

    return TRUE;
}

static gint
detect_file(const GwyFileDetectInfo *fileinfo, gboolean only_name)
{
    gint score = 0;
    gsize expected = 0;
    guint xres, yres;
    const guchar *p;

    if (only_name)
        return g_str_has_suffix(fileinfo->name_lowercase, EXTENSION) ? 15 : 0;

    if (fileinfo->buffer_len < PING_SIZE)
        return 0;

    p = fileinfo->head;
    gwy_get_guint16_le(&p);
    xres = gwy_get_guint16_le(&p);
    gwy_get_guint16_le(&p);
    yres = gwy_get_guint16_le(&p);

    expected = 2*xres*yres + HEADER_SIZE; /* expected file size */
    if (expected == fileinfo->file_size)
        score = 100;      /* that is fine! */

    return score;
}

static GwyFile*
load_file(const gchar *filename,
          G_GNUC_UNUSED GwyRunModeFlags mode,
          GError **error)
{
    SPMFile spmfile;
    GwyFile *file = NULL;
    const guchar *p, *buffer = NULL;
    gsize size = 0;
    GError *err = NULL;
    GwyField *dfield = NULL;

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

    /* read file header */
    if (size < HEADER_SIZE + 2) {
        err_TOO_SHORT(error);
        gwy_unmap_file_contents(buffer);
        return NULL;
    }

    p = buffer;
    spmfile.tx = gwy_get_guint16_le(&p);
    spmfile.mx = gwy_get_guint16_le(&p);
    spmfile.ty = gwy_get_guint16_le(&p);
    spmfile.my = gwy_get_guint16_le(&p);

    if (err_DIMENSION(error, spmfile.mx) || err_DIMENSION(error, spmfile.my)) {
        gwy_unmap_file_contents(buffer);
        return NULL;
    }

    if (err_SIZE_MISMATCH(error, HEADER_SIZE + 2*spmfile.mx*spmfile.my, size, TRUE)) {
        gwy_unmap_file_contents(buffer);
        return NULL;
    }

    spmfile.Kx = gwy_get_gfloat_le(&p);
    spmfile.Ky = gwy_get_gfloat_le(&p);
    spmfile.Kz = gwy_get_gfloat_le(&p);
    get_CHARARRAY0(spmfile.ZUnit, &p);
    get_CHARARRAY0(spmfile.XYUnit, &p);
    spmfile.min = gwy_get_guint16_le(&p);
    spmfile.max = gwy_get_guint16_le(&p);
    spmfile.timeline = gwy_get_guint16_le(&p);
    get_CHARARRAY(spmfile.date, &p);
    get_CHARARRAY(spmfile.time, &p);
    get_CHARARRAY(spmfile.note, &p);
    get_CHARARRAY(spmfile.void_field, &p);
    get_CHARARRAY(spmfile.Version, &p);

    /* read data from buffer */
    dfield = read_field(&spmfile, p + 2);

    if (dfield) {
        file = gwy_file_new_in_construction();
        gwy_file_pass_image(file, 0, dfield);
        gwy_image_title_fall_back(file, 0);
    }
    gwy_log_add_import(file, GWY_FILE_IMAGE, 0, NULL, filename);

    gwy_unmap_file_contents(buffer);

    return file;
}

static GwyField*
read_field(SPMFile *spmfile, const guchar *ptr)
{
    GwyField *dfield;
    const gchar *unitstr;
    gdouble xreal, yreal;

    xreal = spmfile->mx*spmfile->Kx;
    yreal = spmfile->my*spmfile->Ky;
    sanitise_real_size(&xreal, "x size");
    sanitise_real_size(&yreal, "y size");

    dfield = gwy_field_new(spmfile->mx, spmfile->my, xreal*nanometer, yreal*nanometer, FALSE);
    gwy_convert_raw_data(ptr, spmfile->mx * spmfile->my, 1, GWY_RAW_DATA_SINT16, GWY_BYTE_ORDER_LITTLE_ENDIAN,
                         gwy_field_get_data(dfield), 1.0, 0.0);

    if (!gwy_strequal(spmfile->ZUnit, "deg")) {
        gwy_field_multiply(dfield, spmfile->Kz*nanometer);
        unitstr = "m";
    }
    else {
        gwy_field_multiply(dfield, spmfile->Kz);
        unitstr = "deg";
    }
    gwy_unit_set_from_string(gwy_field_get_unit_z(dfield), unitstr);
    gwy_unit_set_from_string(gwy_field_get_unit_xy(dfield), "m");

    return dfield;
}

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