/* 
 * 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 "kmclipm_vector.h"

#include "kmo_priv_arithmetic.h"
#include "kmo_priv_copy.h"
#include "kmo_utils.h"

/**
    @defgroup kmo_priv_arithmetic_test   kmo_priv_arithmetic unit tests

    @{
*/

void kmo_test_verbose_off();
void kmo_test_verbose_on();
void kmo_test_fill_vector(cpl_vector *vec, float seed, float offset);
void kmo_test_fill_image(cpl_image *img, float seed, float offset);
void kmo_test_fill_cube(cpl_imagelist *cube, float seed, float offset);
void kmo_test_alloc_cube(cpl_imagelist *cube, int x, int y, int z);

/**
    @brief   test for kmo_arithmetic_1D_1D()
 */
void test_kmo_arithmetic_1D_1D(void)
{
    // test data only
    int size    = 3;
    float tol   = 0.01;
    kmclipm_vector *v1   = kmclipm_vector_new(size),
                   *n1   = kmclipm_vector_new(size);

    kmclipm_vector_set(v1, 0, 1);
    kmclipm_vector_set(v1, 1, 2);
    kmclipm_vector_set(v1, 2, 3);
    kmclipm_vector_reject(v1, 1);
    kmclipm_vector_set(n1, 0, 0.5);
    kmclipm_vector_set(n1, 1, 0.6);
    kmclipm_vector_set(n1, 2, 0.7);

    kmo_arithmetic_1D_1D(v1, n1, NULL, NULL, "+");
    cpl_test_abs(kmclipm_vector_get_mean(v1), 2.6, tol);

    kmclipm_vector_delete(v1); v1 = NULL;
    kmclipm_vector_delete(n1); n1 = NULL;
    cpl_test_error(CPL_ERROR_NONE);

    // test data and noise
    v1   = kmclipm_vector_new(size);
    n1   = kmclipm_vector_new(size);
    kmclipm_vector *v2   = kmclipm_vector_new(size),
                   *n2   = kmclipm_vector_new(size);

    kmclipm_vector_set(v1, 0, 1);
    kmclipm_vector_set(v1, 1, 2);
    kmclipm_vector_set(v1, 2, 3);
    kmclipm_vector_reject(v1, 1);
    kmclipm_vector_set(n1, 0, 0.5);
    kmclipm_vector_set(n1, 1, 0.6);
    kmclipm_vector_set(n1, 2, 0.7);

    kmclipm_vector_set(v2, 0, 1);
    kmclipm_vector_set(v2, 1, 2);
    kmclipm_vector_set(v2, 2, 3);
    kmclipm_vector_set(n2, 0, 0.2);
    kmclipm_vector_set(n2, 1, 0.3);
    kmclipm_vector_set(n2, 2, 0.4);

    kmo_arithmetic_1D_1D(v1, v2, n1, n2, "*");
    cpl_test_abs(kmclipm_vector_get_mean(v1), 5, tol);
    cpl_test_abs(kmclipm_vector_get_mean(n1), 1.479, tol);

    kmclipm_vector_delete(v1); v1 = NULL;
    kmclipm_vector_delete(n1); n1 = NULL;
    kmclipm_vector_delete(v2); v2 = NULL;
    kmclipm_vector_delete(n2); n2 = NULL;
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_arithmetic_1D_scalar()
 */
void test_kmo_arithmetic_1D_scalar(void)
{
    // test data only
    int size    = 3;
    float tol   = 0.01;
    kmclipm_vector *v1   = kmclipm_vector_new(size);

    kmclipm_vector_set(v1, 0, 1);
    kmclipm_vector_set(v1, 1, 2);
    kmclipm_vector_set(v1, 2, 3);
    kmclipm_vector_reject(v1, 1);

    kmo_arithmetic_1D_scalar(v1, 2, NULL, "^");
    cpl_test_abs(kmclipm_vector_get_mean(v1), 5, tol);

    kmclipm_vector_delete(v1); v1 = NULL;
    cpl_test_error(CPL_ERROR_NONE);

    // test data and noise
    v1   = kmclipm_vector_new(size);
    kmclipm_vector *n1   = kmclipm_vector_new(size);

    kmclipm_vector_set(v1, 0, 1);
    kmclipm_vector_set(v1, 1, 2);
    kmclipm_vector_set(v1, 2, 3);
    kmclipm_vector_reject(v1, 1);
    kmclipm_vector_set(n1, 0, 0.5);
    kmclipm_vector_set(n1, 1, 0.6);
    kmclipm_vector_set(n1, 2, 0.7);

    kmo_arithmetic_1D_scalar(v1, 2, n1, "^");
    cpl_test_abs(kmclipm_vector_get_mean(v1), 5, tol);
    cpl_test_abs(kmclipm_vector_get_mean(n1), 2.6, tol);

    kmclipm_vector_delete(v1); v1 = NULL;
    kmclipm_vector_delete(n1); n1 = NULL;
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_arithmetic_2D_2D()
 */
void test_kmo_arithmetic_2D_2D(void)
{
    // test data only
    int size = 100, rej = 0;
    float tol     = 0.01;
    cpl_image *img1   = cpl_image_new(size, size, CPL_TYPE_FLOAT),
              *img2   = cpl_image_new(size, size, CPL_TYPE_FLOAT);

    kmo_test_fill_image(img1, 0.0, 1.0);
    kmo_test_fill_image(img2, 1.0, 1.0);
    kmo_arithmetic_2D_2D(img1, img2, NULL, NULL, "-");
    cpl_test_abs(cpl_image_get(img1, 10, 10, &rej), -1.0, tol);

    cpl_image_delete(img1);
    cpl_image_delete(img2);
    cpl_test_error(CPL_ERROR_NONE);

    // test data and noise
    img1   = cpl_image_new(size, size, CPL_TYPE_FLOAT);
    img2   = cpl_image_new(size, size, CPL_TYPE_FLOAT);
    cpl_image *noise1 = cpl_image_new(size, size, CPL_TYPE_FLOAT),
              *noise2 = cpl_image_new(size, size, CPL_TYPE_FLOAT);

    kmo_test_fill_image(img1,   0.0, 1.0);
    kmo_test_fill_image(img2,   1.0, 1.0);
    kmo_test_fill_image(noise1, 0.0, 0.1);
    kmo_test_fill_image(noise2, 0.1, 0.1);
    kmo_arithmetic_2D_2D(img1, img2, noise1, noise2, "*");
    cpl_test_abs(cpl_image_get(img1, 2, 3, &rej), 40602, tol);
    cpl_test_abs(cpl_image_get(noise1, 3, 2, &rej), 1485.77, tol);

    cpl_image_delete(img1);
    cpl_image_delete(img2);
    cpl_image_delete(noise1);
    cpl_image_delete(noise2);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_arithmetic_2D_scalar()
 */
void test_kmo_arithmetic_2D_scalar(void)
{
    int size = 100, rej = 0;
    float tol     = 0.01;

    // test data only
    cpl_image *img1   = cpl_image_new(size, size, CPL_TYPE_FLOAT);

    kmo_test_fill_image(img1, 0.0, 1.0);
    kmo_arithmetic_2D_scalar(img1, 1.0, NULL, "+");
    cpl_test_abs(cpl_image_get(img1, 10, 10, &rej), 910.0, tol);

    cpl_image_delete(img1);
    cpl_test_error(CPL_ERROR_NONE);

    // test data and noise
    img1 = cpl_image_new(size, size, CPL_TYPE_FLOAT);
    cpl_image *noise1 = cpl_image_new(size, size, CPL_TYPE_FLOAT);

    kmo_test_fill_image(img1,   0.0, 1.0);
    kmo_test_fill_image(noise1, 0.0, 0.1);
    kmo_arithmetic_2D_scalar(img1, 1.0, noise1, "/");
    cpl_test_abs(cpl_image_get(img1, 2, 3, &rej), 201.0, tol);
    cpl_test_abs(cpl_image_get(noise1, 3, 2, &rej), 10.2, tol);

    cpl_image_delete(img1);
    cpl_image_delete(noise1);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_arithmetic_3D_3D()
 */
void test_kmo_arithmetic_3D_3D(void)
{
    int size2D = 100, size3D = 10, rej;
    float tol = 0.01;

    // test data only
    cpl_imagelist *cube1        = cpl_imagelist_new(),
                  *cube2        = cpl_imagelist_new(),
                  *small_cube   = cpl_imagelist_new();

    kmo_test_alloc_cube(cube1, size2D, size2D, size3D);
    kmo_test_alloc_cube(cube2, size2D, size2D, size3D);
    kmo_test_alloc_cube(small_cube, 6, 5, 3);
    kmo_test_fill_cube(cube1, 0.0, 1.0);
    kmo_test_fill_cube(cube2, 9.9, 1.0);
    kmo_arithmetic_3D_3D(cube1, cube2, NULL, NULL, "-");
    cpl_test_abs(cpl_image_get(cpl_imagelist_get(cube1, 7), 67, 54, &rej),
                 -9.9, tol);

    // too small cube
    kmo_test_verbose_off();
    kmo_arithmetic_3D_3D(cube1, small_cube, NULL, NULL, "-");
    cpl_test_error(CPL_ERROR_ILLEGAL_INPUT);
    kmo_test_verbose_on();

    cpl_imagelist_delete(cube1);
    cpl_imagelist_delete(cube2);
    cpl_imagelist_delete(small_cube);
    cpl_test_error(CPL_ERROR_NONE);

    // test data and noise
    cube1 = cpl_imagelist_new();
    cube2 = cpl_imagelist_new();
    cpl_imagelist *noise_cube1  = cpl_imagelist_new(),
                  *noise_cube2  = cpl_imagelist_new();

    kmo_test_alloc_cube(cube1, size2D, size2D, size3D);
    kmo_test_alloc_cube(cube2, size2D, size2D, size3D);
    kmo_test_alloc_cube(noise_cube1, size2D, size2D, size3D);
    kmo_test_alloc_cube(noise_cube2, size2D, size2D, size3D);

    kmo_test_fill_cube(cube1, 0.0, 1.0);
    kmo_test_fill_cube(cube2, 9.9, 1.0);
    kmo_test_fill_cube(noise_cube1, 0.0, 0.1);
    kmo_test_fill_cube(noise_cube2, 0.1, 0.1);
    kmo_arithmetic_3D_3D(cube1, cube2,noise_cube1, noise_cube2, "/");
    cpl_test_abs(cpl_image_get(cpl_imagelist_get(cube1, 7), 67, 54, &rej),
                 0.998161, tol);
    cpl_test_abs(cpl_image_get(cpl_imagelist_get(noise_cube1, 7), 67, 54, &rej),
                 0.141049, tol);

    cpl_imagelist_delete(cube1);
    cpl_imagelist_delete(cube2);
    cpl_imagelist_delete(noise_cube1);
    cpl_imagelist_delete(noise_cube2);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_arithmetic_3D_2D()
 */
void test_kmo_arithmetic_3D_2D(void)
{
    // test data only
    int size2D = 100, size3D = 10, rej;
    float tol = 0.01;
    cpl_image     *img          = cpl_image_new(size2D, size2D, CPL_TYPE_FLOAT);
    cpl_imagelist *cube1        = cpl_imagelist_new();

    kmo_test_alloc_cube(cube1, size2D, size2D, size3D);
    kmo_test_fill_cube(cube1, 0.0, 1.0);
    kmo_test_fill_image(img, 4.4, 2.2);
    kmo_arithmetic_3D_2D(cube1, img, NULL, NULL, "*");
    cpl_test_abs(cpl_image_get(cpl_imagelist_get(cube1, 2), 4, 99, &rej),
                 2.11504e+08, 400);

    cpl_image_delete(img);
    cpl_imagelist_delete(cube1);
    cpl_test_error(CPL_ERROR_NONE);

    // test data and noise
    img          = cpl_image_new(size2D, size2D, CPL_TYPE_FLOAT);
    cube1        = cpl_imagelist_new();

    kmo_test_alloc_cube(cube1, size2D, size2D, size3D);
    kmo_test_fill_cube(cube1, 0.0, 1.0);
    kmo_test_fill_image(img, 4.4, 2.2);
    cpl_image     *noise_img    = cpl_image_new(size2D, size2D, CPL_TYPE_FLOAT);
    cpl_imagelist *noise_cube1  = cpl_imagelist_new();

    kmo_test_alloc_cube(cube1, size2D, size2D, size3D);
    kmo_test_alloc_cube(noise_cube1, size2D, size2D, size3D);
    kmo_test_fill_cube(cube1, 0.0, 1.0);
    kmo_test_fill_cube(noise_cube1, 0.4, 0.1);
    kmo_test_fill_image(img, 4.4, 2.2);
    kmo_test_fill_image(noise_img, 2.1, 0.2);
    kmo_arithmetic_3D_2D(cube1, img, noise_cube1, noise_img, "+");
    cpl_test_abs(cpl_image_get(cpl_imagelist_get(cube1, 2), 4, 99, &rej),
                 31376, 2*tol);
    cpl_test_abs(cpl_image_get(cpl_imagelist_get(noise_cube1, 2), 4, 99, &rej),
                 2194.16, tol);

    cpl_image_delete(img);
    cpl_image_delete(noise_img);
    cpl_imagelist_delete(cube1);
    cpl_imagelist_delete(noise_cube1);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_arithmetic_3D_1D()
 */
void test_kmo_arithmetic_3D_1D(void)
{
    int size2D = 100, size3D = 10, rej;
    float tol = 0.01;

// test data only
    cpl_vector    *v            = cpl_vector_new(size3D);
    kmclipm_vector *vec         = NULL;
    cpl_imagelist *cube1        = cpl_imagelist_new();


    kmo_test_alloc_cube(cube1, size2D, size2D, size3D);

    // invalid test
    kmo_test_fill_cube(cube1, 0.0, 1.0);
    kmo_test_fill_vector(v, 0.0, 2.2);
    vec = kmclipm_vector_create(v);

    kmo_test_verbose_off();
    kmo_arithmetic_3D_1D(cube1, vec, NULL, NULL, "^");
    cpl_test_error(CPL_ERROR_ILLEGAL_INPUT);
    kmo_test_verbose_on();

    // valid test
    kmo_arithmetic_3D_1D(cube1, vec, NULL, NULL, "+");
    cpl_test_abs(cpl_image_get(cpl_imagelist_get(cube1, 2), 4, 99, &rej),
                 9809.4, tol);

    kmclipm_vector_delete(vec); vec = NULL;
    cpl_imagelist_delete(cube1); cube1 = NULL;
    cpl_test_error(CPL_ERROR_NONE);

// test data and noise
    v = cpl_vector_new(size3D);
    cpl_vector *n = cpl_vector_new(size3D);
    kmclipm_vector *noise_vec    = NULL;
    cube1 = cpl_imagelist_new();
    cpl_imagelist *noise_cube1  = cpl_imagelist_new();

    kmo_test_alloc_cube(cube1, size2D, size2D, size3D);
    kmo_test_alloc_cube(noise_cube1, size2D, size2D, size3D);
    kmo_test_fill_cube(cube1, 0.0, 1.0);
    kmo_test_fill_cube(noise_cube1, 0.4, 0.1);
    kmo_test_fill_vector(v, 0.0, 2.2);
    kmo_test_fill_vector(n, 4.0, 0.2);
    vec = kmclipm_vector_create(v);
    noise_vec = kmclipm_vector_create(n);
    kmo_arithmetic_3D_1D(cube1, vec, noise_cube1, noise_vec, "/");
    cpl_test_abs(cpl_image_get(cpl_imagelist_get(cube1, 9), 100, 100, &rej),
                 505.455, tol);
    cpl_test_abs(cpl_image_get(cpl_imagelist_get(noise_cube1, 9), 100, 100, &rej),
                 156.457, tol);

    kmclipm_vector_delete(vec);
    kmclipm_vector_delete(noise_vec);
    cpl_imagelist_delete(cube1);
    cpl_imagelist_delete(noise_cube1);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_arithmetic_3D_scalar()
 */
void test_kmo_arithmetic_3D_scalar(void)
{
    int size2D = 100, size3D = 10, rej;
    cpl_imagelist *cube1        = cpl_imagelist_new();

    kmo_test_alloc_cube(cube1, size2D, size2D, size3D);
    kmo_test_fill_cube(cube1, 0.0, 1.0);
    kmo_arithmetic_3D_scalar(cube1, 3.3, NULL, "^");
    cpl_test_abs(cpl_image_get(cpl_imagelist_get(cube1, 0), 1, 2, &rej),
                 3.98107e+06, 2);

    cpl_imagelist_delete(cube1);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_arithmetic_3D_scalar_noise()
 */
void test_kmo_arithmetic_3D_scalar_noise(void)
{
        int size2D = 100, size3D = 10, rej;
        float tol = 0.01;

        // test data only
        cpl_imagelist *cube1        = cpl_imagelist_new();

        kmo_test_alloc_cube(cube1, size2D, size2D, size3D);
        kmo_test_fill_cube(cube1, 0.0, 1.0);
        kmo_arithmetic_3D_scalar(cube1, 3.3, NULL, "^");
        cpl_test_abs(cpl_image_get(cpl_imagelist_get(cube1, 0), 1, 2, &rej),
                     3.98107e+06, 2);

        cpl_imagelist_delete(cube1); cube1 = NULL;
        cpl_test_error(CPL_ERROR_NONE);

        // test data and noise
        cube1        = cpl_imagelist_new();
        cpl_imagelist *noise_cube1  = cpl_imagelist_new();

        kmo_test_alloc_cube(cube1, size2D, size2D, size3D);
        kmo_test_alloc_cube(noise_cube1, size2D, size2D, size3D);

        // ----- kmo_arithmetic_3D_scalar_noise -----
        kmo_test_fill_cube(cube1, 0.0, 1.0);
        kmo_test_fill_cube(noise_cube1, 0.4, 0.1);
        kmo_arithmetic_3D_scalar(cube1, 0.1, noise_cube1, "^");
        cpl_test_abs(cpl_image_get(cpl_imagelist_get(cube1, 9), 100, 100, &rej),
                     2.51209, tol);
        cpl_test_abs(cpl_image_get(cpl_imagelist_get(noise_cube1, 9), 100, 100, &rej),
                     0.0251285, tol);
        cpl_test_error(CPL_ERROR_NONE);

        // ----- kmo_arithmetic_3D_scalar_noise: img contains zero and negative
        // ----- exponent
        kmo_test_verbose_off();
        kmo_arithmetic_3D_scalar(cube1, -0.1, noise_cube1, "^");
        cpl_test_error(CPL_ERROR_DIVISION_BY_ZERO);
        kmo_test_verbose_on();

        cpl_imagelist_delete(cube1);
        cpl_imagelist_delete(noise_cube1);
        cpl_test_error(CPL_ERROR_NONE);
}

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

    test_kmo_arithmetic_1D_1D();
    test_kmo_arithmetic_1D_scalar();
    test_kmo_arithmetic_2D_2D();
    test_kmo_arithmetic_2D_scalar();
    test_kmo_arithmetic_3D_3D();
    test_kmo_arithmetic_3D_2D();
    test_kmo_arithmetic_3D_1D();
    test_kmo_arithmetic_3D_scalar();

    return cpl_test_end(0);
}

/** @} */
