/*
 *  $Id: basicops.c 30052 2026-06-12 15:22:24Z yeti-dn $
 *  Copyright (C) 2003-2026 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.
 */

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

#define RUN_MODES GWY_RUN_IMMEDIATE

typedef struct {
    GwyField *fields[2];
    GwyNield *nields[1];
    GQuark quarks[3];
    gint id;
} FieldsAndQuarks;

static gboolean module_register           (void);
static void     flip_horizontally         (GwyFile *data,
                                           GwyRunModeFlags mode);
static void     flip_vertically           (GwyFile *data,
                                           GwyRunModeFlags mode);
static void     flip_diagonally           (GwyFile *data,
                                           GwyRunModeFlags mode);
static void     invert_value              (GwyFile *data,
                                           GwyRunModeFlags mode);
static void     rotate_clockwise_90       (GwyFile *data,
                                           GwyRunModeFlags mode);
static void     rotate_counterclockwise_90(GwyFile *data,
                                           GwyRunModeFlags mode);
static void     rotate_180                (GwyFile *data,
                                           GwyRunModeFlags mode);
static void     square_samples            (GwyFile *data,
                                           GwyRunModeFlags mode);
static void     null_offsets              (GwyFile *data,
                                           GwyRunModeFlags mode);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Basic operations like flipping, value inversion, and rotation by multiples of 90 degrees."),
    "Yeti <yeti@gwyddion.net>",
    "3.0",
    "David Nečas (Yeti) & Petr Klapetek",
    "2003",
};

GWY_MODULE_QUERY2(module_info, basicops)

