/*
 *  $Id: flatten_base.c 29972 2026-05-21 17:17:25Z yeti-dn $
 *  Copyright (C) 2014-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 <string.h>
#include <gtk/gtk.h>
#include <gwy.h>

#define RUN_MODES (GWY_RUN_IMMEDIATE | GWY_RUN_INTERACTIVE)

static gboolean module_register(void);
static void     module_main    (GwyFile *data,
                                GwyRunModeFlags mode);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Levels the flat base of a surface with positive features."),
    "Yeti <yeti@gwyddion.net>",
    "1.3",
    "David Nečas (Yeti)",
    "2014",
};

GWY_MODULE_QUERY2(module_info, flatten_base)

static gboolean
module_register(void)
{
    gwy_image_func_register("flatten_base",
                            module_main,
                            N_("/_Level/Flatten _Base"),
                            GWY_ICON_LEVEL_FLATTEN_BASE,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Flatten base of surface with positive features"));

    return TRUE;
}

static gboolean
find_base_peak(GwyField *field, gdouble *mean, gdouble *rms)
{
    GwyNLFitPreset *gaussian;
    GwyNLFitter *fitter;
    GwyLine *dh = gwy_line_new(1, 1.0, FALSE);
    gdouble *d, *xdata, *ydata;
    gdouble real, off, dhmax = -G_MAXDOUBLE;
    gdouble param[4];
    gint i, res, from, to, ndata, m = 0;
    gboolean retval;

    gwy_field_height_dist(field, dh, 0);
    d = gwy_line_get_data(dh);
    res = dh->res;
    real = dh->real;
    off = dh->off;
    for (i = 0; i < res; i++) {
        if (d[i] > dhmax) {
            dhmax = d[i];
            m = i;
        }
    }

    for (from = m; from > 0; from--) {
        if (d[from] < 0.3*dhmax)
            break;
    }
    for (to = m; to < res-1; to++) {
        if (d[to] < 0.3*dhmax)
            break;
    }

    ndata = to+1 - from;
    while (ndata < 7) {
        if (from)
            from--;
        if (to < res-1)
            to++;
        ndata = to+1 - from;
    }

    xdata = gwy_math_linspace(NULL, ndata, off + (from + 0.5)*real/res, real/res);
    ydata = g_new(gdouble, ndata);
    gwy_assign(ydata, d + from, ndata);
    g_object_unref(dh);

    /* x0, y0, a, b */
    param[0] = (m + 0.5)*real/res + off;
    param[1] = 0.0;
    param[2] = dhmax;
    param[3] = 0.3*ndata * real/res;

    gaussian = gwy_inventory_get_item(gwy_nlfit_presets(), "Gaussian");
    fitter = gwy_nlfit_preset_fit(gaussian, NULL, ndata, xdata, ydata, param, NULL, NULL);
    retval = !!gwy_nl_fitter_get_covar(fitter);
    *mean = param[0];
    *rms = param[3]/G_SQRT2;

    g_free(xdata);
    g_free(ydata);
    gwy_nl_fitter_free(fitter);

    return retval;
}

static gboolean
polylevel_with_mask(GwyField *field, GwyNield *mask,
                    gint max_degree,
                    gdouble mean, gdouble rms)
{
    gint nterms = (max_degree + 1)*(max_degree + 2)/2;
    gint *term_powers = g_new(gint, 2*nterms);

    gdouble threshold = mean + 3*rms;
    gwy_nield_mark_by_threshold(mask, field, threshold, TRUE);
    gwy_nield_grow(mask, 1 + max_degree/2, GWY_DISTANCE_TRANSFORM_CONN4, FALSE);

    gint k = 0;
    for (gint i = 0; i <= max_degree; i++) {
        for (gint j = 0; j <= max_degree - i; j++) {
            term_powers[k++] = i;
            term_powers[k++] = j;
        }
    }

    gdouble *coeffs = gwy_field_fit_poly(field, mask, GWY_MASK_EXCLUDE, nterms, term_powers, NULL);
    gwy_field_subtract_poly(field, nterms, term_powers, coeffs);
    g_free(coeffs);

    return TRUE;
}

static void
module_main(GwyFile *data, GwyRunModeFlags mode)
{
    GwyField *field, *origfield;
    GwyNield *mask = NULL;
    GQuark quark;
    gdouble mean, sigma, min, a, bx, by;
    gboolean found_peak;
    gint id, i, totalsteps = 5 + 4, step = 0;

    g_return_if_fail(mode & RUN_MODES);
    gwy_data_browser_get_current(GWY_APP_FIELD_KEY, &quark,
                                 GWY_APP_FIELD, &origfield,
                                 GWY_APP_FIELD_ID, &id,
                                 0);
    g_return_if_fail(origfield && quark);

    gwy_app_wait_start(gwy_data_browser_get_window_for_data(data, GWY_FILE_IMAGE, id), _("Facet-leveling..."));

    field = gwy_field_copy(origfield);
    found_peak = find_base_peak(field, &mean, &sigma);
    gwy_debug("initial peak: %s (mean=%g, rms=%g)", found_peak ? "OK" : "NOT FOUND", mean, sigma);

    for (i = 0; i < 5; i++) {
        if (!gwy_field_fit_facet_plane(field, NULL, GWY_MASK_IGNORE, &a, &bx, &by))
            break;

        gwy_field_plane_level(field, a, bx, by);
        found_peak = find_base_peak(field, &mean, &sigma);
        gwy_debug("facet[%d] peak: %s (mean=%g, rms=%g)", i, found_peak ? "OK" : "NOT FOUND", mean, sigma);
        if (!found_peak)
            break;

        if (!gwy_app_wait_set_fraction((step + i + 1.0)/totalsteps))
            goto cancelled;
    }

    step = 5;
    if (!gwy_app_wait_set_message(_("Polynomial leveling...")))
        goto cancelled;

    mask = gwy_field_new_nield_alike(field);
    for (i = 2; i <= 5; i++) {
        polylevel_with_mask(field, mask, i, mean, sigma);
        found_peak = find_base_peak(field, &mean, &sigma);
        gwy_debug("poly[%d] peak: %s (mean=%g, rms=%g)", i, found_peak ? "OK" : "NOT FOUND", mean, sigma);
        if (!found_peak)
            break;

        if (!gwy_app_wait_set_fraction((step + i - 1.0)/totalsteps))
            goto cancelled;
    }

    gwy_app_wait_finish();

    if (found_peak)
        gwy_field_add(field, -mean);

    if ((min = gwy_field_min(field)) > 0.0)
        gwy_field_add(field, -min);

    gwy_app_undo_qcheckpoint(GWY_DICT(data), quark, NULL);
    gwy_field_copy_data(field, origfield);
    gwy_log_add(data, GWY_FILE_IMAGE, id, id);
    gwy_field_data_changed(origfield);

cancelled:
    g_clear_object(&mask);
    g_object_unref(field);
}

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