/*
 * This file is part of the ESO Common Pipeline Library
 * Copyright (C) 2014-2019 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 <cpl_test.h>
#include <errno.h>
#include "giscience.c"


/**
 * @internal
 * @brief Helper routine to create a dummy empty FITS file.
 * @param filename  The filename to use for the generated file.
 * @return CPL_TRUE if successful and testing can continue, else CPL_FALSE.
 */
static cpl_boolean create_dummy_proplist(const char* filename)
{
    cpl_error_code error = CPL_ERROR_NONE;
    cpl_propertylist* props = cpl_propertylist_new();
    if (props == NULL) goto cleanup;
    error |= cpl_propertylist_save(props, filename, CPL_IO_CREATE);
    if (error) goto cleanup;
    cpl_propertylist_delete(props);
    return CPL_TRUE;
cleanup:
    cpl_propertylist_delete(props);
    return CPL_FALSE;
}

/**
 * @internal
 * @brief Helper routine to create a dummy FITS file with a single image.
 * @param filename  The filename to use for the generated file.
 * @param nx  Width of image in pixels.
 * @param ny  Height of image in pixels.
 * @return CPL_TRUE if successful and testing can continue, else CPL_FALSE.
 */
static cpl_boolean create_dummy_image(const char* filename,
                                      cpl_size nx, cpl_size ny)
{
    cpl_error_code error = CPL_ERROR_NONE;
    cpl_image* image = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
    cpl_propertylist* props = cpl_propertylist_new();
    if (image == NULL || props == NULL) goto cleanup;
    error |= cpl_propertylist_append_string(props, GIALIAS_GRATNAME,
                                            "TestGrating");
    error |= cpl_propertylist_append_string(props, GIALIAS_PROGID, "TestProg");
    error |= cpl_propertylist_append_int(props, GIALIAS_OBSID, 123);
    error |= cpl_propertylist_append_double(props, GIALIAS_EXPTIME, 2.34);
    error |= cpl_propertylist_append_double(props, GIALIAS_MJDOBS, 3.45);
    error |= cpl_propertylist_append_string(props, GIALIAS_PROPIPEID,
                                            "TestGiraffe");
    error |= cpl_propertylist_append_string(props, GIALIAS_PROTECH,
                                            "TestTechnique");
    error |= cpl_propertylist_append_double(props, GIALIAS_CRPIX2, 4.56);
    error |= cpl_propertylist_append_double(props, GIALIAS_CRVAL2, 5.67);
    error |= cpl_propertylist_append_double(props, GIALIAS_CDELT2, 6.78);
    error |= cpl_propertylist_append_string(props, GIALIAS_CUNIT2, "nm");
    error |= cpl_propertylist_append_double(props, GIALIAS_CONAD, 7.89);
    error |= cpl_propertylist_append_double(props, GIALIAS_RON, 8.91);
    error |= cpl_propertylist_append_string(props, GIALIAS_SETUPNAME, "H379.0");
    error |= cpl_propertylist_append_string(props, "FILTER", "ShouldBeRenamed");
    error |= cpl_propertylist_append_string(props, "COMMENT", "unit test");
    error |= cpl_image_save(image, filename, CPL_TYPE_FLOAT, props,
                            CPL_IO_CREATE);
    if (error) goto cleanup;
    cpl_image_delete(image);
    cpl_propertylist_delete(props);
    return CPL_TRUE;
cleanup:
    cpl_image_delete(image);
    cpl_propertylist_delete(props);
    return CPL_FALSE;
}

/**
 * @internal
 * @brief Helper routine to append a dummy FIBER_SETUP table to the FITS file.
 * @param filename  The filename of the file to append the table to.
 * @return CPL_TRUE if successful and testing can continue, else CPL_FALSE.
 */
