/*                                                                              *
 *   This file is part of the ESO VISIR package                                 *
 *   Copyright (C) 2004,2005 European Southern Observatory                      *
 *                                                                              *
 *   This library 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, 51 Franklin St, Fifth Floor, Boston, MA  02111-1307  USA       *
 *                                                                              */
 
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

/*-----------------------------------------------------------------------------
                                Includes
 -----------------------------------------------------------------------------*/

#include <cpl.h>

#include <visir_utils.h>

/*----------------------------------------------------------------------------*/
/**
 * @defgroup visir_utils_test Testing of the VISIR utilities
 */
/*----------------------------------------------------------------------------*/

#define CONCAT(a,b) a ## _ ## b
#define CONCAT2X(a,b) CONCAT(a,b)

#define PIXEL_TYPE double
#define STDEV_TYPE CPL_TYPE_DOUBLE
#define PIXEL_TYPE_CPL CPL_TYPE_DOUBLE
#include "../recipes/visir_util_clip_body.c"
#undef PIXEL_TYPE
#undef STDEV_TYPE
#undef PIXEL_TYPE_CPL

/* column major */
#define IND(x,y,nx) ((x) + (y) * (nx))

static void visir_get_kth_test(void);
static void visir_clip_kappa_sigma_test(void);
static void visir_fftxcorrelate_test(void);

/*----------------------------------------------------------------------------*/
/**
   @brief   Unit tests of utils module
**/
/*----------------------------------------------------------------------------*/

#define LEN(a) sizeof((a))/sizeof((a)[0])

int main(void)
{

    cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_WARNING);

    visir_get_kth_test();
    visir_clip_kappa_sigma_test();
    visir_fftxcorrelate_test();

    return cpl_test_end(0);
}

static void visir_get_kth_test(void)
{
    FILE          * stream;
    double res = -1;

    stream = cpl_msg_get_level() > CPL_MSG_INFO
        ? fopen("/dev/null", "a") : stdout;

    {
        double a[10];

        for (int i = 0; i < LEN(a); i++)
            a[i] = 0;
        for (int i = 0; i < LEN(a); i++) {
            res = cpl_tools_get_kth_double(a, LEN(a), i);
            cpl_test_eq(0, res);
        }

        for (int i = 0; i < LEN(a); i++)
            a[i] = i;
        for (int i = 0; i < LEN(a); i++) {
            res = cpl_tools_get_kth_double(a, LEN(a), i);
            cpl_test_eq(i, res);
        }

        for (int i = 0; i < LEN(a); i++)
            a[i] = -i;
        for (int i = 0; i < LEN(a); i++) {
            res = cpl_tools_get_kth_double(a, LEN(a), i);
            cpl_test_eq((double)i - LEN(a) + 1., res);
        }
    }

    {
        double a[] = {3,1,4,2,0,9,7,8,6,5};
        res = cpl_tools_get_kth_double(a, LEN(a), 4);
        cpl_test_eq(4, res);
        for (int i = 0; i < LEN(a) / 2; i++)
            cpl_test_leq(a[i], 4);
        for (int i = LEN(a)/2; i < LEN(a); i++)
            cpl_test_lt(4, a[i]);
    }

    {
        double a[] = {8,5,9,2,0,4,6,3,7,1};
        res = cpl_tools_get_kth_double(a, LEN(a), 2);
        cpl_test_eq(2, res);
        res = cpl_tools_get_kth_double(a, LEN(a), 7);
        cpl_test_eq(7, res);

        cpl_test_eq(9, a[LEN(a) - 1]);
        cpl_test_eq(8, a[LEN(a) - 2]);
        cpl_test_eq(0, a[0]);
        cpl_test_eq(1, a[1]);
    }

    {
        double a[] = {8,5,9,2,0,4,6,3,7,1};
        res = cpl_tools_get_kth_double(a, LEN(a), 7);
        cpl_test_eq(7, res);
        res = cpl_tools_get_kth_double(a, LEN(a), 2);
        cpl_test_eq(2, res);

        cpl_test_eq(9, a[LEN(a) - 1]);
        cpl_test_eq(8, a[LEN(a) - 2]);
        cpl_test_eq(0, a[0]);
        cpl_test_eq(1, a[1]);
    }

    cpl_test_nonnull( stream );

    if (stream != stdout) cpl_test_zero( fclose(stream) );

}

