/*
 *  $Id: cdline.c 29754 2026-04-09 09:35:42Z yeti-dn $
 *  Copyright (C) 2004-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.
 */

#include "config.h"
#include <string.h>
#include <glib/gi18n-lib.h>
#include <libgwyddion/macros.h>
#include <libgwyddion/math.h>
#include <libgwyddion/cdline.h>
#include <libgwyddion/nl-fitter.h>

typedef struct {
    const char *name;
    gint power_x;
    gint power_y;
} GwyCDLineParam;

typedef gdouble (*GwyCDLineFitFunc)(gdouble x,
                                    const gdouble *param,
                                    gboolean *fres);

typedef void (*GwyCDLineCDFunc)(const gdouble *x,
                                const gdouble *y,
                                gint n_dat,
                                gdouble *param,
                                gdouble *err,
                                gboolean *fres);

/* If we ever have non-builtin CDlines, we may need to move this to some GwyCDLineBuiltin struct and put just
 * a pointer to GwyCDLinePrivate. But there is no need now. */
typedef struct {
    const gchar *function_name;
    const gchar *group_name;
    const gchar *function_definition;
    GwyCDLineFitFunc function;
    GwyCDLineCDFunc function_fit;
    gint nparams;
    const GwyCDLineParam *param;
} GwyCDLineBuiltin;

struct _GwyCDLinePrivate {
    const GwyCDLineBuiltin *builtin;
};

static GwyCDLine* create_cdline_builtin  (const GwyCDLineBuiltin *data);
static void       resource_setup_builtins(GwyInventory *inventory);

static GwyResourceClass *parent_class = NULL;

G_DEFINE_TYPE_WITH_CODE(GwyCDLine, gwy_cdline, GWY_TYPE_RESOURCE,
                        G_ADD_PRIVATE(GwyCDLine))

static void
get_linestatpars(const gdouble *y, gint ndat,
                 gint from, gint to,
                 gdouble *avg, gdouble *sigma)
{
    gint i, n;

    from = CLAMP(from, 0, ndat);
    to = CLAMP(to, 0, ndat);
    GWY_ORDER(gint, from, to);

    *avg = 0;
    *sigma = 0;

    n = to - from;
    if (n <= 0)
        return;

    for (i = from; i < to; i++)
        *avg += y[i];
    *avg /= n;

    for (i = from; i < to; i++)
        *sigma += (y[i] - *avg)*(y[i] - *avg);
    *sigma = sqrt(*sigma/n);
}

static void
get_linestatpars2(const gdouble *y, gint ndat,
                  gint from1, gint to1,
                  gint from2, gint to2,
                  gdouble *avg, gdouble *sigma)
{
    gint i, n;

    from1 = CLAMP(from1, 0, ndat);
    to1 = CLAMP(to1, 0, ndat);

    from2 = CLAMP(from2, 0, ndat);
    to2 = CLAMP(to2, 0, ndat);

    GWY_ORDER(gint, from1, to1);
    GWY_ORDER(gint, from2, to2);
    if (from1 > from2) {
        GWY_SWAP(gint, from1, from2);
        GWY_SWAP(gint, to1, to2);
    }
    /* Merge overlapping intervals to one. */
    if (from2 <= to1) {
        get_linestatpars(y, ndat, from1, MAX(to1, to2), avg, sigma);
        return;
    }

    if (to1 == from1 && from1 == 0)
        to1++;
    if (to2 == from2 && to2 == ndat)
        from2--;

    *avg = 0;
    *sigma = 0;

    n = to1 - from1 + to2 - from2;
    if (n <= 0)
        return;

    for (i = from1; i < to1; i++)
        *avg += y[i];
    for (i = from2; i < to2; i++)
        *avg += y[i];
    *avg /= n;

    for (i = from1; i < to1; i++)
        *sigma += (y[i] - *avg)*(y[i] - *avg);
    for (i = from2; i < to2; i++)
        *sigma += (y[i] - *avg)*(y[i] - *avg);
    *sigma = sqrt(*sigma/n);
}

