/*
 * 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 <cxmemory.h>
#include <cxmessages.h>
#include <cxstrutils.h>

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

#include "gialias.h"
#include "gierror.h"
#include "gimessages.h"
#include "gifiberutils.h"
#include "gigrating.h"
#include "giwlsolution.h"
#include "giextraction.h"
#include "girebinning.h"
#include "gitransmission.h"


/**
 * @defgroup gitransmission Relative Fiber Transmission Correction
 *
 * TBD
 */

/**@{*/

inline static cxint
_giraffe_transmission_apply(cpl_image *spectra, cpl_table *fibers)
{

    cxint i;
    cxint nx = 0;
    cxint ny = 0;


    cxdouble *pixels = NULL;


    cx_assert(spectra != NULL);
    cx_assert(fibers != NULL);

    nx = cpl_image_get_size_x(spectra);
    ny = cpl_image_get_size_y(spectra);

    pixels = cpl_image_get_data(spectra);

    if (pixels == NULL) {
        return 1;
    }


    /*
     * For each fiber spectrum each wavelength resolution element is
     * corrected for the relative fiber transmission effects.
     */

    for (i = 0; i < cpl_table_get_nrow(fibers); i++) {

        register cxint j;

        register cxdouble tc = cpl_table_get_double(fibers, "TRANSMISSION",
                                                    i, NULL);


        for (j = 0; j < ny; j++) {
            pixels[j * nx + i] /= tc;
        }

    }

    return 0;

}