static void visir_clip_kappa_sigma_test(void)
{
    FILE          * stream;
    int dump;
    double res = -1;

    stream = cpl_msg_get_level() > CPL_MSG_INFO
        ? fopen("/dev/null", "a") : stdout;

    cpl_test_nonnull( stream );

    {
        double * pixels = cpl_calloc(9, sizeof(double));
        int shifts[3 * 2] = {0};
        pixels[1 + 3 * 1] = 1;

        cpl_imagelist * list = cpl_imagelist_new();
        cpl_imagelist * dev = cpl_imagelist_new();

        cpl_image* img = cpl_image_wrap_double(3, 3, pixels);
        cpl_imagelist_set(list, img, 0);
        img = cpl_image_duplicate(img);
        cpl_imagelist_set(list, img, 1);
        img = cpl_image_duplicate(img);
        cpl_imagelist_set(list, img, 2);

        visir_util_clip_kappa_sigma_double(list, dev, 1.0, 3, 3, shifts);

        cpl_image * map = cpl_image_new_from_accepted(list);
        cpl_test_eq(3, cpl_image_get(map, 2, 2, &dump));

        cpl_image_delete(map);
        cpl_imagelist_delete(list);
        cpl_imagelist_delete(dev);
    }


    {
        double * pixels = cpl_calloc(9, sizeof(double));
        /* gaus mean 100 sigma 5, 2 sigma range */
        int values[] = {92, 93, 94, 94, 95, 95, 96, 96, 96, 97,
                        97, 97, 97, 98, 98, 98, 98, 99, 99, 99,
                        99, 100, 100, 100, 100, 100, 101, 101, 101, 101,
                        102, 102, 102, 102, 103, 103, 103, 103, 104, 104,
                        104, 105, 105, 106, 106, 107, 108 };
        int shifts[LEN(values) * 2] = {0};

        cpl_imagelist * list = cpl_imagelist_new();
        cpl_imagelist * dev = cpl_imagelist_new();

        cpl_image* img = cpl_image_wrap_double(3, 3, pixels);
        for (int i = 0; i < LEN(values); i++) {
            cpl_image_set(img, 2, 2, values[i]);
            cpl_imagelist_set(list, img, i);
            img = cpl_image_duplicate(img);
        }
        cpl_image_delete(img);

        visir_util_clip_kappa_sigma_double(list, dev, 1.0, 3, 3, shifts);

        cpl_image * map = cpl_image_new_from_accepted(list);
        cpl_test_eq(LEN(values), cpl_image_get(map, 2, 2, &dump));

        cpl_image_delete(map);
        cpl_imagelist_delete(list);
        cpl_imagelist_delete(dev);
    }

    {
        double * pixels = cpl_calloc(9, sizeof(double));
        /* gaus mean 100 sigma 5, 2 sigma range, 2 outliers */
        int values[] = {1, 150, 94, 94, 95, 95, 96, 96, 96, 97,
                        97, 97, 97, 98, 98, 98, 98, 99, 99, 99,
                        99, 100, 100, 100, 100, 100, 101, 101, 101, 101,
                        102, 102, 102, 102, 103, 103, 103, 103, 104, 104,
                        104, 105, 105, 106, 106, 107, 108 };
        int shifts[LEN(values) * 2] = {0};

        cpl_imagelist * list = cpl_imagelist_new();
        cpl_imagelist * dev = cpl_imagelist_new();

        cpl_image* img = cpl_image_wrap_double(3, 3, pixels);
        for (int i = 0; i < LEN(values); i++) {
            cpl_image_set(img, 2, 2, values[i]);
            cpl_imagelist_set(list, img, i);
            img = cpl_image_duplicate(img);
        }
        cpl_image_delete(img);

        visir_util_clip_kappa_sigma_double(list, dev, 1.0, 3, 3, shifts);

        cpl_image * map = cpl_image_new_from_accepted(list);
        //cpl_image_dump_structure(map, stdout);
        //cpl_image_dump_window(map, 1, 1, 3, 3, stdout);
        //cpl_image_dump_window(img, 1, 1, 3, 3, stdout);
        cpl_test_eq(LEN(values) - 2, cpl_image_get(map, 2, 2, &dump));

        cpl_image_delete(map);
        cpl_imagelist_delete(list);
        cpl_imagelist_delete(dev);
    }

    {
        double * pixels = cpl_calloc(9, sizeof(double));
        cpl_msg_info(cpl_func, "-----------------");
        int values[] = { -3, 10, 10, 10, 10, 10, 10, 10 };
        int shifts[LEN(values) * 2] = {0};

        cpl_imagelist * list = cpl_imagelist_new();
        cpl_imagelist * dev = cpl_imagelist_new();

        cpl_image* origimg = cpl_image_wrap_double(3, 3, pixels);
        for (int i = 0; i < LEN(values); i++) {
            cpl_image * img = cpl_image_duplicate(origimg);
            if (i == 1) {
                cpl_image_set(img, 1, 2, values[i]);
                shifts[i * 2] = -1;
            }
            else if (i == 2) {
                cpl_image_set(img, 2, 3, values[i]);
                shifts[i * 2 + 1] = 1;
            }
            else if (i == 3) {
                cpl_image_set(img, 3, 1, values[i]);
                shifts[i * 2] = 1;
                shifts[i * 2 + 1] = -1;
            }
            else
                cpl_image_set(img, 2, 2, values[i]);
            cpl_imagelist_set(list, img, i);
        }
        cpl_image_delete(origimg);

        visir_util_clip_kappa_sigma_double(list, dev, 1.0, 2, 2, shifts);

        cpl_image * map = cpl_image_new_from_accepted(list);
        //cpl_image_dump_window(map, 1, 1, 3, 3, stdout);
        /* to test boundaries change recipes/visir_util_clip_body.c to mark
         * pixels shifted out of bounds as bad */
        if (0) {
            cpl_test_eq(LEN(values) - 2, cpl_image_get(map, 1, 1, &dump));
            cpl_test_eq(LEN(values) - 1, cpl_image_get(map, 1, 2, &dump));
            cpl_test_eq(LEN(values) - 2, cpl_image_get(map, 1, 3, &dump));
            cpl_test_eq(LEN(values) - 1, cpl_image_get(map, 2, 1, &dump));
            cpl_test_eq(LEN(values) - 1, cpl_image_get(map, 2, 2, &dump)); // one rejected
            cpl_test_eq(LEN(values) - 1, cpl_image_get(map, 2, 3, &dump));
            cpl_test_eq(LEN(values) - 1, cpl_image_get(map, 3, 1, &dump));
            cpl_test_eq(LEN(values) - 1, cpl_image_get(map, 3, 2, &dump));
            cpl_test_eq(LEN(values) - 2, cpl_image_get(map, 3, 3, &dump));
        }
        else
            cpl_test_eq(LEN(values) - 1, cpl_image_get(map, 2, 2, &dump));

        cpl_image_delete(map);
        cpl_imagelist_delete(list);
        cpl_imagelist_delete(dev);
    }

    /* test shifted rejects */
    {
        double * pixels = cpl_calloc(9, sizeof(double));
        /* gaus mean 100 sigma 5, 2 sigma range, 3 outliers */
        int values[] = {1, 150, -94, 94, 95, 95, 96, 96, 96, 97,
                        97, 97, 97, 98, 98, 98, 98, 99, 99, 99,
                        99, 100, 100, 100, 100, 100, 101, 101, 101, 101,
                        102, 102, 102, 102, 103, 103, 103, 103, 104, 104,
                        104, 105, 105, 106, 106, 107, 108 };
        int shifts[LEN(values) * 2] = {0};
        shifts[2] = 1;
        shifts[4] = 1;
        cpl_image * out1, * out2;

        cpl_imagelist * list = cpl_imagelist_new();
        cpl_imagelist * dev = cpl_imagelist_new();

        cpl_image* img = cpl_image_wrap_double(3, 3, pixels);
        for (int i = 0; i < LEN(values); i++) {
            cpl_image_set(img, 2, 2, values[i]);
            cpl_imagelist_set(list, img, i);
            img = cpl_image_duplicate(img);
        }
        cpl_image_delete(img);
        out1 = cpl_imagelist_get(list, 1);
        out2 = cpl_imagelist_get(list, 2);
        cpl_image_set(out1, 2, 2, 0);
        cpl_image_set(out1, 3, 2, -94);
        cpl_image_set(out2, 2, 2, 0);
        cpl_image_set(out2, 3, 2, 150);

        visir_util_clip_kappa_sigma_double(list, dev, 1.0, 3, 3, shifts);

        cpl_image * map = cpl_image_new_from_accepted(list);
        //cpl_image_dump_structure(map, stdout);
        //cpl_image_dump_window(map, 2, 2, 3, 3, stdout);
        //cpl_image_dump_window(img, 1, 1, 3, 3, stdout);
        cpl_test_eq(LEN(values) - 1, cpl_image_get(map, 2, 2, &dump));
        cpl_test_eq(LEN(values) - 2, cpl_image_get(map, 3, 2, &dump));
        dump = 0;
        cpl_image_get(out1, 3, 2, &dump);
        cpl_test_eq(dump, 1);
        dump = 0;
        cpl_image_get(out2, 3, 2, &dump);
        cpl_test_eq(dump, 1);

        cpl_image_delete(map);
        cpl_imagelist_delete(list);
        cpl_imagelist_delete(dev);
    }

    if (stream != stdout) cpl_test_zero( fclose(stream) );

}