static void
cd_uedgeheight(const gdouble *x,
               const gdouble *y,
               gint n_dat,
               gdouble *param,
               gdouble *err,
               gboolean *fres)
{
    gint i;
    gint nstep;
    gdouble max, val;
    gint imax, iwidth;

    nstep = n_dat/20;
    iwidth = n_dat/8;
    if (nstep < 1)
        nstep = 1;

    max = -G_MAXDOUBLE;
    imax = nstep/2;
    for (i = nstep; i < (n_dat - 2 * nstep); i++) {
        val = ((y[i + nstep] - y[i])/(x[i + nstep] - x[i]));
        if (max < val) {
            max = val;
            imax = i + nstep/2;
            param[1] = (x[i + nstep] + x[i])/2.0;
        }
    }

    get_linestatpars(y, n_dat, 0, imax - iwidth/2, param + 2, err + 2);
    get_linestatpars(y, n_dat, imax + iwidth/2, n_dat, param + 3, err + 3);

    param[0] = param[3] - param[2];
    err[0] = hypot(err[2], err[3]);
    err[1] = -1;

    *fres = TRUE;
}

static void
cd_ledgeheight(const gdouble *x,
               const gdouble *y,
               gint n_dat,
               gdouble *param,
               gdouble *err,
               gboolean *fres)
{
    gint i;
    gint nstep;
    gdouble min, val;
    gint imin, iwidth;

    nstep = n_dat/20;
    iwidth = n_dat/8;
    if (nstep < 1)
        nstep = 1;

    min = G_MAXDOUBLE;
    imin = nstep/2;
    for (i = nstep; i < (n_dat - 2 * nstep); i++) {
        val = ((y[i + nstep] - y[i])/(x[i + nstep] - x[i]));
        if (min > val) {
            min = val;
            imin = i + nstep/2;
            param[1] = (x[i + nstep] + x[i])/2.0;
        }
    }

    get_linestatpars(y, n_dat, 0, imin - iwidth/2, param + 2, err + 2);
    get_linestatpars(y, n_dat, imin + iwidth/2, n_dat, param + 3, err + 3);

    param[0] = param[2] - param[3];

    err[0] = hypot(err[2], err[3]);
    err[1] = -1;
    *fres = TRUE;
}

static gdouble
func_edgeheight(gdouble x,
                const gdouble *param,
                G_GNUC_UNUSED gboolean *fres)
{
    if (x < param[1])
        return param[2];
    else
        return param[3];
}

static void
cd_rstepheight(const gdouble *x,
               const gdouble *y,
               gint n_dat,
               gdouble *param,
               gdouble *err,
               gboolean *fres)
{
    gint i;
    gint nstep;
    gdouble max, min, val;
    gint imax, imin, iwidth;

    nstep = n_dat/20;
    if (nstep < 1)
        nstep = 1;

    max = -G_MAXDOUBLE;
    min = G_MAXDOUBLE;

    imax = imin = nstep/2;
    for (i = nstep; i < (n_dat - 2 * nstep); i++) {
        val = ((y[i + nstep] - y[i])/(x[i + nstep] - x[i]));
        if (min > val) {
            min = val;
            imin = i + nstep/2;
            param[3] = (x[i + nstep] + x[i])/2.0;
        }
    }

    for (i = imin; i < (n_dat - 2 * nstep); i++) {
        val = ((y[i + nstep] - y[i])/(x[i + nstep] - x[i]));
        if (max < val) {
            max = val;
            imax = i + nstep/2;
            param[4] = (x[i + nstep] + x[i])/2.0;
        }
    }
    iwidth = imax - imin;

    /*FIXME modidfied now (imin+iwidth/3, imax-iwidth/3) */
    get_linestatpars(y, n_dat, imin + iwidth/3, imax - iwidth/3,
                     param+2, err+2);
    /* XXX: The +1s correspond to sharp inequalities in original code. */
    get_linestatpars2(y, n_dat,
                      imin - iwidth + 1, imin - iwidth/3,
                      imax + iwidth/3 + 1, imax + iwidth,
                      param+1, err+1);
    param[0] = param[2] - param[1];

    err[0] = hypot(err[2], err[1]);
    err[3] = err[4] = -1;
    *fres = TRUE;
}

