/*
 * This file is part of the GIRAFFE Pipeline
 * Copyright (C) 2002-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 <math.h>
#include <float.h>

#include <cxmemory.h>
#include <cxmessages.h>
#include <cxstrutils.h>

#include <cpl_msg.h>
#include <cpl_parameterlist.h>

#include "gifiberutils.h"
#include "giflat.h"
#include "gimessages.h"


/**
 * @defgroup giflat Flat Field Correction
 *
 * TBD
 */

/**@{*/

/*
 * For each extracted spectra look up the corresponding flat field
 * spectrum and devide the extracted flux value by the flat field
 * flux value for each individual pixel along the dispersion axis.
 */

inline static cxint
_giraffe_flat_apply(GiImage *spectra, const GiTable *fibers,
                    const GiImage *flat)
{

    const cxchar *fctid = "giraffe_flat_apply";

    const cxchar *idx = NULL;

    cxint nf;
    cxint nfibers = 0;
    cxint nbins = 0;

    cpl_image *_spectra = giraffe_image_get(spectra);
    const cpl_image *_flat = giraffe_image_get(flat);

    const cpl_table *_fibers = giraffe_table_get(fibers);


    idx = giraffe_fiberlist_query_index(_fibers);

    if (idx == NULL) {
        cpl_error_set(fctid, CPL_ERROR_DATA_NOT_FOUND);
        return -1;
    }


    /*
     * The number of fibers to be process must be less or equal to the
     * number of spectra available in the flat field.
     */

    nfibers = cpl_table_get_nrow(_fibers);

    if (nfibers > cpl_image_get_size_x(_spectra)) {
        cpl_error_set(fctid, CPL_ERROR_INCOMPATIBLE_INPUT);
        return -2;
    }

    nbins = cpl_image_get_size_y(_spectra);

    if (nbins != cpl_image_get_size_y(_flat)) {
        cpl_error_set(fctid, CPL_ERROR_INCOMPATIBLE_INPUT);
        return -3;
    }

    for (nf = 0; nf < nfibers; ++nf) {

        register cxint y;
        register cxint ns = cpl_table_get_int(_fibers, idx, nf, NULL) - 1;

        const cxdouble *f = cpl_image_get_data_const(_flat);

        cxdouble *s = cpl_image_get_data(_spectra);


        for (y = 0; y < nbins ; ++y) {

            cxint ls = y * cpl_image_get_size_x(_spectra) + nf;
            cxint lf = y * cpl_image_get_size_x(_flat) + ns;

            if (fabs(f[lf]) < DBL_EPSILON) {
                s[ls] = 0.;
            }
            else {
                s[ls] /= f[lf];
            }

        }

    }

    return 0;

}


/*
 * The errors of the extracted flat field spectra are taken into
 * account for the computation of the extracted spectra errors.
 */

inline static cxint
_giraffe_flat_apply_errors(GiImage *spectra, GiImage* errors,
                           const GiTable *fibers, const GiImage* fspectra,
                           const GiImage *ferrors)
{

    const cxchar *fctid = "giraffe_flat_apply";

    const cxchar *idx = NULL;

    cxint nf;
    cxint nfibers = 0;
    cxint nbins = 0;

    const cpl_image *_fspectra = giraffe_image_get(fspectra);
    const cpl_image *_ferrors  = giraffe_image_get(ferrors);

    cpl_image *_spectra = giraffe_image_get(spectra);
    cpl_image *_errors = giraffe_image_get(errors);

    const cpl_table *_fibers = giraffe_table_get(fibers);


    idx = giraffe_fiberlist_query_index(_fibers);

    if (idx == NULL) {
        cpl_error_set(fctid, CPL_ERROR_DATA_NOT_FOUND);
        return -1;
    }


    /*
     * The number of fibers to be process must be less or equal to the
     * number of spectra available in the flat field.
     */

    nfibers = cpl_table_get_nrow(_fibers);

    if (nfibers > cpl_image_get_size_x(_spectra)) {
        cpl_error_set(fctid, CPL_ERROR_INCOMPATIBLE_INPUT);
        return -2;
    }

    nbins = cpl_image_get_size_y(_spectra);

    if (nbins != cpl_image_get_size_y(_fspectra)) {
        cpl_error_set(fctid, CPL_ERROR_INCOMPATIBLE_INPUT);
        return -3;
    }

    for (nf = 0; nf < nfibers; ++nf) {

        register cxint y;
        register cxint ns = cpl_table_get_int(_fibers, idx, nf, NULL) - 1;

        const cxdouble *fs = cpl_image_get_data_const(_fspectra);
        const cxdouble *fe = cpl_image_get_data_const(_ferrors);

        cxdouble *s = cpl_image_get_data(_spectra);
        cxdouble *e = cpl_image_get_data(_errors);


        for (y = 0; y < nbins ; ++y) {

            cxint ls = y * cpl_image_get_size_x(_spectra) + nf;
            cxint lf = y * cpl_image_get_size_x(_fspectra) + ns;


            if (fabs(fs[lf]) < DBL_EPSILON) {
                s[ls] = 0.;
                e[ls] = 0.;
            }
            else {
                s[ls] /= fs[lf];
                e[ls] = sqrt(e[ls] * e[ls] +
                             (s[ls] * s[ls]) * (fe[lf] * fe[lf])) / fs[lf];
            }

        }

    }

    return 0;

}