static void visir_fftxcorrelate_test(void)
{
    FILE          * stream;
    int dump;
    double res = -1;

    stream = cpl_msg_get_level() > CPL_MSG_INFO
        ? fopen("/dev/null", "a") : stdout;

    cpl_test_nonnull( stream );
    {
        double xshift = 0, yshift = 0;
        double * values = cpl_calloc(16, sizeof(double));
        cpl_error_code err;

        values[IND(2,2,4)] = 1;
        cpl_image * tmp = cpl_image_wrap_double(4, 4, values);
        values = cpl_calloc(16, sizeof(double));
        values[IND(3,3,4)] = 1;
        cpl_image * img2 = cpl_image_wrap_double(4, 4, values);

        err = visir_fftxcorrelate(tmp, img2, CPL_TRUE, &xshift, &yshift);
        cpl_test_eq(CPL_ERROR_NONE, err);

        cpl_test_rel(1, xshift, 0.05);
        cpl_test_rel(1, yshift, 0.05);

        cpl_image_delete(tmp);
        cpl_image_delete(img2);
    }
    /* subpixel shift test */
    {
        cpl_size N = 5;
        double xshift = 0, yshift = 0;
        double * values = cpl_calloc(N*N, sizeof(double));
        cpl_error_code err;

        values[IND(2,1,N)] = 1;
        values[IND(2,2,N)] = 3;
        values[IND(2,3,N)] = 2;
        cpl_image * tmp = cpl_image_wrap_double(N, N, values);
        values = cpl_calloc(N*N, sizeof(double));
        values[IND(2,1,N)] = 0.5;
        values[IND(2,2,N)] = 2;
        values[IND(2,3,N)] = 2.5;
        values[IND(2,4,N)] = 1;
        cpl_image * img2 = cpl_image_wrap_double(N, N, values);

        err = visir_fftxcorrelate(tmp, img2, CPL_TRUE, &xshift, &yshift);
        cpl_test_eq(CPL_ERROR_NONE, err);

        cpl_test_rel(1, xshift+1, 0.05);
        /* subpixel estimate has low precision */
        cpl_test_rel(0.5, yshift, 0.15);

        cpl_image_delete(tmp);
        cpl_image_delete(img2);
    }
    /* subpixel shift test 2*/
    {
        cpl_size N = 5;
        double xshift = 0, yshift = 0;
        double * values = cpl_calloc(N*N, sizeof(double));
        cpl_error_code err;

        values[IND(2,1,N)] = 1;
        values[IND(2,2,N)] = 3;
        values[IND(2,3,N)] = 2;
        cpl_image * tmp = cpl_image_wrap_double(N, N, values);
        values = cpl_calloc(N*N, sizeof(double));
        values[IND(3,0,N)] = 2;
        values[IND(3,1,N)] = 2.5;
        values[IND(3,2,N)] = 1;
        cpl_image * img2 = cpl_image_wrap_double(N, N, values);

        err = visir_fftxcorrelate(tmp, img2, CPL_TRUE, &xshift, &yshift);
        cpl_test_eq(CPL_ERROR_NONE, err);

        cpl_test_rel(1, xshift, 0.05);
        /* subpixel estimate has low precision */
        cpl_test_rel(-1.5, yshift, 0.1);

        cpl_image_delete(tmp);
        cpl_image_delete(img2);
    }
    /* test bad pixel */
    {
        double xshift = 0, yshift = 0;
        double * values = cpl_calloc(16, sizeof(double));
        cpl_error_code err;

        values[IND(2,2,4)] = 1;
        cpl_image * tmp = cpl_image_wrap_double(4, 4, values);
        values = cpl_calloc(16, sizeof(double));
        values[IND(3,1,4)] = 2;
        values[IND(1,1,4)] = 200;
        cpl_image * img2 = cpl_image_wrap_double(4, 4, values);

        cpl_mask * m = cpl_mask_threshold_image_create(img2, 10, FLT_MAX);
        cpl_image_reject_from_mask(img2, m);
        cpl_mask_delete(m);

        err = visir_fftxcorrelate(tmp, img2, CPL_TRUE, &xshift, &yshift);
        cpl_test_eq(CPL_ERROR_NONE, err);

        cpl_test_rel(1, xshift, 0.05);
        cpl_test_rel(-1, yshift, 0.05);

        cpl_image_delete(tmp);
        cpl_image_delete(img2);
    }
    /* test non-square */
    {
        cpl_size Nx = 8, Ny = 4;
        double xshift = 0, yshift = 0;
        double * values = cpl_calloc(Nx*Ny, sizeof(double));
        cpl_error_code err;

        values[IND(2,1,Nx)] = 1;
        values[IND(2,2,Nx)] = 3;
        values[IND(2,3,Nx)] = 2;
        cpl_image * tmp = cpl_image_wrap_double(Nx, Ny, values);
        values = cpl_calloc(Nx*Ny, sizeof(double));
        values[IND(3,0,Nx)] = 2;
        values[IND(3,1,Nx)] = 2.5;
        values[IND(3,2,Nx)] = 1;
        cpl_image * img2 = cpl_image_wrap_double(Nx, Ny, values);

        err = visir_fftxcorrelate(tmp, img2, CPL_TRUE, &xshift, &yshift);
        cpl_test_eq(CPL_ERROR_NONE, err);

        cpl_test_rel(1, xshift, 0.05);
        /* subpixel estimate has low precision */
        cpl_test_rel(-1.5, yshift, 0.1);

        cpl_image_delete(tmp);
        cpl_image_delete(img2);
    }

    if (stream != stdout) cpl_test_zero( fclose(stream) );
}