static void
cd_stepheight(const gdouble *x,
              const gdouble *y,
              gint n_dat,
              gdouble *param,
              gdouble *err,
              gboolean *fres)
{
    gint i;
    gint nstep;
    gdouble max, min, val;
    gint imax, imin, iwidth;

    nstep = n_dat/20;
    if (nstep < 1)
        nstep = 1;

    max = -G_MAXDOUBLE;
    min = G_MAXDOUBLE;
    imax = imin = nstep/2;

    for (i = nstep; i < (n_dat - 2 * nstep); i++) {
        val = ((y[i + nstep] - y[i])/(x[i + nstep] - x[i]));
        if (max < val) {
            max = val;
            imax = i + nstep/2;
            param[3] = (x[i + nstep] + x[i])/2.0;
        }
    }

    for (i = imax; i < (n_dat - 2 * nstep); i++) {
        val = ((y[i + nstep] - y[i])/(x[i + nstep] - x[i]));
        if (min > val) {
            min = val;
            imin = i + nstep/2;
            param[4] = (x[i + nstep] + x[i])/2.0;
        }
    }
    iwidth = imin - imax;

    /*FIXME: modified now (imax+iwidth/3, imin-iwidth/3) */
    get_linestatpars(y, n_dat, imax + iwidth/3, imin - iwidth/3, param + 2,
                     err + 2);
    /* XXX: The +1s correspond to sharp inequalities in original code. */
    get_linestatpars2(y, n_dat,
                      imax - iwidth + 1, imax - iwidth/3,
                      imin + iwidth/3 + 1, imin + iwidth,
                      param+1, err+1);

    param[0] = param[2] - param[1];

    err[0] = hypot(err[2], err[1]);
    err[3] = err[4] = -1;

    *fres = TRUE;
}

static gdouble
func_stepheight(gdouble x,
                const gdouble *param,
                G_GNUC_UNUSED gboolean *fres)
{
    if (x > param[3] && x < param[4])
        return param[2];
    else
        return param[1];
}

static gdouble
func_circle_down(gdouble x,
                 const gdouble *param,
                 gboolean *fres)
{
    *fres = TRUE;
    if ((param[0]*param[0] - (x-param[1])*(x-param[1])) > 0.0)
        return param[2] - sqrt(param[0]*param[0] - (x-param[1])*(x-param[1]));
    else
        return param[2];
}

static gdouble
fitfunc_circle_down(gdouble x,
                    const gdouble *param,
                    G_GNUC_UNUSED gpointer user_data,
                    gboolean *fres)
{
    return func_circle_down(x, param, fres);
}

static void
cd_circle_down(const gdouble *x,
               const gdouble *y,
               gint n_dat,
               gdouble *param,
               gdouble *err,
               gboolean *fres)
{
    gdouble x1, x2, x3, y11, y2, y3, rx, ry, res; /* y1 already used somewhere... */
    gint i, m;
    GwyNLFitter *fitter;
    GwyNLFitFunc ff;
    gdouble par_init[3];
    gdouble par_res[3];
    gboolean par_fix[3];

    m = n_dat/2;
    if (x[0] != x[n_dat-1]) {
        x1 = x[0];
        y11 = y[0];
        x2 = x[m];
        y2 = y[m];
        x3 = x[n_dat-1];
        y3 = y[n_dat-1];
    }
    else {
        x1 = x[m];
        y11 = y[m];
        x2 = x[n_dat-1];
        y2 = y[n_dat-1];
        x3 = x[0];
        y3 = y[0];
    }
    ry = ((x1-x3)*((x2-x1)*(x2-x1)+(y2-y11)*(y2-y11)) + (x2-x1)*((x3-x1)*(x3-x1)+(y3-y11)*(y3-y11)))/
        (2*((x2-x1)*(y3-y11)-(x3-x1)*(y2-y11)));
    rx = ((x3-x1)*(x3-x1)+(y3-y11)*(y3-y11)+2*ry*(y3-y11))/(-2*(x3-x1));
    par_init[0] = sqrt(rx*rx+ry*ry); /*r*/
    par_init[1] = x1-rx; /*x0*/
    par_init[2] = y11+ry; /*y0*/
    for (i = 0; i < 3; i++) {
        par_fix[i] = FALSE;
        par_res[i] = par_init[i];
    }
    ff = fitfunc_circle_down;
    fitter = gwy_nl_fitter_new(ff, NULL);
    res = gwy_nl_fitter_fit_full(fitter, n_dat, x, y, NULL, 3, par_res, par_fix, NULL, NULL);
    if (res > 0.0){
        if (par_res[0] < 0.0)
            par_res[0] *= -1;
        for (i = 0; i < 3; i++) {
            param[i] = par_res[i];
            err[i] = gwy_nl_fitter_get_sigma(fitter, i);
        }
        *fres = TRUE;
    }
    else
        *fres = FALSE;
}

static gdouble
func_circle_up(gdouble x,
               const gdouble *param,
               gboolean *fres)
{
    *fres = TRUE;
    if ( (param[0]*param[0] - (x-param[1])*(x-param[1])) > 0.0)
        return param[2] + sqrt(param[0]*param[0] - (x-param[1])*(x-param[1]));
    else
        return param[2];
}