static gboolean
module_register(void)
{
    gwy_image_func_register("invert_value",
                            invert_value,
                            N_("/_Basic Operations/_Invert Value"),
                            GWY_ICON_VALUE_INVERT,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Invert values about mean"));
    gwy_image_func_register("flip_horizontally",
                            flip_horizontally,
                            N_("/_Basic Operations/Flip _Horizontally"),
                            GWY_ICON_FLIP_HORIZONTALLY,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Flip data horizontally"));
    gwy_image_func_register("flip_vertically",
                            flip_vertically,
                            N_("/_Basic Operations/Flip _Vertically"),
                            GWY_ICON_FLIP_VERTICALLY,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Flip data vertically"));
    gwy_image_func_register("flip_diagonally",
                            flip_diagonally,
                            N_("/_Basic Operations/Flip Dia_gonally"),
                            GWY_ICON_FLIP_DIAGONALLY,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Flip data diagonally"));
    gwy_image_func_register("rotate_180",
                            rotate_180,
                            N_("/_Basic Operations/Flip _Both"),
                            GWY_ICON_ROTATE_180,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Flip data both horizontally and vertically"));
    gwy_image_func_register("rotate_90_cw",
                            rotate_clockwise_90,
                            N_("/_Basic Operations/Rotate C_lockwise"),
                            GWY_ICON_ROTATE_90_CW,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Rotate data 90 degrees clockwise"));
    gwy_image_func_register("rotate_90_ccw",
                            rotate_counterclockwise_90,
                            N_("/_Basic Operations/Rotate _Counterclockwise"),
                            GWY_ICON_ROTATE_90_CCW,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Rotate data 90 degrees counterclockwise"));
    gwy_image_func_register("square_samples",
                            square_samples,
                            N_("/_Basic Operations/S_quare Samples"),
                            GWY_ICON_SQUARE_SAMPLES,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Resample data with non-1:1 aspect ratio to square samples"));
    gwy_image_func_register("null_offsets",
                            null_offsets,
                            N_("/_Basic Operations/_Null Offsets"),
                            GWY_ICON_NULL_OFFSETS,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Null horizontal offsets, moving the origin to the upper left corner"));

    return TRUE;
}

static inline guint
get_fields_and_quarks(FieldsAndQuarks *faq, gboolean masks_too)
{
    gwy_clear(faq, 1);
    gwy_data_browser_get_current(GWY_APP_FIELD, faq->fields + 0,
                                 GWY_APP_SHOW_FIELD, faq->fields + 1,
                                 GWY_APP_FIELD_KEY, faq->quarks + 0,
                                 GWY_APP_SHOW_FIELD_KEY, faq->quarks + 1,
                                 GWY_APP_FIELD_ID, &faq->id,
                                 0);
    gint have_show = !!faq->fields[1];
    if (!masks_too)
        return 1 + have_show;

    gwy_data_browser_get_current(GWY_APP_MASK_FIELD, faq->nields + 0,
                                 GWY_APP_MASK_FIELD_KEY, faq->quarks + 1 + have_show,
                                 0);
    gint have_mask = !!faq->nields[0];
    return 1 + have_show + have_mask;
}

static void
flip(GwyFile *data, gboolean horizontally, gboolean vertically)
{
    FieldsAndQuarks faq;
    guint n = get_fields_and_quarks(&faq, TRUE);
    gwy_app_undo_qcheckpointv(GWY_DICT(data), n, faq.quarks);
    gwy_field_flip(faq.fields[0], horizontally, vertically);
    gwy_field_data_changed(faq.fields[0]);
    if (faq.fields[1]) {
        gwy_field_flip(faq.fields[1], horizontally, vertically);
        gwy_field_data_changed(faq.fields[1]);
    }
    if (faq.nields[0]) {
        gwy_nield_flip(faq.nields[0], horizontally, vertically);
        gwy_nield_data_changed(faq.nields[0]);
    }
    gwy_file_remove_selections(data, GWY_FILE_IMAGE, faq.id);
    gwy_log_add(data, GWY_FILE_IMAGE, faq.id, faq.id);
}

static void
flip_horizontally(GwyFile *data, GwyRunModeFlags mode)
{
    g_return_if_fail(mode & RUN_MODES);
    flip(data, TRUE, FALSE);
}

static void
flip_vertically(GwyFile *data, GwyRunModeFlags mode)
{
    g_return_if_fail(mode & RUN_MODES);
    flip(data, FALSE, TRUE);
}

static void
rotate_180(GwyFile *data, GwyRunModeFlags mode)
{
    g_return_if_fail(mode & RUN_MODES);
    flip(data, TRUE, TRUE);
}

static void
flip_diagonally(GwyFile *data, GwyRunModeFlags mode)
{
    g_return_if_fail(mode & RUN_MODES);

    FieldsAndQuarks faq;
    guint n = get_fields_and_quarks(&faq, TRUE);
    gwy_app_undo_qcheckpointv(GWY_DICT(data), n, faq.quarks);
    GwyField *newfield = gwy_field_new_alike(faq.fields[0], FALSE);
    gwy_field_transpose(faq.fields[0], newfield, FALSE);
    gwy_file_pass_image(data, faq.id, newfield);
    if (faq.fields[1]) {
        newfield = gwy_field_new_alike(faq.fields[1], FALSE);
        gwy_field_transpose(faq.fields[1], newfield, FALSE);
        gwy_dict_pass_object(GWY_DICT(data), gwy_file_key_image_picture(faq.id), newfield);
    }
    if (faq.nields[0]) {
        GwyNield *newmask = gwy_nield_new_alike(faq.nields[0]);
        gwy_nield_transpose(faq.nields[0], newmask, FALSE);
        gwy_file_pass_image_mask(data, faq.id, newmask);
    }
    gwy_file_remove_selections(data, GWY_FILE_IMAGE, faq.id);
    gwy_log_add(data, GWY_FILE_IMAGE, faq.id, faq.id);
}

static void
invert_value(GwyFile *data, GwyRunModeFlags mode)
{
    g_return_if_fail(mode & RUN_MODES);

    FieldsAndQuarks faq;
    guint n = get_fields_and_quarks(&faq, FALSE);
    gwy_app_undo_qcheckpointv(GWY_DICT(data), n, faq.quarks);
    for (guint i = 0; i < 2; i++) {
        if (faq.fields[i]) {
            gwy_field_invert_value(faq.fields[i]);
            gwy_field_data_changed(faq.fields[i]);
        }
    }
    gwy_log_add(data, GWY_FILE_IMAGE, faq.id, faq.id);
}

static void
rotate_90(GwyFile *data, gboolean cw)
{
    FieldsAndQuarks faq;
    guint n = get_fields_and_quarks(&faq, TRUE);
    gwy_app_undo_qcheckpointv(GWY_DICT(data), n, faq.quarks);
    GwyField *newfield = gwy_field_new_rotated_90(faq.fields[0], cw);
    gwy_file_pass_image(data, faq.id, newfield);
    if (faq.fields[1]) {
        newfield = gwy_field_new_rotated_90(faq.fields[1], cw);
        gwy_dict_pass_object(GWY_DICT(data), gwy_file_key_image_picture(faq.id), newfield);
    }
    if (faq.nields[0]) {
        GwyNield *newmask = gwy_nield_new_rotated_90(faq.nields[0], cw);
        gwy_file_pass_image_mask(data, faq.id, newmask);
    }
    gwy_file_remove_selections(data, GWY_FILE_IMAGE, faq.id);
    gwy_log_add(data, GWY_FILE_IMAGE, faq.id, faq.id);
}

static void
rotate_clockwise_90(GwyFile *data, GwyRunModeFlags mode)
{
    g_return_if_fail(mode & RUN_MODES);
    rotate_90(data, TRUE);
}

static void
rotate_counterclockwise_90(GwyFile *data, GwyRunModeFlags mode)
{
    g_return_if_fail(mode & RUN_MODES);
    rotate_90(data, FALSE);
}

static void
square_samples(GwyFile *data, GwyRunModeFlags mode)
{
    g_return_if_fail(mode & RUN_MODES);

    FieldsAndQuarks faq;
    get_fields_and_quarks(&faq, TRUE);
    GwyField *field = faq.fields[0];
    gint xres = gwy_field_get_xres(field), yres = gwy_field_get_yres(field);
    gdouble xreal = gwy_field_get_xreal(field), yreal = gwy_field_get_yreal(field);
    gdouble dx = gwy_field_get_dx(field), dy = gwy_field_get_dy(field);
    if (fabs(log(dx/dy)) > 1.0/hypot(xres, yres)) {
        /* Resample */
        if (dx < dy)
            xres = MAX(GWY_ROUND(xreal*dy), 1);
        else
            yres = MAX(GWY_ROUND(yreal*dx), 1);

        faq.fields[0] = gwy_field_new_resampled(faq.fields[0], xres, yres, GWY_INTERPOLATION_BSPLINE);
        if (faq.fields[1])
            faq.fields[1] = gwy_field_new_resampled(faq.fields[1], xres, yres, GWY_INTERPOLATION_BSPLINE);
        if (faq.nields[0])
            faq.nields[0] = gwy_nield_new_resampled(faq.nields[0], xres, yres);
    }
    else {
        /* Ratios are equal, just duplicate */
        faq.fields[0] = gwy_field_copy(faq.fields[0]);
        if (faq.fields[1])
            faq.fields[1] = gwy_field_copy(faq.fields[1]);
        if (faq.nields[0])
            faq.nields[0] = gwy_nield_copy(faq.nields[0]);
    }

    gint newid = gwy_file_add_image(data, faq.fields[0]);
    g_object_unref(faq.fields[0]);
    gwy_file_set_visible(data, GWY_FILE_IMAGE, newid, TRUE);
    gwy_file_sync_items(data, GWY_FILE_IMAGE, faq.id,
                        data, GWY_FILE_IMAGE, newid,
                        GWY_FILE_ITEM_PALETTE | GWY_FILE_ITEM_RANGE | GWY_FILE_ITEM_MASK_COLOR, FALSE);

    if (faq.fields[1])
        gwy_dict_pass_object(GWY_DICT(data), gwy_file_key_image_picture(newid), faq.fields[1]);
    if (faq.nields[0])
        gwy_file_pass_image_mask(data, newid, faq.nields[0]);

    gwy_log_add(data, GWY_FILE_IMAGE, faq.id, newid);
}

static void
null_offsets(GwyFile *data,
             GwyRunModeFlags mode)
{
    g_return_if_fail(mode & RUN_MODES);

    FieldsAndQuarks faq;
    get_fields_and_quarks(&faq, FALSE);
    guint nn = 0;
    for (guint i = 0; i < 2; i++) {
        if (faq.fields[i] && (gwy_field_get_xoffset(faq.fields[i]) || gwy_field_get_yoffset(faq.fields[i]))) {
            faq.quarks[nn] = faq.quarks[i];
            faq.fields[nn] = faq.fields[i];
            nn++;
        }
    }
    if (!nn)
        return;

    gwy_app_undo_qcheckpointv(GWY_DICT(data), nn, faq.quarks);
    for (guint i = 0; i < nn; i++) {
        gwy_field_set_xoffset(faq.fields[i], 0.0);
        gwy_field_set_yoffset(faq.fields[i], 0.0);
        gwy_field_data_changed(faq.fields[i]);
    }
    gwy_file_remove_selections(data, GWY_FILE_IMAGE, faq.id);
    gwy_log_add(data, GWY_FILE_IMAGE, faq.id, faq.id);
}

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