cxint
giraffe_transmission_compute(GiExtraction *extraction, GiTable *fibers,
                             GiLocalization *localization,
                             GiTable *wcalcoeff, GiTable *grating,
                             GiTable *slitgeometry)
{

    const cxchar *idx = NULL;

    cxint i;
    cxint pos = 0;
    cxint status = 0;

    cxdouble peak = 0.;
    cxdouble *flux = NULL;
    cxdouble *error = NULL;

    cpl_image *tflux = NULL;
    cpl_image *tvariance = NULL;
    cpl_image *variance = NULL;

    cpl_table *_fibers = NULL;

    GiImage *spectra = NULL;

    GiTable *_wcalcoeff = NULL;

    GiRebinning *fspectra = giraffe_rebinning_new();

    GiRebinConfig rebin_config = {
        GIREBIN_METHOD_LINEAR,
        TRUE,
        0.005,
        GIREBIN_SCALE_LINEAR,
        0,
        GIREBIN_RANGE_COMMON
    };


    if (extraction == NULL) {
        return 1;
    }

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

    spectra = extraction->spectra;


    /*
     * If no wavelength solution table was provided create a default one.
     * The wavelength solution residuals are not available in this case
     * and are therefore disabled for the rebinning.
     */

    if (wcalcoeff == NULL) {

        cxint nx = 0;

        cxdouble pixsize = 0.;

        cpl_propertylist *properties = giraffe_image_get_properties(spectra);

        cpl_image *_spectra = giraffe_image_get(spectra);

        GiGrating *setup = NULL;

        GiWlSolution *solution = NULL;


        if (!cpl_propertylist_has(properties, GIALIAS_PIXSIZX)) {

            giraffe_rebinning_delete(fspectra);
            return 1;

        }
        else {

            pixsize = cpl_propertylist_get_double(properties,
                                                  GIALIAS_PIXSIZX);
            pixsize /= 1000.;

        }

        nx = cpl_image_get_size_y(_spectra);

        setup = giraffe_grating_create(spectra, grating);

        if (setup == NULL) {

            giraffe_rebinning_delete(fspectra);
            return 1;

        }

        solution = giraffe_wlsolution_new("xoptmod2", 1, nx, pixsize, setup);

        if (solution == NULL) {

            giraffe_grating_delete(setup);
            giraffe_rebinning_delete(fspectra);

            return 1;

        }

        _wcalcoeff = giraffe_wlsolution_create_table(solution);

        if (_wcalcoeff == NULL) {

            giraffe_wlsolution_delete(solution);
            giraffe_grating_delete(setup);
            giraffe_rebinning_delete(fspectra);

            return 1;
        }

        giraffe_grating_delete(setup);
        setup = NULL;

        giraffe_wlsolution_delete(solution);
        solution = NULL;

        rebin_config.xresiduals = FALSE;

        wcalcoeff = _wcalcoeff;

    }


    status = giraffe_rebin_spectra(fspectra, extraction, fibers,
                                   localization, grating, slitgeometry,
                                   wcalcoeff, &rebin_config);

    if (status) {

        if (_wcalcoeff != NULL) {
            giraffe_table_delete(_wcalcoeff);
            _wcalcoeff = NULL;
        }

        giraffe_rebinning_delete(fspectra);
        fspectra = NULL;

        return 1;

    }

    if (_wcalcoeff != NULL) {
        giraffe_table_delete(_wcalcoeff);
        _wcalcoeff = NULL;
    }


    tflux = cpl_image_collapse_create(giraffe_image_get(fspectra->spectra),
                                      0);

    if (tflux == NULL) {
        giraffe_rebinning_delete(fspectra);
        return 1;
    }


    /*
     * The spectrum errors are the standard deviation. i.e. to get the
     * error of the total flux the variances have to be summed.
     */

    variance = cpl_image_power_create(giraffe_image_get(fspectra->errors),
                                      2.);

    if (variance == NULL) {
        cpl_image_delete(tflux);
        tflux = NULL;

        giraffe_rebinning_delete(fspectra);
        fspectra = NULL;

        return 1;
    }

    tvariance = cpl_image_collapse_create(variance, 0);

    if (tvariance == NULL) {
        cpl_image_delete(variance);
        variance = NULL;

        cpl_image_delete(tflux);
        tflux = NULL;

        giraffe_rebinning_delete(fspectra);
        fspectra = NULL;

        return 1;
    }


    cpl_image_delete(variance);
    variance = NULL;

    _fibers = giraffe_table_get(fibers);
    idx = giraffe_fiberlist_query_index(_fibers);

    flux = cpl_image_get_data(tflux);

    for (i = 0; i < cpl_table_get_nrow(_fibers); i++) {

        register cxint rp = cpl_table_get_int(_fibers, "RP", i, NULL);

        if (rp != -1) {

            register cxint j = cpl_table_get_int(_fibers, idx, i , NULL) - 1;

            if (flux[j] > peak) {
                peak = flux[j];
                pos = i;
            }

        }

    }

    giraffe_error_push();

    cpl_table_new_column(_fibers, "TRANSMISSION", CPL_TYPE_DOUBLE);
    cpl_table_new_column(_fibers, "DTRANSMISSION", CPL_TYPE_DOUBLE);

    if (cpl_error_get_code() != CPL_ERROR_NONE) {

        cpl_image_delete(tflux);
        tflux = NULL;

        cpl_image_delete(tvariance);
        tvariance = NULL;

        giraffe_rebinning_delete(fspectra);
        fspectra = NULL;

        return 1;
    }

    giraffe_error_pop();


    error = cpl_image_get_data(tvariance);

    for (i = 0; i < cpl_table_get_nrow(_fibers); i++) {

        cxint rp = cpl_table_get_int(_fibers, "RP", i, NULL);

        if (rp == -1 || i == pos) {
            cpl_table_set_double(_fibers, "TRANSMISSION", i, 1.);
            cpl_table_set_double(_fibers, "DTRANSMISSION", i, 0.);
        }
        else {

            cxint j = cpl_table_get_int(_fibers, idx, i , NULL) - 1;

            cpl_table_set_double(_fibers, "TRANSMISSION", i, flux[j] / peak);
            cpl_table_set_double(_fibers, "DTRANSMISSION", i,
                                 sqrt(error[j]) / peak);

        }

    }

    cpl_image_delete(tflux);
    cpl_image_delete(tvariance);

    giraffe_rebinning_destroy(fspectra);

    return 0;

}