static gdouble
fitfunc_circle_up(gdouble x,
                  const gdouble *param,
                  G_GNUC_UNUSED gpointer user_data,
                  gboolean *fres)
{
    return func_circle_up(x, param, fres);
}

static void
cd_circle_up(const gdouble *x,
             const gdouble *y,
             gint n_dat,
             gdouble *param,
             gdouble *err,
             gboolean *fres)
{
    gdouble x1, x2, x3, y11, y2, y3, rx, ry, res;
    gint i, m;
    GwyNLFitter *fitter;
    GwyNLFitFunc ff;
    gdouble par_init[3];
    gdouble par_res[3];
    gboolean par_fix[3];

    m = n_dat/2;
    if (x[0] != x[n_dat-1]) {
        x1 = x[0];
        y11 = y[0];
        x2 = x[m];
        y2 = y[m];
        x3 = x[n_dat-1];
        y3 = y[n_dat-1];
    }
    else{
        x1 = x[m];
        y11 = y[m];
        x2 = x[n_dat-1];
        y2 = y[n_dat-1];
        x3 = x[0];
        y3 = y[0];
    }

    ry = ((x1-x3)*((x2-x1)*(x2-x1)+(y2-y11)*(y2-y11)) + (x2-x1)*((x3-x1)*(x3-x1)+(y3-y11)*(y3-y11)))/
        (2*((x2-x1)*(y3-y11)-(x3-x1)*(y2-y11)) );
    rx = ((x3-x1)*(x3-x1)+(y3-y11)*(y3-y11)+2*ry*(y3-y11))/(-2*(x3-x1));
    par_init[0] = sqrt(rx*rx+ry*ry); /*r*/
    par_init[1] = x1-rx; /*x0*/
    par_init[2] = y11+ry; /*y0*/
    for (i = 0; i < 3; i++) {
        par_fix[i] = FALSE;
        par_res[i] = par_init[i];
    }
    ff = fitfunc_circle_up;
    fitter = gwy_nl_fitter_new(ff, NULL);
    res = gwy_nl_fitter_fit_full(fitter, n_dat, x, y, NULL, 3, par_res, par_fix, NULL, NULL);
    if (res > 0.0) {
        for (i = 0; i < 3; i++) {
            param[i] = par_res[i];
            err[i] = gwy_nl_fitter_get_sigma(fitter, i);
        }
        *fres = TRUE;
    }
    else
        *fres = FALSE;
}

/************************** cdlines ****************************/

static const GwyCDLineParam stepheight_pars[] = {
    { "h",             0, 1, },
    { "y<sub>1</sub>", 0, 1, },
    { "y<sub>2</sub>", 0, 1, },
    { "x<sub>1</sub>", 1, 0, },
    { "x<sub>2</sub>", 1, 0, },
};

static const GwyCDLineParam edgeheight_pars[] = {
    { "h",             0, 1, },
    { "x",             1, 0, },
    { "y<sub>1</sub>", 0, 1, },
    { "y<sub>2</sub>", 0, 1, },
};

static const GwyCDLineParam circle_pars[] = {
    { "r",             0, 1, },
    { "x<sub>0</sub>", 1, 0, },
    { "y<sub>0</sub>", 0, 1, },
};

static const GwyCDLineBuiltin cdlines[] = {
    {
        N_("Edge height (right)"),
        N_("Edge"),
        "cd_step",
        func_edgeheight,
        cd_uedgeheight,
        G_N_ELEMENTS(edgeheight_pars),
        edgeheight_pars
    },
    {
        N_("Edge height (left)"),
        N_("Edge"),
        "cd_rstep",
        func_edgeheight,
        cd_ledgeheight,
        G_N_ELEMENTS(edgeheight_pars),
        edgeheight_pars
    },
    {
        N_("Step height (positive)"),  /* ISO 5436 */
        N_("Line"),
        "cd_line",
        func_stepheight,
        cd_stepheight,
        G_N_ELEMENTS(stepheight_pars),
        stepheight_pars
    },
    {
        N_("Step height (negative)"),
        N_("Line"),
        "cd_rline",
        func_stepheight,
        cd_rstepheight,
        G_N_ELEMENTS(stepheight_pars),
        stepheight_pars
    },
    {
        N_("Circle (down)"),
        N_("Circle"),
        "circle_down",
        func_circle_down,
        cd_circle_down,
        G_N_ELEMENTS(circle_pars),
        circle_pars
    },
    {
        N_("Circle (up)"),
        N_("Circle"),
        "circle_up",
        func_circle_up,
        cd_circle_up,
        G_N_ELEMENTS(circle_pars),
        circle_pars
    },
};

