/*
 * 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 <string.h>
#include <math.h>

#include <cpl_plot.h>

#include "kmo_dfs.h"
#include "kmo_utils.h"
#include "kmo_cpl_extensions.h"
#include "kmo_constants.h"
#include "kmo_debug.h"

/**
    @defgroup kmo_utils_test   kmo_utils unit tests

    @{
*/

void kmo_test_verbose_off();
void kmo_test_verbose_on();
void kmo_test_fill_vector(cpl_vector *vec, float seed, float offset);

/**
    @brief   test for kmos_get_license()
*/
void test_kmos_get_license(void)
{
    const char *ch = NULL;

    cpl_test_nonnull(ch = kmos_get_license());
}

/**
    @brief   test kmo_cut_endings()
*/
void test_kmo_cut_endings(void)
{
    cpl_vector  *vec = cpl_vector_new(5);
    int begin = 0, end = 0;

    cpl_vector_set(vec, 0, -1);
    cpl_vector_set(vec, 1, -1);
    cpl_vector_set(vec, 2, 2);
    cpl_vector_set(vec, 3, -1);
    cpl_vector_set(vec, 4, -1);

    cpl_test_eq(CPL_ERROR_NONE, kmo_cut_endings(&vec, &begin, &end, FALSE));
    cpl_test_eq(begin, 2);
    cpl_test_eq(end, 2);

    cpl_test_eq(CPL_ERROR_NONE, kmo_cut_endings(&vec, &begin, &end, TRUE));
    cpl_test_eq(begin, 2);
    cpl_test_eq(end, 2);
    cpl_test_eq(cpl_vector_get_size(vec), 1);

    cpl_vector_delete(vec);

    vec = cpl_vector_new(5);

    cpl_vector_set(vec, 0, -1);
    cpl_vector_set(vec, 1, -1);
    cpl_vector_set(vec, 2, -1);
    cpl_vector_set(vec, 3, -1);
    cpl_vector_set(vec, 4, -1);

    cpl_test_eq(CPL_ERROR_NONE, kmo_cut_endings(&vec, &begin, &end, FALSE));
    cpl_test_eq(begin, 0);
    cpl_test_eq(end, 0);

    cpl_vector_delete(vec);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test kmo_easy_gaussfit()
*/
void test_kmo_easy_gaussfit(void)
{
    cpl_vector  *x = cpl_vector_new(5),
                *y = cpl_vector_new(5);
    double      x0 = 0,
                sigma = 0,
                area = 0,
                offset = 0;
    float       tol      = 0.01;

    cpl_vector_set(x, 0, 0);
    cpl_vector_set(x, 1, 1);
    cpl_vector_set(x, 2, 2);
    cpl_vector_set(x, 3, 3);
    cpl_vector_set(x, 4, 4);

    cpl_vector_set(y, 0, 0.1);
    cpl_vector_set(y, 1, 0.3);
    cpl_vector_set(y, 2, 1);
    cpl_vector_set(y, 3, 0.3);
    cpl_vector_set(y, 4, 0.1);

    cpl_test_eq(CPL_ERROR_NONE, kmo_easy_gaussfit(x, y, &x0, &sigma, &area,
                                                  &offset));
    cpl_test_abs(x0, 2, tol);
    cpl_test_abs(sigma, 0.578262, tol);
    cpl_test_abs(area, 1.30784, tol);
    cpl_test_abs(offset, 0.0977208, tol);

    cpl_vector_delete(x);
    cpl_vector_delete(y);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test kmo_polyfit_1d()
*/
void test_kmo_polyfit_1d(void)
{
    cpl_vector  *x = cpl_vector_new(5),
                *y = cpl_vector_new(5),
                *res = NULL;

    float       tol      = 0.01;

    cpl_vector_set(x, 0, 0);
    cpl_vector_set(x, 1, 1);
    cpl_vector_set(x, 2, 2);
    cpl_vector_set(x, 3, 3);
    cpl_vector_set(x, 4, 4);

    cpl_vector_set(y, 0, 0.1);
    cpl_vector_set(y, 1, 0.3);
    cpl_vector_set(y, 2, 1);
    cpl_vector_set(y, 3, 0.3);
    cpl_vector_set(y, 4, 0.1);

    cpl_test_nonnull(res = kmo_polyfit_1d(x, y, 2));
    cpl_test_eq(cpl_vector_get_size(res), 3);
    cpl_test_abs(cpl_vector_get(res, 0), 0.0457143, tol);
    cpl_test_abs(cpl_vector_get(res, 1), 0.628571, tol);
    cpl_test_abs(cpl_vector_get(res, 2), -0.157143, tol);

    cpl_vector_delete(x);
    cpl_vector_delete(y);
    cpl_vector_delete(res);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test kmo_to_deg()
*/
void test_kmo_to_deg(void)
{
    kmo_test_verbose_off();
    cpl_test_abs(0, kmo_to_deg(10000001), 0.01);
    cpl_test_error(CPL_ERROR_ILLEGAL_INPUT);
    cpl_test_abs(0, kmo_to_deg(-10000001), 0.01);
    cpl_test_error(CPL_ERROR_ILLEGAL_INPUT);
    kmo_test_verbose_on();

    cpl_test_abs(99, kmo_to_deg(990000), 0.01);
    cpl_test_abs(-89.3872, kmo_to_deg(-892313.87), 0.0001);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test kmo_string_to_frame_type()
*/
void test_kmo_string_to_frame_type(void)
{
    cpl_test_eq(ifu_frame, kmo_string_to_frame_type("F2I"));
    cpl_test_eq(ifu_frame, kmo_string_to_frame_type("F3I"));
    cpl_test_eq(ifu_frame, kmo_string_to_frame_type("F1I"));
    cpl_test_eq(detector_frame, kmo_string_to_frame_type("RAW"));
    cpl_test_eq(detector_frame, kmo_string_to_frame_type("F2D"));
    cpl_test_eq(detector_frame, kmo_string_to_frame_type("B2D"));
    cpl_test_eq(list_frame, kmo_string_to_frame_type("F1L"));
    cpl_test_eq(list_frame, kmo_string_to_frame_type("F2L"));
    cpl_test_eq(spectrum_frame, kmo_string_to_frame_type("F1S"));
    cpl_test_error(CPL_ERROR_NONE);

    kmo_test_verbose_off();

    cpl_test_eq(illegal_frame, kmo_string_to_frame_type("X"));
    cpl_test_eq(illegal_frame, kmo_string_to_frame_type(""));
    kmo_string_to_frame_type(NULL);
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    kmo_test_verbose_on();
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test kmo_identify_fits_header()
*/
void test_kmo_identify_fits_header(void)
{
    main_fits_desc       main_desc;

    char *my_path = cpl_sprintf("%s/ref_data/KMOS_dummy.fits", getenv("srcdir"));
    main_desc = kmo_identify_fits_header(my_path);
    cpl_free(my_path);
    cpl_test_eq(3, main_desc.nr_ext);
    cpl_test_eq(raw_fits, main_desc.fits_type);
    cpl_test_eq(detector_frame, main_desc.frame_type);
    cpl_test_eq(2, main_desc.naxis);
    cpl_test_eq(1, main_desc.naxis1);
    cpl_test_eq(1, main_desc.naxis2);
    cpl_test_eq(0, main_desc.naxis3);
    cpl_test_eq(FALSE, main_desc.ex_noise);
    cpl_test_eq(1, main_desc.sub_desc[0].device_nr);
    cpl_test_eq(1, main_desc.sub_desc[0].ext_nr);
    cpl_test_eq(FALSE, main_desc.sub_desc[0].is_noise);
    cpl_test_eq(TRUE, main_desc.sub_desc[0].valid_data);
    cpl_test_eq(2, main_desc.sub_desc[1].device_nr);
    cpl_test_eq(2, main_desc.sub_desc[1].ext_nr);
    cpl_test_eq(FALSE, main_desc.sub_desc[1].is_noise);
    cpl_test_eq(TRUE, main_desc.sub_desc[1].valid_data);
    cpl_test_eq(3, main_desc.sub_desc[2].device_nr);
    cpl_test_eq(3, main_desc.sub_desc[2].ext_nr);
    cpl_test_eq(FALSE, main_desc.sub_desc[2].is_noise);
    cpl_test_eq(TRUE, main_desc.sub_desc[2].valid_data);

    kmo_free_fits_desc(&main_desc);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test kmo_identify_fits_sub_header()
*/
void test_kmo_identify_fits_sub_header(void)
{
    sub_fits_desc       sub_desc;

    sub_desc = kmo_identify_fits_sub_header(2, TRUE, TRUE, FALSE, 1);
    cpl_test_eq(2, sub_desc.ext_nr);
    cpl_test_eq(1, sub_desc.valid_data);
    cpl_test_eq(1, sub_desc.is_noise);
    cpl_test_eq(0, sub_desc.is_badpix);
    cpl_test_eq(1, sub_desc.device_nr);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test for kmo_check_indices()
*/
void test_kmo_check_indices(void)
{
    kmo_test_verbose_off();
    kmo_check_indices(NULL, -1, -1);
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    int aa[4] = {1, 1, 2, 2};
    int bb[4] = {1, 2, 3, 4};
    kmo_check_indices(aa, -1, -1);
    cpl_test_error(CPL_ERROR_ILLEGAL_INPUT);

    kmo_check_indices(aa, 4, -1);
    cpl_test_error(CPL_ERROR_ILLEGAL_INPUT);

    kmo_check_indices(aa, 4, 0);
    cpl_test_error(CPL_ERROR_ILLEGAL_INPUT);

    kmo_check_indices(bb, 4, 1);
    cpl_test_error(CPL_ERROR_ILLEGAL_INPUT);
    kmo_test_verbose_on();

    kmo_check_indices(aa, 4, 1);
    cpl_test_error(CPL_ERROR_NONE);

    kmo_check_indices(bb, 4, 0);
    cpl_test_error(CPL_ERROR_NONE);

}

/**
    @brief   test kmo_free_fits_desc()
*/
void test_kmo_free_fits_desc(void)
{
    main_fits_desc       main_desc;

    char *my_path = cpl_sprintf("%s/ref_data/KMOS_dummy.fits", getenv("srcdir"));
    main_desc = kmo_identify_fits_header(my_path);
    cpl_free(my_path);


    kmo_free_fits_desc(&main_desc);
    cpl_test_error(CPL_ERROR_NONE);

    kmo_test_verbose_off();
    kmo_free_fits_desc(NULL);
    cpl_test_error(CPL_ERROR_NULL_INPUT);
    kmo_test_verbose_on();
}

/**
    @brief   test kmo_init_fits_desc()
*/
void test_kmo_init_fits_desc(void)
{
    main_fits_desc       main_desc;

    kmo_test_verbose_off();

    kmo_init_fits_desc(NULL);
    cpl_test_error(CPL_ERROR_NULL_INPUT);
    kmo_test_verbose_on();

    kmo_init_fits_desc(&main_desc);
    cpl_test_eq(-1, main_desc.nr_ext);
    cpl_test_eq(illegal_fits, main_desc.fits_type);
    cpl_test_eq(illegal_frame, main_desc.frame_type);
    cpl_test_eq(-1, main_desc.naxis);
    cpl_test_eq(-1, main_desc.naxis1);
    cpl_test_eq(-1, main_desc.naxis2);
    cpl_test_eq(-1, main_desc.naxis3);
    cpl_test_eq(-1, main_desc.ex_noise);
    cpl_test_null(main_desc.sub_desc);
    cpl_test_error(CPL_ERROR_NONE);
}


/**
    @brief   test for kmo_init_fits_sub_desc()
*/
void test_kmo_init_fits_sub_desc(void)
{
    kmo_test_verbose_off();
    kmo_init_fits_sub_desc(NULL);
    cpl_test_error(CPL_ERROR_NULL_INPUT);
    kmo_test_verbose_on();

    sub_fits_desc desc;
    kmo_init_fits_sub_desc(&desc);
    cpl_test_error(CPL_ERROR_NONE);
    cpl_test_eq(-1, desc.ext_nr);
    cpl_test_eq(-1, desc.valid_data);
    cpl_test_eq(-1, desc.is_noise);
    cpl_test_eq(-1, desc.is_badpix);
    cpl_test_eq(-1, desc.device_nr);
}

/**
    @brief   test kmo_idl_where()
*/
void test_kmo_idl_where(void)
{
    cpl_vector  *vec    = cpl_vector_new(5),
                *res    = NULL;
    float               tol     = 0.01;

    kmo_test_fill_vector(vec, 0.0, 1.0);

    res = kmo_idl_where(vec, 2, ge);
    cpl_test_abs(cpl_vector_get_mean(res), 3, tol);

    cpl_vector_delete(res);

    cpl_test_null(res = kmo_idl_where(vec, 7, ge));

    cpl_vector_delete(vec);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   test kmo_idl_values_at_indices()
*/
void test_kmo_idl_values_at_indices(void)
{
    cpl_vector  *vec    = cpl_vector_new(5),
                *res    = NULL,
                *ind    = NULL;
    float               tol     = 0.01;

    kmo_test_fill_vector(vec, 0.0, 1.0);

    ind = kmo_idl_where(vec, 2, ge);

    res = kmo_idl_values_at_indices(vec, ind);

    cpl_test_abs(cpl_vector_get_mean(res), 3, tol);

    cpl_vector_delete(res);
    cpl_vector_delete(ind);
    cpl_test_error(CPL_ERROR_NONE);

    kmo_test_verbose_off();
    res = kmo_idl_values_at_indices(vec, NULL);
    cpl_test_error(CPL_ERROR_NULL_INPUT);
    kmo_test_verbose_on();

    cpl_vector_delete(vec);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   Test for kmo_get_unused_ifus()
*/
void test_kmo_get_unused_ifus(void)
{
    kmo_test_verbose_off();
    cpl_test_null(kmo_get_unused_ifus(NULL, 1, 1));
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    // create primary headers and save
    cpl_propertylist *pl = cpl_propertylist_new();
    cpl_propertylist_append_int(pl, "ESO OCS ARM5 NOTUSED", 0);
    cpl_propertylist_append_string(pl, "ESO OCS ARM6 NOTUSED", "this no use");
    cpl_propertylist_append_double(pl, "ESO PRO ARM11 NOTUSED", 9.9);
    cpl_propertylist_append_bool(pl, "ESO PRO ARM23 NOTUSED", TRUE);
    cpl_image_save(NULL, "h1.fits", CPL_BPP_IEEE_FLOAT, pl, CPL_IO_DEFAULT);
    cpl_propertylist_delete(pl);

    pl = cpl_propertylist_new();
    cpl_propertylist_append_int(pl, "ESO OCS ARM1 NOTUSED", 0);
    cpl_propertylist_append_int(pl, "ESO PRO ARM6 NOTUSED", 0);
    cpl_propertylist_append_int(pl, "ESO OCS ARM23 NOTUSED", 0);
    cpl_image_save(NULL, "h2.fits", CPL_BPP_IEEE_FLOAT, pl, CPL_IO_DEFAULT);
    cpl_propertylist_delete(pl);

    cpl_frameset *fs = cpl_frameset_new();

    cpl_test_null(kmo_get_unused_ifus(fs, 1, 1));
    cpl_test_error(CPL_ERROR_ILLEGAL_INPUT);
    kmo_test_verbose_on();

    cpl_frame *fr = cpl_frame_new();
    cpl_frame_set_filename(fr, "h1.fits");
    cpl_frame_set_tag(fr, FLAT_ON);
    cpl_frameset_insert(fs, fr);
    fr = cpl_frame_new();
    cpl_frame_set_filename(fr, "h2.fits");
    cpl_frame_set_tag(fr, FLAT_ON);
    cpl_frameset_insert(fs, fr);

    // test kmo_get_unused_ifus()
    cpl_array **unused = NULL;
    cpl_test_nonnull(unused = kmo_get_unused_ifus(fs, 1, 1));
    cpl_frameset_delete(fs); fs = NULL;
    int i = 0;
    for (i = 0; i < KMOS_NR_DETECTORS; i++) {
        int *pret = cpl_array_get_data_int(unused[i]);

        if (i == 0) {
            cpl_test_eq(1, pret[0]);
            cpl_test_eq(0, pret[1]);
            cpl_test_eq(0, pret[2]);
            cpl_test_eq(0, pret[3]);
            cpl_test_eq(1, pret[4]);
            cpl_test_eq(1, pret[5]);
            cpl_test_eq(0, pret[6]);
            cpl_test_eq(0, pret[7]);
        }
        if (i == 1) {
            cpl_test_eq(0, pret[0]);
            cpl_test_eq(0, pret[1]);
            cpl_test_eq(2, pret[2]);
            cpl_test_eq(0, pret[3]);
            cpl_test_eq(0, pret[4]);
            cpl_test_eq(0, pret[5]);
            cpl_test_eq(0, pret[6]);
            cpl_test_eq(0, pret[7]);
        }
        if (i == 2) {
            cpl_test_eq(0, pret[0]);
            cpl_test_eq(0, pret[1]);
            cpl_test_eq(0, pret[2]);
            cpl_test_eq(0, pret[3]);
            cpl_test_eq(0, pret[4]);
            cpl_test_eq(0, pret[5]);
            cpl_test_eq(1, pret[6]);
            cpl_test_eq(0, pret[7]);
        }
    }
    cpl_test_error(CPL_ERROR_NONE);

    kmo_free_unused_ifus(unused);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   Test for kmo_set_unused_ifus()
*/
void test_kmo_set_unused_ifus(void)
{
    kmo_test_verbose_off();
    kmo_set_unused_ifus(NULL, NULL, NULL);
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    // create primary headers and save
    cpl_propertylist *pl = cpl_propertylist_new();
    cpl_propertylist_append_int(pl, "ESO OCS ARM5 NOTUSED", 0);
    cpl_propertylist_append_string(pl, "ESO OCS ARM6 NOTUSED", "this no use");
    cpl_propertylist_append_double(pl, "ESO PRO ARM11 NOTUSED", 9.9);
    cpl_propertylist_append_bool(pl, "ESO PRO ARM23 NOTUSED", TRUE);
    cpl_image_save(NULL, "h1.fits", CPL_BPP_IEEE_FLOAT, pl, CPL_IO_DEFAULT);
    cpl_propertylist_delete(pl);

    pl = cpl_propertylist_new();
    cpl_propertylist_append_int(pl, "ESO OCS ARM1 NOTUSED", 0);
    cpl_propertylist_append_int(pl, "ESO PRO ARM6 NOTUSED", 0);
    cpl_propertylist_append_int(pl, "ESO OCS ARM23 NOTUSED", 0);
    cpl_image_save(NULL, "h2.fits", CPL_BPP_IEEE_FLOAT, pl, CPL_IO_DEFAULT);
    cpl_propertylist_delete(pl);

    cpl_frameset *fs = cpl_frameset_new();
    cpl_frame *fr = cpl_frame_new();
    cpl_frame_set_filename(fr, "h1.fits");
    cpl_frame_set_tag(fr, FLAT_ON);
    cpl_frameset_insert(fs, fr);
    fr = cpl_frame_new();
    cpl_frame_set_filename(fr, "h2.fits");
    cpl_frame_set_tag(fr, FLAT_ON);
    cpl_frameset_insert(fs, fr);

    cpl_array **unused = kmo_get_unused_ifus(fs, 1, 1);
    cpl_frameset_delete(fs); fs = NULL;

    kmo_set_unused_ifus(unused, NULL, NULL);
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    pl = cpl_propertylist_new();
    cpl_propertylist_append_int(pl, "GAGA", 99);


    kmo_set_unused_ifus(unused, pl, NULL);
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    kmo_test_verbose_on();

    // test kmo_set_unused_ifus()
    kmo_set_unused_ifus(unused, pl, "aaa");
    cpl_test_error(CPL_ERROR_NONE);
    cpl_test_eq(6, cpl_propertylist_get_size(pl));
    cpl_test_eq(99, cpl_propertylist_get_int(pl, "GAGA"));
    cpl_test_eq(TRUE, cpl_propertylist_has(pl, "ESO PRO ARM1 NOTUSED"));
    cpl_test_eq(TRUE, cpl_propertylist_has(pl, "ESO PRO ARM5 NOTUSED"));
    cpl_test_eq(TRUE, cpl_propertylist_has(pl, "ESO PRO ARM6 NOTUSED"));
    cpl_test_eq(TRUE, cpl_propertylist_has(pl, "ESO PRO ARM23 NOTUSED"));
    cpl_test_eq(TRUE, cpl_propertylist_has(pl, "ESO PRO ARM11 NOTUSED"));
    cpl_propertylist_delete(pl);

    kmo_free_unused_ifus(unused);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   Test for kmo_duplicate_unused_ifus()
*/
void test_kmo_duplicate_unused_ifus(void)
{
    kmo_test_verbose_off();
    cpl_test_null(kmo_duplicate_unused_ifus(NULL));
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    // create primary headers and save
    cpl_propertylist *pl = cpl_propertylist_new();
    cpl_propertylist_append_int(pl, "ESO OCS ARM5 NOTUSED", 0);
    cpl_propertylist_append_string(pl, "ESO OCS ARM6 NOTUSED", "this no use");
    cpl_propertylist_append_double(pl, "ESO PRO ARM11 NOTUSED", 9.9);
    cpl_propertylist_append_bool(pl, "ESO PRO ARM23 NOTUSED", TRUE);
    cpl_image_save(NULL, "h1.fits", CPL_BPP_IEEE_FLOAT, pl, CPL_IO_DEFAULT);
    cpl_propertylist_delete(pl);

    pl = cpl_propertylist_new();
    cpl_propertylist_append_int(pl, "ESO OCS ARM1 NOTUSED", 0);
    cpl_propertylist_append_int(pl, "ESO PRO ARM6 NOTUSED", 0);
    cpl_propertylist_append_int(pl, "ESO OCS ARM23 NOTUSED", 0);
    cpl_image_save(NULL, "h2.fits", CPL_BPP_IEEE_FLOAT, pl, CPL_IO_DEFAULT);
    cpl_propertylist_delete(pl);

    cpl_frameset *fs = cpl_frameset_new();
    cpl_frame *fr = cpl_frame_new();
    cpl_frame_set_filename(fr, "h1.fits");
    cpl_frame_set_tag(fr, FLAT_ON);
    cpl_frameset_insert(fs, fr);
    fr = cpl_frame_new();
    cpl_frame_set_filename(fr, "h2.fits");
    cpl_frame_set_tag(fr, FLAT_ON);
    cpl_frameset_insert(fs, fr);

    cpl_array **unused = kmo_get_unused_ifus(fs, 1, 1);
    cpl_frameset_delete(fs); fs = NULL;
    kmo_test_verbose_on();

    // test kmo_duplicate_unused_ifus()
    cpl_array **unused_dup = NULL;
    cpl_test_nonnull(unused_dup = kmo_duplicate_unused_ifus(unused));
    cpl_test_error(CPL_ERROR_NONE);

    kmo_free_unused_ifus(unused);
    kmo_free_unused_ifus(unused_dup);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   Test for kmo_print_unused_ifus()
*/
void test_kmo_print_unused_ifus(void)
{
    kmo_test_verbose_off();
    kmo_print_unused_ifus(NULL, -1);
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    // create primary headers and save
    cpl_propertylist *pl = cpl_propertylist_new();
    cpl_propertylist_append_int(pl, "ESO OCS ARM5 NOTUSED", 0);
    cpl_propertylist_append_string(pl, "ESO OCS ARM6 NOTUSED", "this no use");
    cpl_propertylist_append_double(pl, "ESO PRO ARM11 NOTUSED", 9.9);
    cpl_propertylist_append_bool(pl, "ESO PRO ARM23 NOTUSED", TRUE);
    cpl_image_save(NULL, "h1.fits", CPL_BPP_IEEE_FLOAT, pl, CPL_IO_DEFAULT);
    cpl_propertylist_delete(pl);

    pl = cpl_propertylist_new();
    cpl_propertylist_append_int(pl, "ESO OCS ARM1 NOTUSED", 0);
    cpl_propertylist_append_int(pl, "ESO PRO ARM6 NOTUSED", 0);
    cpl_propertylist_append_int(pl, "ESO OCS ARM23 NOTUSED", 0);
    cpl_image_save(NULL, "h2.fits", CPL_BPP_IEEE_FLOAT, pl, CPL_IO_DEFAULT);
    cpl_propertylist_delete(pl);

    cpl_frameset *fs = cpl_frameset_new();
    cpl_frame *fr = cpl_frame_new();
    cpl_frame_set_filename(fr, "h1.fits");
    cpl_frame_set_tag(fr, FLAT_ON);
    cpl_frameset_insert(fs, fr);
    fr = cpl_frame_new();
    cpl_frame_set_filename(fr, "h2.fits");
    cpl_frame_set_tag(fr, FLAT_ON);
    cpl_frameset_insert(fs, fr);

    cpl_array **unused = kmo_get_unused_ifus(fs, 1, 1);
    cpl_frameset_delete(fs); fs = NULL;

    kmo_print_unused_ifus(unused, -1);
    cpl_test_error(CPL_ERROR_ILLEGAL_INPUT);
    kmo_test_verbose_on();

    // test kmo_print_unused_ifus()
    kmo_print_unused_ifus(unused, 1);
    cpl_test_error(CPL_ERROR_NONE);
    kmo_print_unused_ifus(unused, 0);
    cpl_test_error(CPL_ERROR_NONE);

    kmo_free_unused_ifus(unused);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   Test for kmo_free_unused_ifus()
*/
void test_kmo_free_unused_ifus(void)
{
    kmo_test_verbose_off();
    kmo_free_unused_ifus(NULL);
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    // create primary headers and save
    cpl_propertylist *pl = cpl_propertylist_new();
    cpl_propertylist_append_int(pl, "ESO OCS ARM5 NOTUSED", 0);
    cpl_propertylist_append_string(pl, "ESO OCS ARM6 NOTUSED", "this no use");
    cpl_propertylist_append_double(pl, "ESO PRO ARM11 NOTUSED", 9.9);
    cpl_propertylist_append_bool(pl, "ESO PRO ARM23 NOTUSED", TRUE);
    cpl_image_save(NULL, "h1.fits", CPL_BPP_IEEE_FLOAT, pl, CPL_IO_DEFAULT);
    cpl_propertylist_delete(pl);

    pl = cpl_propertylist_new();
    cpl_propertylist_append_int(pl, "ESO OCS ARM1 NOTUSED", 0);
    cpl_propertylist_append_int(pl, "ESO PRO ARM6 NOTUSED", 0);
    cpl_propertylist_append_int(pl, "ESO OCS ARM23 NOTUSED", 0);
    cpl_image_save(NULL, "h2.fits", CPL_BPP_IEEE_FLOAT, pl, CPL_IO_DEFAULT);
    cpl_propertylist_delete(pl);

    cpl_frameset *fs = cpl_frameset_new();
    cpl_frame *fr = cpl_frame_new();
    cpl_frame_set_filename(fr, "h1.fits");
    cpl_frame_set_tag(fr, FLAT_ON);
    cpl_frameset_insert(fs, fr);
    fr = cpl_frame_new();
    cpl_frame_set_filename(fr, "h2.fits");
    cpl_frame_set_tag(fr, FLAT_ON);
    cpl_frameset_insert(fs, fr);

    cpl_array **unused = kmo_get_unused_ifus(fs, 1, 1);
    cpl_frameset_delete(fs); fs = NULL;
    kmo_test_verbose_on();

    // test kmo_free_unused_ifus()
    kmo_free_unused_ifus(unused);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   Test for kmo_strsplit()
*/
void test_kmo_strsplit(void)
{
    kmo_test_verbose_off();
    cpl_test_null(kmo_strsplit(NULL, NULL, NULL));
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    char a[256] = "aaa";
    cpl_test_null(kmo_strsplit(a, NULL, NULL));
    cpl_test_error(CPL_ERROR_NULL_INPUT);
    kmo_test_verbose_on();

    char **res = NULL;
    cpl_test_nonnull(res = kmo_strsplit(a, "b", NULL));
    cpl_test_eq_string(res[0], "aaa");
    cpl_test_null(res[1]);
    kmo_strfreev(res);

    cpl_test_nonnull(res = kmo_strsplit(a, "a", NULL));
    cpl_test_eq_string(res[0], "");
    cpl_test_eq_string(res[1], "");
    cpl_test_eq_string(res[2], "");
    cpl_test_null(res[3]);
    kmo_strfreev(res);

    char b[256] = "1a2a3";
    cpl_test_nonnull(res = kmo_strsplit(b, "a", NULL));
    cpl_test_eq_string(res[0], "1");
    cpl_test_eq_string(res[1], "2");
    cpl_test_eq_string(res[2], "3");
    cpl_test_null(res[3]);
    kmo_strfreev(res);

    char c[256] = "1a2a3a";
    cpl_test_nonnull(res = kmo_strsplit(c, "a", NULL));
    cpl_test_eq_string(res[0], "1");
    cpl_test_eq_string(res[1], "2");
    cpl_test_eq_string(res[2], "3");
    cpl_test_null(res[3]);
    kmo_strfreev(res);

    char d[256] = "a1a2a3a";
    cpl_test_nonnull(res = kmo_strsplit(d, "a", NULL));
    cpl_test_eq_string(res[0], "");
    cpl_test_eq_string(res[1], "1");
    cpl_test_eq_string(res[2], "2");
    cpl_test_eq_string(res[3], "3");
    cpl_test_null(res[4]);
    kmo_strfreev(res);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   Test for kmo_strfreev()
*/
void test_kmo_strfreev(void)
{
    kmo_strfreev(NULL);
    cpl_test_error(CPL_ERROR_NONE);

    char **s = cpl_malloc(3 * sizeof(char*));
    s[0] = cpl_sprintf("%s", "aga");
    s[1] = cpl_sprintf("%s", "dad");
    s[2] = NULL;
    kmo_strfreev(s);
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   Test for kmo_strlower()
*/
void test_kmo_strlower(void)
{
    kmo_test_verbose_off();
    cpl_test_null(kmo_strlower(NULL));
    cpl_test_error(CPL_ERROR_NULL_INPUT);
    kmo_test_verbose_on();

    char a[256] = "aaa";
    cpl_test_nonnull(kmo_strlower(a));
    cpl_test_eq_string(a, "aaa");
    cpl_test_error(CPL_ERROR_NONE);

    char b[256] = "AAA";
    cpl_test_nonnull(kmo_strlower(b));
    cpl_test_eq_string(b, "aaa");
    cpl_test_error(CPL_ERROR_NONE);

    char c[256] = "A5a";
    cpl_test_nonnull(kmo_strlower(c));
    cpl_test_eq_string(c, "a5a");
    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief   Test for kmo_strupper()
*/
void test_kmo_strupper(void)
{
    kmo_test_verbose_off();
    cpl_test_null(kmo_strupper(NULL));
    cpl_test_error(CPL_ERROR_NULL_INPUT);
    kmo_test_verbose_on();

    char a[256] = "AAA";
    cpl_test_nonnull(kmo_strupper(a));
    cpl_test_eq_string(a, "AAA");
    cpl_test_error(CPL_ERROR_NONE);

    char b[256] = "aaa";
    cpl_test_nonnull(kmo_strupper(b));
    cpl_test_eq_string(b, "AAA");
    cpl_test_error(CPL_ERROR_NONE);

    char c[256] = "A5a";
    cpl_test_nonnull(kmo_strupper(c));
    cpl_test_eq_string(c, "A5A");
    cpl_test_error(CPL_ERROR_NONE);
}

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

    test_kmos_get_license();
    test_kmo_cut_endings();
    test_kmo_easy_gaussfit();
    test_kmo_polyfit_1d();
    test_kmo_to_deg();
    test_kmo_string_to_frame_type();
    test_kmo_identify_fits_header();
    test_kmo_identify_fits_sub_header();
    test_kmo_check_indices();
    test_kmo_free_fits_desc();
    test_kmo_init_fits_desc();
    test_kmo_init_fits_sub_desc();
    test_kmo_idl_where();
    test_kmo_idl_values_at_indices();
    test_kmo_get_unused_ifus();
    test_kmo_set_unused_ifus();
    test_kmo_duplicate_unused_ifus();
    test_kmo_print_unused_ifus();
    test_kmo_free_unused_ifus();
    test_kmo_strsplit();
    test_kmo_strfreev();
    test_kmo_strlower();
    test_kmo_strupper();

    return cpl_test_end(0);
}

/** @}*/