static cpl_boolean append_dummy_fibertable(const char* filename)
{
    cpl_error_code error = CPL_ERROR_NONE;
    cpl_propertylist* props = cpl_propertylist_new();
    cpl_table* table = cpl_table_new(3);
    if (table == NULL || props == NULL) goto cleanup;
    error |= cpl_table_new_column(table, "INDEX", CPL_TYPE_INT);
    error |= cpl_table_new_column(table, "FPS", CPL_TYPE_INT);
    error |= cpl_table_new_column(table, "RP", CPL_TYPE_INT);
    error |= cpl_table_new_column(table, "OBJECT", CPL_TYPE_STRING);
    error |= cpl_table_new_column(table, "TYPE", CPL_TYPE_STRING);
    error |= cpl_table_new_column(table, "RA", CPL_TYPE_DOUBLE);
    error |= cpl_table_new_column(table, "DEC", CPL_TYPE_DOUBLE);
    error |= cpl_table_new_column(table, "GCORR", CPL_TYPE_DOUBLE);
    error |= cpl_table_new_column(table, "HCORR", CPL_TYPE_DOUBLE);
    error |= cpl_table_new_column(table, "BCORR", CPL_TYPE_DOUBLE);
    error |= cpl_table_set_int(table, "INDEX", 0, 1);
    error |= cpl_table_set_int(table, "FPS", 0, 11);
    error |= cpl_table_set_int(table, "RP", 0, 102);
    error |= cpl_table_set_string(table, "OBJECT", 0, "somestar");
    error |= cpl_table_set_string(table, "TYPE", 0, "M");
    error |= cpl_table_set_double(table, "RA", 0, 2.34);
    error |= cpl_table_set_double(table, "DEC", 0, 5.67);
    error |= cpl_table_set_double(table, "GCORR", 0, -0.1);
    error |= cpl_table_set_double(table, "HCORR", 0, -0.2);
    error |= cpl_table_set_double(table, "BCORR", 0, -0.3);
    error |= cpl_table_set_int(table, "INDEX", 1, 2);
    error |= cpl_table_set_int(table, "FPS", 1, 22);
    error |= cpl_table_set_int(table, "RP", 1, 262);
    error |= cpl_table_set_string(table, "OBJECT", 1, "background");
    error |= cpl_table_set_string(table, "TYPE", 1, "S");
    error |= cpl_table_set_double(table, "RA", 1, 3.94);
    error |= cpl_table_set_double(table, "DEC", 1, 4.83);
    error |= cpl_table_set_double(table, "GCORR", 1, -0.4);
    error |= cpl_table_set_double(table, "HCORR", 1, -0.5);
    error |= cpl_table_set_double(table, "BCORR", 1, -0.6);
    error |= cpl_table_set_int(table, "INDEX", 2, 3);
    error |= cpl_table_set_int(table, "FPS", 2, 33);
    error |= cpl_table_set_int(table, "RP", 2, -1);
    error |= cpl_table_set_string(table, "OBJECT", 2, "CALSIM");
    error |= cpl_table_set_string(table, "TYPE", 2, NULL);
    error |= cpl_table_set_double(table, "RA", 2, 0.0);
    error |= cpl_table_set_double(table, "DEC", 2, 0.0);
    error |= cpl_table_set_double(table, "GCORR", 2, 0.0);
    error |= cpl_table_set_double(table, "HCORR", 2, 0.0);
    error |= cpl_table_set_double(table, "BCORR", 2, 0.0);
    error |= cpl_propertylist_append_string(props, "EXTNAME", "FIBER_SETUP");
    error |= cpl_table_save(table, NULL, props, filename, CPL_IO_EXTEND);
    if (error) goto cleanup;
    cpl_table_delete(table);
    cpl_propertylist_delete(props);
    return CPL_TRUE;
cleanup:
    cpl_table_delete(table);
    cpl_propertylist_delete(props);
    return CPL_FALSE;
}

/**
 * @internal
 * @brief Helper routine to create a dummy input frameset.
 * @return Returns a frameset for testing on NULL on error. The returned object
 *      must be deleted with @c cpl_frameset_delete.
 */
static cpl_frameset* create_dummy_input_frameset(void)
{
    cpl_error_code error = CPL_ERROR_NONE;
    cpl_boolean success;
    cpl_frameset* frames = cpl_frameset_new();
    cpl_frame* frame;
    if (frames == NULL) goto cleanup;
    frame = cpl_frame_new();
    error |= cpl_frame_set_filename(frame, "dummy_raw_file1.fits");
    error |= cpl_frame_set_tag(frame, "SCIENCE");
    error |= cpl_frame_set_type(frame, CPL_FRAME_TYPE_IMAGE);
    error |= cpl_frame_set_group(frame, CPL_FRAME_GROUP_RAW);
    error |= cpl_frame_set_level(frame, CPL_FRAME_LEVEL_FINAL);
    error |= cpl_frameset_insert(frames, frame);
    success = create_dummy_image(cpl_frame_get_filename(frame), 10, 10);
    if (! success) goto cleanup;
    frame = cpl_frame_new();
    error |= cpl_frame_set_filename(frame, "dummy_calib_file1.fits");
    error |= cpl_frame_set_tag(frame, "CALIB1");
    error |= cpl_frame_set_type(frame, CPL_FRAME_TYPE_IMAGE);
    error |= cpl_frame_set_group(frame, CPL_FRAME_GROUP_CALIB);
    error |= cpl_frame_set_level(frame, CPL_FRAME_LEVEL_FINAL);
    error |= cpl_frameset_insert(frames, frame);
    success = create_dummy_image(cpl_frame_get_filename(frame), 10, 10);
    if (! success) goto cleanup;
    frame = cpl_frame_new();
    error |= cpl_frame_set_filename(frame, "dummy_calib_file2.fits");
    error |= cpl_frame_set_tag(frame, "CALIB2");
    error |= cpl_frame_set_type(frame, CPL_FRAME_TYPE_IMAGE);
    error |= cpl_frame_set_group(frame, CPL_FRAME_GROUP_CALIB);
    error |= cpl_frame_set_level(frame, CPL_FRAME_LEVEL_FINAL);
    error |= cpl_frameset_insert(frames, frame);
    success = create_dummy_image(cpl_frame_get_filename(frame), 10, 10);
    if (error || ! success) goto cleanup;
    return frames;
cleanup:
    cpl_frameset_delete(frames);
    return NULL;
}