/**
 * gwy_cdline_get_name:
 * @cdline: A critical dimension evaluator.
 *
 * Return cdline name (its unique identifier).
 *
 * Returns: The cdline name.
 **/
const gchar*
gwy_cdline_get_name(GwyCDLine* cdline)
{
    g_return_val_if_fail(GWY_IS_CDLINE(cdline), NULL);

    const GwyCDLineBuiltin *builtin = cdline->priv->builtin;
    g_return_val_if_fail(builtin, NULL);
    return builtin->function_name;
}

/**
 * gwy_cdline_get_definition:
 * @cdline: A critical dimension evaluator.
 *
 * Gets the name of the image file with critical dimension evaluator description.
 *
 * Returns: The cdline function definition.
 **/
const gchar*
gwy_cdline_get_definition(GwyCDLine* cdline)
{
    g_return_val_if_fail(GWY_IS_CDLINE(cdline), NULL);

    const GwyCDLineBuiltin *builtin = cdline->priv->builtin;
    g_return_val_if_fail(builtin, NULL);
    return builtin->function_definition;
}

/**
 * gwy_cdline_get_param_name:
 * @cdline: A NL evaluator function cdline.
 * @i: Parameter index.
 *
 * Returns the name of a critical dimension evaluator parameter.
 *
 * The name may contain Pango markup.
 *
 * Returns: The name of @i-th parameter.
 **/
const gchar*
gwy_cdline_get_param_name(GwyCDLine* cdline,
                          gint i)
{
    g_return_val_if_fail(GWY_IS_CDLINE(cdline), NULL);

    const GwyCDLineBuiltin *builtin = cdline->priv->builtin;
    g_return_val_if_fail(builtin, NULL);
    g_return_val_if_fail(i >= 0 && i < builtin->nparams, NULL);

    const GwyCDLineParam *param = builtin->param + i;

    return param->name;
}

/**
 * gwy_cdline_get_param_units:
 * @cdline: A critical dimension evaluator.
 * @i: Parameter index.
 * @unit_x: SI unit of abscissa.
 * @unit_y: SI unit of ordinate.
 *
 * Derives the SI unit of a critical dimension parameter from the units of abscissa and ordinate.
 *
 * Returns: A newly created #GwyUnit with the units of the @i-th parameter. If the units are not
 *          representable as #GwyUnit, the result is unitless (i.e. it will be presented as a mere number).
 **/
GwyUnit*
gwy_cdline_get_param_units(GwyCDLine *cdline,
                           gint i,
                           GwyUnit *unit_x,
                           GwyUnit *unit_y)
{
    g_return_val_if_fail(GWY_IS_CDLINE(cdline), NULL);

    const GwyCDLineBuiltin *builtin = cdline->priv->builtin;
    g_return_val_if_fail(builtin, NULL);
    g_return_val_if_fail(i >= 0 && i < builtin->nparams, NULL);

    const GwyCDLineParam *param = builtin->param + i;

    return gwy_unit_power_multiply(gwy_unit_new(NULL),
                                   unit_x, param->power_x,
                                   unit_y, param->power_y);
}

/**
 * gwy_cdline_get_param_power_x:
 * @cdline: A critical dimension evaluator.
 * @i: Parameter index.
 *
 * Gets the power of abscissa units in a critical dimension fitting parameter.
 *
 * Usually it is easier to let gwy_cdline_get_param_units() derive the parameter units.
 *
 * Returns: The power of abscissa in the parameter.
 **/
gint
gwy_cdline_get_param_power_x(GwyCDLine *cdline,
                             gint i)
{
    g_return_val_if_fail(GWY_IS_CDLINE(cdline), 0);

    const GwyCDLineBuiltin *builtin = cdline->priv->builtin;
    g_return_val_if_fail(builtin, 0);
    g_return_val_if_fail(i >= 0 && i < builtin->nparams, 0);

    return builtin->param[i].power_x;
}

/**
 * gwy_cdline_get_param_power_y:
 * @cdline: A critical dimension evaluator.
 * @i: Parameter index.
 *
 * Gets the power of ordinate units in a critical dimension parameter.
 *
 * Usually it is easier to let gwy_cdline_get_param_units() derive the parameter units.
 *
 * Returns: The power of ordinate in the parameter.
 **/