/**
 * @brief
 *   Apply the flat field correction to the given extracted spectra.
 *
 * @param extraction  Extracted spectra to be corrected.
 * @param fibers      Fiber setup information.
 * @param flat        Image of extracted flat field spectra.
 * @param errors      Image of extracted flat field spectra errors.
 * @param config      Flat field correction setup information.
 *
 * @return The function returns 0 on success and a non-zero value otherwise.
 *
 * TBD
 */

cxint
giraffe_flat_apply(GiExtraction *extraction, const GiTable *fibers,
                   const GiImage *flat, const GiImage* errors,
                   GiFlatConfig *config)
{

    cxint status = 0;


    if (extraction == NULL || extraction->spectra == NULL) {
        return -1;
    }

    if (fibers == NULL) {
        return -2;
    }

    if (flat == NULL) {
        return -3;
    }

    if (config == NULL) {
        return -4;
    }


    if (errors == NULL) {

        status = _giraffe_flat_apply(extraction->spectra, fibers, flat);

        if ((status == 0) && (extraction->error != NULL)) {
            status = _giraffe_flat_apply(extraction->error, fibers, flat);
        }

    }
    else {

        status = _giraffe_flat_apply_errors(extraction->spectra,
                                            extraction->error,
                                            fibers, flat, errors);

    }

    if (status != 0) {
        return 1;
    }

    return 0;

}


/**
 * @brief
 *   Creates a setup structure for the flat field correction.
 *
 * @param list  Parameter list from which the setup informations is read.
 *
 * @return A newly allocated and initialized setup structure if no errors
 *   occurred, or @c NULL otherwise.
 *
 * TBD
 */

GiFlatConfig *
giraffe_flat_config_create(cpl_parameterlist *list)
{

    cpl_parameter *p;

    GiFlatConfig *config = NULL;


    if (!list) {
        return NULL;
    }

    config = cx_calloc(1, sizeof *config);


    /*
     * Some defaults
     */

    config->apply = FALSE;
    config->transmission = TRUE;


    p = cpl_parameterlist_find(list, "giraffe.flat.apply");
    config->apply = cpl_parameter_get_bool(p);

    p = cpl_parameterlist_find(list, "giraffe.flat.transmission");
    config->transmission = cpl_parameter_get_bool(p);

    config->load = config->apply || config->transmission;

    return config;

}


/**
 * @brief
 *   Destroys a flat field setup structure.
 *
 * @param config  The setup structure to destroy.
 *
 * @return Nothing.
 *
 * The function deallocates the memory used by the setup structure
 * @em config.
 *
 * TBD
 */

void
giraffe_flat_config_destroy(GiFlatConfig *config)
{

    if (config) {
        cx_free(config);
    }

    return;
}


/**
 * @brief
 *   Adds parameters for the flat field correction.
 *
 * @param list  Parameter list to which parameters are added.
 *
 * @return Nothing.
 *
 * TBD
 */

void
giraffe_flat_config_add(cpl_parameterlist *list)
{

    cpl_parameter *p;


    if (!list) {
        return;
    }

    p = cpl_parameter_new_value("giraffe.flat.apply",
                                CPL_TYPE_BOOL,
                                "Controls the flat field correction.",
                                "giraffe.flat",
                                TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "flat-apply");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.flat.transmission",
                                CPL_TYPE_BOOL,
                                "Controls the fiber to fiber transmission "
                                "correction.",
                                "giraffe.flat",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "transmission-apply");
    cpl_parameterlist_append(list, p);

    return;

}
/**@}*/
