/*
 * This file is part of the KMOS Library
 * Copyright (C) 2007-2008 European Southern Observatory
 *
 * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <math.h>

#include <cpl_plot.h>

#include "kmo_priv_fit_profile.h"
#include "kmo_cpl_extensions.h"
#include "kmo_debug.h"

/**
    @defgroup kmo_priv_fit_profile_test   kmo_priv_fit_profile unit tests

    @{
 */

/**
    @brief   test for kmo_fit_profile_1D()
*/
void test_kmo_fit_profile_1D(void)
{
    float               tol     = 0.01;
    int                 i       = 0,
                        size    = 256;
    cpl_vector          *x      = cpl_vector_new(size),
                        *y      = cpl_vector_new(size),
                        *par    = NULL,
                        *vec    = NULL;
    cpl_propertylist    *pl     = NULL;
    double              *px     = cpl_vector_get_data(x),
                        *py     = cpl_vector_get_data(y),
                        res     = 0.0,
                        xx[1],
                        a[4]    = {2, 3, 129, 21},
                        b[5]    = {2, 3, 129, 21, 2},
                        c[5]    = {2, 1, 129, 10, 0};

    // test gauss 1D
    for (i = 0; i < size; i++) {
        px[i] = i;
        xx[0] = i;
        kmo_priv_gauss1d_fnc(xx, a, &res);
        py[i] = res;
    }

    cpl_test_nonnull(par = kmo_fit_profile_1D(x, y, NULL,
                                                   "gauss",
                                                   &vec, &pl));

    cpl_test_abs(cpl_vector_get_mean(par), 38.75, tol);
    cpl_test_abs(cpl_vector_get_mean(vec), 2.61687, tol);
    cpl_vector *y_dup = cpl_vector_duplicate(y);
    cpl_vector_subtract(y_dup, vec);
    cpl_test_abs(cpl_vector_get_mean(y_dup), 0.0, tol);
    cpl_vector_delete(y_dup);

    cpl_test_eq(10, cpl_propertylist_get_size(pl));
    cpl_vector_delete(par); par = NULL;
    cpl_vector_delete(vec); vec = NULL;
    cpl_propertylist_delete(pl); pl = NULL;

    cpl_test_nonnull(par = kmo_fit_profile_1D(x, y, NULL,
                                               "gauss",
                                               &vec, NULL));

    cpl_test_abs(cpl_vector_get_mean(par), 38.75, tol);
    cpl_test_abs(cpl_vector_get_mean(vec), 2.61687, tol);
    cpl_vector_subtract(y, vec);
    cpl_test_abs(cpl_vector_get_mean(y), 0.0, tol);

    cpl_vector_delete(par); par = NULL;
    cpl_vector_delete(vec); vec = NULL;

    // test moffat 1D
    for (i = 0; i < size; i++) {
        px[i] = i;
        xx[0] = i;
        kmo_priv_moffat1d_fnc(xx, b, &res);
        py[i] = res;
    }

    cpl_test_nonnull(par = kmo_fit_profile_1D(x, y, NULL,
                                                   "moffat",
                                                   &vec, &pl));

    cpl_test_abs(cpl_vector_get_mean(par), 31.4, tol);
    cpl_test_abs(cpl_vector_get_mean(vec), 2.38586, tol);
    cpl_vector_subtract(y, vec);
    cpl_test_abs(cpl_vector_get_mean(y), 0.0, tol);

    cpl_test_eq(12, cpl_propertylist_get_size(pl));
    cpl_vector_delete(par);
    cpl_vector_delete(vec);
    cpl_propertylist_delete(pl);

    // test lorentz 1D
    for (i = 0; i < size; i++) {
        px[i] = i;
        xx[0] = i;
        kmo_priv_lorentz1d_fnc(xx, c, &res);
        py[i] = res;
    }

    cpl_test_nonnull(par = kmo_fit_profile_1D(x, y, NULL,
                                                   "lorentz",
                                                   &vec, &pl));

    cpl_test_abs(cpl_vector_get_mean(par), 24, tol);
    cpl_test_abs(cpl_vector_get_mean(vec), 2.00381, tol);
    cpl_vector_subtract(y, vec);
    cpl_test_abs(cpl_vector_get_mean(y), 0.0, tol);

    cpl_test_eq(10, cpl_propertylist_get_size(pl));
    cpl_vector_delete(par);
    cpl_vector_delete(vec);
    cpl_propertylist_delete(pl);

    cpl_vector_delete(x);
    cpl_vector_delete(y);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_fit_profile_2D()
*/
void test_kmo_fit_profile_2D(void)
{
    float               tol         = 0.01,
                        *pimg       = NULL;
    int                 i           = 0,
                        j           = 0,
                        nx          = 100,
                        ny          = 70;
    cpl_vector          *par        = NULL;
    cpl_image           *img        = cpl_image_new(nx, ny, CPL_TYPE_FLOAT),
                        *img_out    = NULL;
    cpl_propertylist    *pl         = NULL;
    double              res         = 0.0,
                        xx[2],
                        a[7]        = {2, 3, 50, 30, 10, 20, 1.2},
                        b[8]        = {2, 3, 50, 30, 10, 20, 1.2, 2};

    // test gauss 2D
    pimg = cpl_image_get_data_float(img);
    for (j = 0; j < ny; j++) {
        xx[1] = j;
        for (i = 0; i < nx; i++) {
            xx[0] = i;
            kmo_priv_gauss2d_fnc(xx, a, &res);
            pimg[i+j*nx] = res;
        }
    }

    cpl_test_nonnull(par = kmo_fit_profile_2D(img,
                                    NULL,
                                    "gauss",
                                    NULL,
                                    &pl));
    cpl_test_error(CPL_ERROR_NONE);
    cpl_vector_delete(par);
    cpl_propertylist_delete(pl);

    cpl_test_nonnull(par = kmo_fit_profile_2D(img,
                                    NULL,
                                    "gauss",
                                    &img_out,
                                    &pl));
    cpl_test_error(CPL_ERROR_NONE);

    cpl_test_abs(cpl_vector_get_mean(par), 16.6613, tol);
    cpl_test_abs(cpl_image_get_mean(img_out), 2.53135, tol);
    cpl_image_subtract(img, img_out);
    cpl_test_abs(cpl_image_get_mean(img), 1.32152e-08, tol);

    cpl_vector_delete(par);
    cpl_image_delete(img_out);
    cpl_propertylist_delete(pl);

    // test moffat 2D
    pimg = cpl_image_get_data_float(img);
    for (j = 0; j < ny; j++) {
        xx[1] = j;
        for (i = 0; i < nx; i++) {
            xx[0] = i;
            kmo_priv_moffat2d_fnc(xx, b, &res);
            pimg[i+j*nx] = res;
        }
    }

    cpl_test_nonnull(par = kmo_fit_profile_2D(img,
                                    NULL,
                                    "moffat",
                                    &img_out,
                                    &pl));

    cpl_test_abs(cpl_vector_get_mean(par), 14.8287, tol);
    cpl_test_abs(cpl_image_get_mean(img_out), 2.24427, tol);
    cpl_image_subtract(img, img_out);
    cpl_test_abs(cpl_image_get_mean(img), 0.0, tol);

    cpl_vector_delete(par);
    cpl_image_delete(img);
    cpl_image_delete(img_out);
    cpl_propertylist_delete(pl);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_vector_fit_gauss()
*/
void test_kmo_vector_fit_gauss(void)
{
    float               tol     = 0.01;
    cpl_vector          *x      = cpl_vector_new(256),
                        *y      = cpl_vector_new(256),
                        *par    = NULL;
    cpl_propertylist    *pl     = NULL;
    double              max_pos = 0.0,
                        *px     = NULL,
                        *py     = NULL,
                        res     = 0.0,
                        xx[1],
                        a[4]    = {2, 3, 129, 21};
    int                 i       = 0,
                        tmp     = 0;

    px = cpl_vector_get_data(x);
    py = cpl_vector_get_data(y);
    for (i = 0; i < 256; i++) {
        px[i] = i;
        xx[0] = i;
        kmo_priv_gauss1d_fnc(xx, a, &res);
        py[i] = res;
    }

    kmo_vector_get_maxpos_old(y, &tmp);
    max_pos = tmp;

    // without sigma
    cpl_test_nonnull(par = kmo_vector_fit_gauss(x, y, NULL,
                                                max_pos, &pl));
    cpl_test_abs(cpl_vector_get_mean(par), 38.75, tol);
    cpl_test_eq(4, cpl_vector_get_size(par));
    cpl_vector_delete(par); par = NULL;
    cpl_test_nonnull(pl);
    cpl_test_eq(10, cpl_propertylist_get_size(pl));
    cpl_propertylist_delete(pl); pl = NULL;

    // with sigma
    cpl_vector *sig = cpl_vector_duplicate(x);
    cpl_vector_fill(sig, 0.5);
    cpl_test_nonnull(par = kmo_vector_fit_gauss(x, y, sig,
                                                max_pos, &pl));
    cpl_test_abs(cpl_vector_get_mean(par), 38.75, tol);
    cpl_test_eq(4, cpl_vector_get_size(par));
    cpl_vector_delete(par); par = NULL;
    cpl_test_nonnull(pl);
    cpl_test_eq(10, cpl_propertylist_get_size(pl));
    cpl_propertylist_delete(pl); pl = NULL;

    cpl_vector_delete(sig);
    cpl_vector_delete(x);
    cpl_vector_delete(y);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_vector_fit_moffat()
*/
void test_kmo_vector_fit_moffat(void)
{
    float               tol     = 0.01;
    cpl_vector          *x      = cpl_vector_new(256),
                        *y      = cpl_vector_new(256),
                        *par    = NULL;
    cpl_propertylist    *pl     = NULL;
    double              max_pos = 0.0,
                        max_val = 0.0,
                        *px     = NULL,
                        *py     = NULL,
                        res     = 0.0,
                        xx[1],
                        a[5]    = {2, 3, 129, 21, 2};
    int                 i       = 0,
                        tmp     = 0;

    px = cpl_vector_get_data(x);
    py = cpl_vector_get_data(y);
    for (i = 0; i < 256; i++) {
        px[i] = i;
        xx[0] = i;
        kmo_priv_moffat1d_fnc(xx, a, &res);
        py[i] = res;
    }

    kmo_vector_get_maxpos_old(y, &tmp);
    max_pos = tmp;
    max_val = cpl_vector_get_max(y);
    cpl_test_nonnull(par = kmo_vector_fit_moffat(x, y, NULL,
                                                 max_pos, max_val, &pl));
    cpl_test_nonnull(pl);

    cpl_test_abs(cpl_vector_get_mean(par), 31.4, tol);

    cpl_test_eq(5, cpl_vector_get_size(par));
    cpl_test_eq(12, cpl_propertylist_get_size(pl));

    cpl_vector_delete(x);
    cpl_vector_delete(y);
    cpl_vector_delete(par);
    cpl_propertylist_delete(pl);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_vector_fit_lorentz()
*/
void test_kmo_vector_fit_lorentz(void)
{
    float               tol     = 0.01;
    cpl_vector          *x      = cpl_vector_new(256),
                        *y      = cpl_vector_new(256),
                        *par    = NULL;
    cpl_propertylist    *pl     = NULL;
    double              max_pos = 0.0,
                        max_val = 0.0,
                        *px     = NULL,
                        *py     = NULL,
                        res     = 0.0,
                        xx[1],
                        a[5]    = {2, 1, 129, 10, 0},
                        b[5]    = {2, 1, 129, 10, -0.0002};
    int                 i       = 0,
                        tmp     = 0;

    px = cpl_vector_get_data(x);
    py = cpl_vector_get_data(y);
    for (i = 0; i < 256; i++) {
        px[i] = i;
        xx[0] = i;
        kmo_priv_lorentz1d_fnc(xx, a, &res);
        py[i] = res;
    }

    kmo_vector_get_maxpos_old(y, &tmp);
    max_pos = tmp;
    max_val = cpl_vector_get_max(y);

    // fit without linear element
    cpl_test_nonnull(par = kmo_vector_fit_lorentz(x, y, NULL,
                                     max_pos, max_val, 0.0, &pl, FALSE));
    cpl_test_nonnull(pl);
    cpl_test_abs(cpl_vector_get_mean(par), 28.4, tol);
    cpl_test_eq(5, cpl_vector_get_size(par));
    cpl_test_eq(10, cpl_propertylist_get_size(pl));
    cpl_vector_delete(par); par = NULL;
    cpl_propertylist_delete(pl); pl = NULL;

    // fit with linear element
    px = cpl_vector_get_data(x);
    py = cpl_vector_get_data(y);
    for (i = 0; i < 256; i++) {
        px[i] = i;
        xx[0] = i;
        kmo_priv_lorentz1d_fnc(xx, b, &res);
        py[i] = res;
    }

    kmo_vector_get_maxpos_old(y, &tmp);
    max_pos = tmp;
    max_val = cpl_vector_get_max(y);

    cpl_test_nonnull(par = kmo_vector_fit_lorentz(x, y, NULL,
                                     max_pos, max_val, 0.0, &pl, TRUE));
    cpl_test_nonnull(pl);
    cpl_test_abs(cpl_vector_get_mean(par), 28.4, tol);
    cpl_test_eq(5, cpl_vector_get_size(par));
    cpl_test_eq(10, cpl_propertylist_get_size(pl));

    cpl_vector_delete(x); x = NULL;
    cpl_vector_delete(y); y = NULL;
    cpl_vector_delete(par); par = NULL;
    cpl_propertylist_delete(pl); pl = NULL;

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_image_fit_gauss()
*/
void test_kmo_image_fit_gauss(void)
{
    float               tol         = 0.01,
                        *pimg       = NULL;
    int                 i           = 0,
                        j           = 0,
                        nx          = 100,
                        ny          = 70;
    cpl_vector          *par        = NULL;
    cpl_image           *img        = cpl_image_new(nx, ny, CPL_TYPE_FLOAT),
                        *nimg       = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
    cpl_propertylist    *pl         = NULL;
    double              res         = 0.0,
                        xx[2],
                        a[7]        = {2, 3, 50, 30 , 10, 20, 1.2};

    pimg = cpl_image_get_data_float(img);
    for (j = 0; j < ny; j++) {
        xx[1] = j;
        for (i = 0; i < nx; i++) {
            xx[0] = i;
            kmo_priv_gauss2d_fnc(xx, a, &res);
            pimg[i+j*nx] = res;
        }
    }

    // test without noise
    cpl_test_nonnull(par = kmo_image_fit_gauss(img, NULL, &pl));

    cpl_test_nonnull(pl);
    cpl_test_abs(cpl_vector_get_mean(par), 16.6613, tol);
    cpl_vector_delete(par);
    cpl_test_eq(17, cpl_propertylist_get_size(pl));
    cpl_propertylist_delete(pl);

    // test with noise
    cpl_image_fill_noise_uniform(nimg, 0, 1.1);
    cpl_test_nonnull(par = kmo_image_fit_gauss(img, nimg, &pl));

    cpl_test_nonnull(pl);
    if (fabs(cpl_vector_get_mean(par)-16.8899) < tol) {
        // Linux
        cpl_test_abs(cpl_vector_get_mean(par), 16.8899, tol);
    } else {
        // Mac
        cpl_test_abs(cpl_vector_get_mean(par), 16.6629, tol);
    }
    cpl_vector_delete(par);
    cpl_test_eq(17, cpl_propertylist_get_size(pl));
    cpl_propertylist_delete(pl);

    cpl_image_delete(img);
    cpl_image_delete(nimg);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_image_fit_moffat()
*/
void test_kmo_image_fit_moffat(void)
{
    float               tol         = 0.01,
                        *pimg       = NULL;
    int                 i           = 0,
                        j           = 0,
                        nx          = 100,
                        ny          = 70;
    cpl_vector          *par        = NULL;
    cpl_image           *img        = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
    cpl_propertylist    *pl         = NULL;
    double              res         = 0.0,
                        xx[2],
                        a[8]        = {2, 3, 50, 30, 10, 20, 1.2, 2};

    pimg = cpl_image_get_data_float(img);
    for (j = 0; j < ny; j++) {
        xx[1] = j;
        for (i = 0; i < nx; i++) {
            xx[0] = i;
            kmo_priv_moffat2d_fnc(xx, a, &res);
            pimg[i+j*nx] = res;
        }
    }

    cpl_test_nonnull(par = kmo_image_fit_moffat(img, NULL, &pl));
    cpl_test_nonnull(pl);
    cpl_test_abs(cpl_vector_get_mean(par), 14.8287, tol);
    cpl_vector_delete(par);
    cpl_test_eq(19, cpl_propertylist_get_size(pl));
    cpl_propertylist_delete(pl);
    cpl_image_delete(img);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_priv_gauss1d_fnc()
*/
void test_kmo_priv_gauss1d_fnc(void)
{
    double  res     = 0.0,
            xx[1]   = {2},
            a[4]    = {2, 3, 129, 21};

    kmo_priv_gauss1d_fnc(xx, a, &res);
    cpl_test_abs(2.0, res, 0.01);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_priv_moffat1d_fnc()
*/
void test_kmo_priv_moffat1d_fnc(void)
{
    float           tol      = 0.01;
    double          x[1]    = {129},
                    result  = 0.0,
                    a[5]    = {2, 3, 129, 21, 2};

    cpl_test_eq(0, kmo_priv_moffat1d_fnc(x, a, &result));
    cpl_test_abs(result, 5, tol);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_priv_moffat1d_fncd()
*/
void test_kmo_priv_moffat1d_fncd(void)
{
    double  res[5],
            xx[1]   = {2},
            a[5]    = {2, 3, 129, 21, 2};

    kmo_priv_moffat1d_fncd(xx, a, res);
    cpl_test_abs(1.0, res[0], 0.01);
    cpl_test_abs(0.000708324, res[1], 0.0001);
    cpl_test_abs(-6.5147e-05, res[2], 0.00001);
    cpl_test_abs(0.000393984, res[3], 0.001);
    cpl_test_abs(-0.0077058, res[4], 0.001);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_priv_lorentz1d_fnc()
*/
void test_kmo_priv_lorentz1d_fnc(void)
{
    float           tol     = 0.01;
    double          x[1]    = {129},
                    result  = 0.0,
                    a[5]    = {2, 1, 129, 10, 0};

    cpl_test_eq(0, kmo_priv_lorentz1d_fnc(x, a, &result));
    cpl_test_abs(result, 2.06366, tol);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_priv_lorentz1d_fncd()
*/
void test_kmo_priv_lorentz1d_fncd(void)
{
    double  res[5],
            xx[1]   = {2},
            a[5]    = {2, 3, 129, 21, 2};

    kmo_priv_lorentz1d_fncd(xx, a, res);
    cpl_test_abs(1.0, res[0], 0.01);
    cpl_test_abs(0.000205813, res[1], 0.0001);
    cpl_test_abs(-9.65745e-06, res[2], 0.00001);
    cpl_test_abs(0.000393984, res[3], 0.001);
    cpl_test_abs(2.0, res[4], 0.001);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_priv_gauss2d_fnc()
*/
void test_kmo_priv_gauss2d_fnc(void)
{
    float           tol     = 0.01;
    double          x[2]    = {120, 130},
                    result  = 0.0,
                    a[7]    = {2, 3, 120, 130, 10, 20, 1.2};

    cpl_test_eq(0, kmo_priv_gauss2d_fnc(x, a, &result));
    cpl_test_abs(result, 5.0, tol);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_priv_gauss2d_fncd()
*/
void test_kmo_priv_gauss2d_fncd(void)
{
    double  res[7],
            xx[2]   = {2, 3},
            a[7]    = {2, 3, 129, 21, 2, 3, 3};

    kmo_priv_gauss2d_fncd(xx, a, res);
    cpl_test_abs(1.0, res[0], 0.01);
    cpl_test_abs(0.0, res[1], 0.0001);
    cpl_test_abs(-9.65745e-06, res[2], 0.00001);
    cpl_test_abs(0.000393984, res[3], 0.001);
    cpl_test_abs(0.0, res[4], 0.001);
    cpl_test_abs(0.0, res[5], 0.001);
    cpl_test_abs(0.0, res[6], 0.001);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_priv_moffat2d_fnc()
*/
void test_kmo_priv_moffat2d_fnc(void)
{
    float           tol     = 0.01;
    double          x[2]    = {50, 30},
                    result  = 0.0,
                    a[8]    = {2, 3, 50, 30, 20, 10, 1.2, 2};

    cpl_test_eq(0, kmo_priv_moffat2d_fnc(x, a, &result));
    cpl_test_abs(result, 5.0, tol);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_priv_moffat2d_fncd()
*/
void test_kmo_priv_moffat2d_fncd(void)
{
    double  res[8] = {0,0,0,0,0,0,0,0},
            xx[2]   = {2, 3},
            a[7]    = {2, 3, 129, 21, 2, 3, 3};

    kmo_priv_gauss2d_fncd(xx, a, res);
    cpl_test_abs(1.0, res[0], 0.01);
    cpl_test_abs(0.0, res[1], 0.0001);
    cpl_test_abs(-9.65745e-06, res[2], 0.00001);
    cpl_test_abs(0.000393984, res[3], 0.001);
    cpl_test_abs(0.0, res[4], 0.001);
    cpl_test_abs(0.0, res[5], 0.001);
    cpl_test_abs(0.0, res[6], 0.001);
    cpl_test_abs(0.0, res[7], 0.001);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
 * @brief   Test of helper functions for kmo_flat
 */
int main(void)
{
    cpl_test_init("<usd-help@eso.org>", CPL_MSG_WARNING);

    // 1D
    test_kmo_fit_profile_1D();
    test_kmo_fit_profile_2D();
    test_kmo_vector_fit_gauss();
    test_kmo_vector_fit_moffat();
    test_kmo_vector_fit_lorentz();
    test_kmo_image_fit_gauss();
    test_kmo_image_fit_moffat();
    test_kmo_priv_gauss1d_fnc();
    test_kmo_priv_moffat1d_fnc();
    test_kmo_priv_moffat1d_fncd();
    test_kmo_priv_lorentz1d_fnc();
    test_kmo_priv_lorentz1d_fncd();
    test_kmo_priv_gauss2d_fnc();
    test_kmo_priv_gauss2d_fncd();
    test_kmo_priv_moffat2d_fnc();
    test_kmo_priv_moffat2d_fncd();

    return cpl_test_end(0);
}

/**@}*/