gint
gwy_cdline_get_param_power_y(GwyCDLine *cdline,
                             gint i)
{
    g_return_val_if_fail(GWY_IS_CDLINE(cdline), 0);

    const GwyCDLineBuiltin *builtin = cdline->priv->builtin;
    g_return_val_if_fail(builtin, 0);
    g_return_val_if_fail(i >= 0 && i < builtin->nparams, 0);

    return builtin->param[i].power_y;
}

/**
 * gwy_cdline_get_n_params:
 * @cdline: A critical dimension evaluator.
 *
 * Return the number of parameters of @cdline.
 *
 * Returns: The number of function parameters.
 **/
gint
gwy_cdline_get_n_params(GwyCDLine* cdline)
{
    g_return_val_if_fail(GWY_IS_CDLINE(cdline), 0);

    const GwyCDLineBuiltin *builtin = cdline->priv->builtin;
    g_return_val_if_fail(builtin, 0);
    return builtin->nparams;
}

/**
 * gwy_cdline_get_value:
 * @cdline: A critical dimension evaluator.
 * @x: The point to compute value at.
 * @params: Evaluator parameter values.
 * @fres: Set to %TRUE if succeeds, %FALSE on failure.
 *
 * Calculates critical dimension function value in a single point with given parameters.
 *
 * Returns: The function value.
 **/
gdouble
gwy_cdline_get_value(GwyCDLine *cdline,
                     gdouble x,
                     const gdouble *params,
                     gboolean *fres)
{
    g_return_val_if_fail(GWY_IS_CDLINE(cdline), 0.0);

    const GwyCDLineBuiltin *builtin = cdline->priv->builtin;
    g_return_val_if_fail(builtin, 0.0);
    return builtin->function(x, params, fres);
}

/**
 * gwy_cdline_fit:
 * @cdline: A critical dimension evaluator.
 * @n_dat: The number of data points (number of items in @x and @y).
 * @x: Abscissa points.
 * @y: Ordinate points.
 * @params: Array to store fitted parameter values to.
 * @err: Array to store parameter errros to, may be %NULL.
 *
 * Performs a critical dimension evaulation (fit).
 **/
void
gwy_cdline_fit(GwyCDLine* cdline,
               gint n_dat,
               const gdouble *x,
               const gdouble *y,
               gdouble *params,
               gdouble *err)
{
    g_return_if_fail(GWY_IS_CDLINE(cdline));

    const GwyCDLineBuiltin *builtin = cdline->priv->builtin;
    g_return_if_fail(builtin);
    gboolean fres = TRUE;
    builtin->function_fit(x, y, n_dat, params, err, &fres);
}

static void
gwy_cdline_class_init(GwyCDLineClass *klass)
{
    GwyResourceClass *res_class = GWY_RESOURCE_CLASS(klass);

    parent_class = gwy_cdline_parent_class;

    res_class->item_type = *gwy_resource_class_get_item_type(parent_class);
    res_class->item_type.type = G_TYPE_FROM_CLASS(klass);
    res_class->name = "cdlines";
    res_class->inventory = gwy_inventory_new(&res_class->item_type);
    gwy_inventory_forget_order(res_class->inventory);
    res_class->setup_builtins = resource_setup_builtins;
}

static void
gwy_cdline_init(GwyCDLine *cdline)
{
    cdline->priv = gwy_cdline_get_instance_private(cdline);
}

static GwyCDLine*
create_cdline_builtin(const GwyCDLineBuiltin *data)
{
    GwyCDLine *cdline = g_object_new(GWY_TYPE_CDLINE,
                                     "name", data->function_name,
                                     "const", TRUE,
                                     NULL);
    cdline->priv->builtin = data;

    return cdline;
}

static void
resource_setup_builtins(GwyInventory *res_inventory)
{
    for (guint i = 0; i < G_N_ELEMENTS(cdlines); i++) {
        GwyCDLine *cdline = create_cdline_builtin(cdlines + i);
        gwy_inventory_insert_item(res_inventory, cdline);
        g_object_unref(cdline);
    }
    gwy_inventory_restore_order(res_inventory);
}

/**
 * gwy_cdlines:
 *
 * Gets inventory with all the critical dimension evaluators.
 *
 * Returns: Critical dimension evaluator inventory.
 **/
GwyInventory*
gwy_cdlines(void)
{
    return GWY_RESOURCE_CLASS(g_type_class_peek(GWY_TYPE_CDLINE))->inventory;
}

/**
 * SECTION:cdline
 * @title: cdline
 * @short_description: Critical dimension measurement
 **/

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