/**
 * @internal
 * @brief Makes a call to the tested function but suppresses CPL output.
 */
static cxint run_make_sdp_spectra(const cxchar* flux_filename,
                                  const cxchar* err_filename,
                                  cxint nassoc_keys,
                                  cpl_frameset* allframes,
                                  const cpl_parameterlist* parlist,
                                  const cxchar* recipe_id)
{
    cxint result;
    cpl_msg_severity level = cpl_msg_get_level();
    cpl_msg_set_level(CPL_MSG_OFF);
    result = _giraffe_make_sdp_spectra(flux_filename, err_filename, nassoc_keys,
                                       allframes, parlist, recipe_id);
    cpl_msg_set_level(level);
    return result;
}


int main(void)
{
    const char* recipe_id = "test_recipe";
    const char* flux_filename = "dummy_flux_spectrum_inputfile.fits";
    const char* err_filename = "dummy_err_spectrum_inputfile.fits";
    cpl_parameterlist* parlist = NULL;
    cpl_frameset* inputframes = NULL;
    cpl_frameset* allframes = NULL;
    cpl_propertylist* props = NULL;
    cpl_table* table = NULL;

    /* Force deletion of input test files in case stale files exist. */
    (void) remove(flux_filename);
    (void) remove(err_filename);
    errno = 0;  /* Make sure to reset in case of errors. */

    cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_WARNING);
    giraffe_error_init();

    parlist = cpl_parameterlist_new();
    cpl_test_assert(parlist != NULL);
    inputframes = create_dummy_input_frameset();
    cpl_test_assert(inputframes != NULL);

    /* Test basic error handling of the _giraffe_make_sdp_spectra function. */
    cpl_test(run_make_sdp_spectra(NULL, NULL, 0, NULL, NULL, NULL) != 0);
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    allframes = cpl_frameset_duplicate(inputframes);
    cpl_test_assert(allframes != NULL);
    cpl_test(run_make_sdp_spectra(flux_filename, err_filename, 0, allframes,
                                  parlist, recipe_id) != 0);
    cpl_test_error(CPL_ERROR_FILE_IO);

    cpl_test_assert(create_dummy_proplist(flux_filename));
    cpl_test(run_make_sdp_spectra(flux_filename, err_filename, 0, allframes,
                                  parlist, recipe_id) != 0);
    cpl_test_error(CPL_ERROR_DATA_NOT_FOUND);

    cpl_test_assert(create_dummy_image(flux_filename, 10, 3));
    cpl_test(run_make_sdp_spectra(flux_filename, err_filename, 0, allframes,
                                  parlist, recipe_id) != 0);
    cpl_test_error(CPL_ERROR_FILE_IO);

    cpl_test_assert(create_dummy_proplist(err_filename));
    cpl_test(run_make_sdp_spectra(flux_filename, err_filename, 0, allframes,
                                  parlist, recipe_id) != 0);
    cpl_test_error(CPL_ERROR_DATA_NOT_FOUND);

    cpl_test_assert(create_dummy_image(err_filename, 10, 3));
    cpl_test(run_make_sdp_spectra(flux_filename, err_filename, 0, allframes,
                                  parlist, recipe_id) != 0);
    cpl_test_error(CPL_ERROR_DATA_NOT_FOUND);

    cpl_test_assert(create_dummy_image(err_filename, 11, 4));
    cpl_test_assert(append_dummy_fibertable(err_filename));
    cpl_test(run_make_sdp_spectra(flux_filename, err_filename, 0, allframes,
                                  parlist, recipe_id) != 0);
    cpl_test_error(CPL_ERROR_INCOMPATIBLE_INPUT);
    cpl_test_assert(create_dummy_image(err_filename, 10, 4));
    cpl_test_assert(append_dummy_fibertable(err_filename));
    cpl_test(run_make_sdp_spectra(flux_filename, err_filename, 0, allframes,
                                  parlist, recipe_id) != 0);
    cpl_test_error(CPL_ERROR_INCOMPATIBLE_INPUT);
    cpl_test_assert(create_dummy_image(err_filename, 11, 3));
    cpl_test_assert(append_dummy_fibertable(err_filename));
    cpl_test(run_make_sdp_spectra(flux_filename, err_filename, 0, allframes,
                                  parlist, recipe_id) != 0);
    cpl_test_error(CPL_ERROR_INCOMPATIBLE_INPUT);

    cpl_frameset_delete(allframes);
    allframes = cpl_frameset_new();
    cpl_test_assert(allframes != NULL);
    cpl_test_assert(create_dummy_image(err_filename, 10, 3));
    cpl_test_assert(append_dummy_fibertable(flux_filename));
    cpl_test(run_make_sdp_spectra(flux_filename, err_filename, 0, allframes,
                                  parlist, recipe_id) != 0);
    cpl_test_error(CPL_ERROR_DATA_NOT_FOUND);

    /* Test for successful processing. */
    cpl_frameset_delete(allframes);
    allframes = cpl_frameset_duplicate(inputframes);
    cpl_test_assert(allframes != NULL);
    cpl_test_zero(run_make_sdp_spectra(flux_filename, err_filename, 0,
                                       allframes, parlist, recipe_id));
    cpl_test_error(CPL_ERROR_NONE);
    /* Load the output and check that the basic structure is correct. */
    props = cpl_propertylist_load("science_spectrum_0.fits", 0);
    cpl_test_null(props);
    cpl_test_error(CPL_ERROR_FILE_IO);
    props = cpl_propertylist_load("science_spectrum_2.fits", 0);
    cpl_test_null(props);
    cpl_test_error(CPL_ERROR_FILE_IO);
    props = cpl_propertylist_load("science_spectrum_1.fits", 0);
    cpl_test_nonnull(props);
    cpl_test(cpl_propertylist_has(props, "OFILTER"));
    cpl_test_eq_string(cpl_propertylist_get_string(props, "OFILTER"),
                       "ShouldBeRenamed");
    cpl_test(cpl_propertylist_has(props, "GEOCORR"));
    cpl_test_abs(cpl_propertylist_get_double(props, "GEOCORR"), -0.1,
                 DBL_EPSILON);
    cpl_test(cpl_propertylist_has(props, "HELICORR"));
    cpl_test_abs(cpl_propertylist_get_double(props, "HELICORR"), -0.2,
                 DBL_EPSILON);
    cpl_test(cpl_propertylist_has(props, "BARYCORR"));
    cpl_test_abs(cpl_propertylist_get_double(props, "BARYCORR"), -0.3,
                 DBL_EPSILON);
    cpl_propertylist_delete(props);
    /* Check the Ancillary data only contains the sky background entry. */
    props = cpl_propertylist_load("science_ancillary.fits", 0);
    cpl_test_nonnull(props);
    table = cpl_table_load("science_ancillary.fits", 1, 0);
    cpl_test_nonnull(table);
    cpl_test_eq(cpl_table_get_nrow(table), 1);
    cpl_test_eq(cpl_table_and_selected_string(table, "OBJECT", CPL_EQUAL_TO,
                                              "background"), 1);
    cpl_test_eq(cpl_table_and_selected_string(table, "TYPE", CPL_EQUAL_TO, "S"),
                1);
    /* Note: the INDEX should be remapped to 1 from 2 by
     * _giraffe_make_sdp_spectra if everything is OK. */
    cpl_test_eq(cpl_table_and_selected_int(table, "INDEX", CPL_EQUAL_TO, 1), 1);
    cpl_test_eq(cpl_table_and_selected_int(table, "FPS", CPL_EQUAL_TO, 22), 1);
    cpl_test_eq(cpl_table_and_selected_int(table, "RP", CPL_EQUAL_TO, 262), 1);

    /* Delete input files if no tests failed. */
    if (cpl_test_get_failed() == 0) {
        cpl_size i;
        for (i = 0; i < cpl_frameset_get_size(allframes); ++i) {
            const cpl_frame* frame;
            const char* filename;
            frame = cpl_frameset_get_position_const(allframes, i);
            filename = cpl_frame_get_filename(frame);
            if (filename == NULL) continue;
            (void) remove(filename);
        }
        (void) remove(flux_filename);
        (void) remove(err_filename);
    }
    cpl_parameterlist_delete(parlist);
    cpl_frameset_delete(inputframes);
    cpl_frameset_delete(allframes);
    cpl_propertylist_delete(props);
    cpl_table_delete(table);
    return cpl_test_end(0);
}