cxint
giraffe_transmission_setup(GiTable *fibers, GiTable *reference)
{

    cxint i;

    cpl_table *_fibers = NULL;
    cpl_table *_reference = NULL;


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

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

    _fibers = giraffe_table_get(fibers);
    _reference = giraffe_table_get(reference);

    if (_fibers == NULL || cpl_table_has_column(_fibers, "FPS") == 0) {
        return -3;
    }

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

    if (cpl_table_has_column(_reference, "FPS") == 0 ||
        cpl_table_has_column(_reference, "TRANSMISSION") == 0) {
        return -4;
    }

    if (cpl_table_has_column(_fibers, "TRANSMISSION") == 0) {

        cxint status = cpl_table_new_column(_fibers, "TRANSMISSION",
                                            CPL_TYPE_DOUBLE);

        if (status) {
            return 1;
        }

    }

    for (i = 0; i < cpl_table_get_nrow(_fibers); i++) {

        cxint j;
        cxint nrows = cpl_table_get_nrow(_reference);
        cxint fps = cpl_table_get_int(_fibers, "FPS", i, NULL);

        cxdouble t = -1.;


        for (j = 0; j < nrows; j++) {

            cxint _fps = cpl_table_get_int(_reference, "FPS", j, NULL);

            if (fps == _fps) {
                t = cpl_table_get_double(_reference, "TRANSMISSION", j, NULL);
                break;
            }
        }

        if (t < 0.) {
            cpl_table_erase_column(_fibers, "TRANSMISSION");
            return 2;
        }
        else {

            cxint status = cpl_table_set_double(_fibers, "TRANSMISSION",
                                                i, t);

            if (status) {
                return 3;
            }

        }

    }

    return 0;

}


cxint
giraffe_transmission_apply(GiExtraction *extraction, GiTable *fibers)
{

    cxint status;

    cpl_image *_spectra = NULL;

    cpl_table *_fibers = NULL;



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

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


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

    _fibers = giraffe_table_get(fibers);

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


    if (cpl_table_has_column(_fibers, "TRANSMISSION") == 0) {
        return -5;
    }


    _spectra = giraffe_image_get(extraction->spectra);
    status = _giraffe_transmission_apply(_spectra, _fibers);

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

    if (extraction->error != NULL) {

        _spectra = giraffe_image_get(extraction->error);
        status = _giraffe_transmission_apply(_spectra, _fibers);

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

    return 0;

}


/**
 * @brief
 *   Load relative fiber transmission data from a file and add it to a fiber
 *   table.
 *
 * @param fibers    The fiber table to which the relative transmission data
 *                  is added.
 * @param filename  The name of the file from which the data is loaded.
 *
 * @return
 *   The function returns 0 on success and a non zero value otherwise.
 *
 * The function loads the fiber setup from @em filename. If the loaded fiber
 * setup provides the relative transmission information it is added to the
 * fiber table @em fibers. If no fiber setup is present in @em filename, or
 * if it does not contain the relative fiber transmission data, the function
 * returns an error.
 */

cxint
giraffe_transmission_attach(GiTable* fibers, const cxchar* filename)
{

    const cxchar* const _id = "giraffe_transmission_attach";

    cxint status = 0;

    GiTable* _fibers = NULL;


    if ((fibers == NULL) || (filename == NULL)) {
        return -1;
    }


    _fibers = giraffe_fiberlist_load(filename, 1, "FIBER_SETUP");

    if (fibers == NULL) {
        cpl_error_set(_id, CPL_ERROR_ILLEGAL_INPUT);
        return 1;
    }


    status = giraffe_transmission_setup(fibers, _fibers);

    giraffe_table_delete(_fibers);
    _fibers = NULL;

    if (status < 0) {
        cpl_error_set(_id, CPL_ERROR_DATA_NOT_FOUND);
        return 2;
    }

    if (status > 0) {
        cpl_error_set(_id, CPL_ERROR_INCOMPATIBLE_INPUT);
        return 3;
    }

    return status;

}


/**
 * @brief
 *   Creates a setup structure for the relative transmission computation.
 *
 * @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
 */

GiTransmissionConfig *
giraffe_transmission_config_create(cpl_parameterlist *list)
{

    GiTransmissionConfig *config = NULL;


    if (!list) {
        return NULL;
    }

    config = cx_calloc(1, sizeof *config);

    return config;

}


/**
 * @brief
 *   Destroys a transmission 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_transmission_config_destroy(GiTransmissionConfig *config)
{

    if (config) {
        cx_free(config);
    }

    return;
}


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

void
giraffe_transmission_config_add(cpl_parameterlist *list)
{

    if (!list) {
        return;
    }

    return;

}
/**@}*/
