/* $Id: kmo_priv_extract_spec.c,v 1.7 2013-03-12 14:55:37 erw Exp $
 *
 * This file is part of the KMOS Pipeline
 * Copyright (C) 2002,2003 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * $Author: erw $
 * $Date: 2013-03-12 14:55:37 $
 * $Revision: 1.7 $
 * $Name: not supported by cvs2svn $
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

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

#include <math.h>

#include <cpl.h>

#include "kmclipm_constants.h"
#include "kmclipm_functions.h"
#include "kmclipm_math.h"

#include "kmo_debug.h"
#include "kmo_utils.h"
#include "kmo_dfs.h"
#include "kmo_error.h"
#include "kmo_priv_extract_spec.h"

/*----------------------------------------------------------------------------*/
/**
    @defgroup kmos_priv_extract_spec     Helper functions for recipe kmo_extract_spec.

    @{
 */
/*----------------------------------------------------------------------------*/

/**
    @brief
        Extracts the spectrum of a data cube and an additional noise cube.

    Only the region defined by the mask is taken into account. The mask normally
    should contain values between 0.0 and 1.0.

    @param data_in        The input data cube.
    @param noise_in       Optional: The input noise cube.
    @param mask           The mask. If NULL, it is ignored
    @param spec_data_out  The calculated spectrum of the data.
    @param spec_noise_out Optional: The calculated spectrum of the noise.

    @return CPL_ERROR_NONE or the relevant _cpl_error_code_ on error.

    Possible cpl_error_code set in this function:
    @li CPL_ERROR_NULL_INPUT    if any input required pointer is NULL
    @li CPL_ERROR_ILLEGAL_INPUT if any of the inputs don't have the same size
*/
cpl_error_code kmo_priv_extract_spec(const cpl_imagelist *data_in,
                                     const cpl_imagelist *noise_in,
                                     cpl_image     *mask,
                                     cpl_vector    **spec_data_out,
                                     cpl_vector    **spec_noise_out)
{
    cpl_error_code  ret_error       = CPL_ERROR_NONE;

    const cpl_image  *tmp_img        = NULL;
    const cpl_mask   *bpm            = NULL;
    const cpl_binary *bpm_data       = NULL;
    cpl_mask         *empty_mask     = NULL;

    int             nx              = 0,
                    ny              = 0,
                    nz              = 0,
                    i               = 0,
                    j               = 0,
                    g               = 0;

    const float     *pmask          = NULL,
                    *ptmp_img       = NULL;

    double          sum             = 0.0,
                    weights         = 0.0,
                    *pvec           = NULL,
                    *pvecn          = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((data_in != NULL) &&
                       (spec_data_out != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_EXIT_IF_NULL(
            tmp_img = cpl_imagelist_get_const(data_in, 0));

        nx = cpl_image_get_size_x(tmp_img);
        ny = cpl_image_get_size_y(tmp_img);
        nz = cpl_imagelist_get_size(data_in);

        if (mask != NULL) {
            KMO_TRY_ASSURE((nx == cpl_image_get_size_x(mask)) &&
                           (ny == cpl_image_get_size_y(mask)),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Data and mask don't have same dimensions!");

            KMO_TRY_EXIT_IF_NULL(
                pmask = cpl_image_get_data_float_const(mask));
        }

        if (noise_in != NULL) {
            KMO_TRY_ASSURE(spec_noise_out != NULL,
                           CPL_ERROR_NULL_INPUT,
                           "Not all input data is provided!");

            KMO_TRY_EXIT_IF_NULL(
                tmp_img = cpl_imagelist_get_const(noise_in, 0));

            KMO_TRY_ASSURE((nx == cpl_image_get_size_x(tmp_img)) &&
                           (ny == cpl_image_get_size_y(tmp_img)) &&
                           (nz == cpl_imagelist_get_size(noise_in)),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Data and noise don't have same dimensions!");
        }

        KMO_TRY_EXIT_IF_NULL(
            *spec_data_out = cpl_vector_new(nz));

        KMO_TRY_EXIT_IF_NULL(
            pvec = cpl_vector_get_data(*spec_data_out));

        if (noise_in != NULL) {
            KMO_TRY_EXIT_IF_NULL(
                *spec_noise_out = cpl_vector_new(nz));

            KMO_TRY_EXIT_IF_NULL(
                pvecn = cpl_vector_get_data(*spec_noise_out));
        }

        KMO_TRY_EXIT_IF_NULL(
                empty_mask = cpl_mask_new(nx,ny));

        // loop over all spatial slices
        for (g = 0; g < nz; g++) {
            KMO_TRY_EXIT_IF_NULL(
                tmp_img = cpl_imagelist_get_const(data_in, g));
            KMO_TRY_EXIT_IF_NULL(
                ptmp_img = cpl_image_get_data_float_const(tmp_img));

            bpm = cpl_image_get_bpm_const(tmp_img);
            if (bpm == NULL) {
                bpm = empty_mask;
            }
            KMO_TRY_EXIT_IF_NULL(
                bpm_data = cpl_mask_get_data_const(bpm));

            // extract spectrum for data
            sum = 0.0;
            weights = 0.0;
            for (j = 0; j < ny; j++) {
                for (i = 0; i < nx; i++) {
                    // sum weighted pixels in spatial plane
                    if ((bpm_data[i+j*nx] == CPL_BINARY_0) && !kmclipm_is_nan_or_inf(ptmp_img[i+j*nx])) {
                        if (mask != NULL) {
                            sum += ptmp_img[i+j*nx] * pmask[i+j*nx];
                            weights += pmask[i+j*nx];
                        } else {
                            sum += ptmp_img[i+j*nx];
                            weights += 1.;
                        }
                    }
                }
            }
            pvec[g] = sum/weights;


            // extract spectrum for noise
            if (noise_in != NULL) {
                KMO_TRY_EXIT_IF_NULL(
                    tmp_img = cpl_imagelist_get_const(noise_in, g));
                KMO_TRY_EXIT_IF_NULL(
                    ptmp_img = cpl_image_get_data_float_const(tmp_img));

                sum = 0.0;
                weights = 0.0;
                for (j = 0; j < ny; j++) {
                    for (i = 0; i < nx; i++) {
                        // combine weighted pixels in spatial plane
                        if ((bpm_data[i+j*nx] == CPL_BINARY_0) && !kmclipm_is_nan_or_inf(ptmp_img[i+j*nx])) {
                            if (mask != NULL) {
                                sum += pow(ptmp_img[i+j*nx], 2) * pow(pmask[i+j*nx], 2);
                                weights += pow(pmask[i+j*nx], 2);
                            } else {
                                sum += pow(ptmp_img[i+j*nx], 2);
                                weights += 1.;
                            }
                        }
                    }
                }
                pvecn[g] = sqrt(sum/weights);
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    if (empty_mask != NULL) {cpl_mask_delete(empty_mask);}

    return ret_error;
}

/**
    @brief
        Copies WCS data from 3rd dimension to 1st one.

    WCS for 2nd and 3rd dimension are erased afterwards.

    @param header   The header to update.

    @return         The updated header.

    Possible cpl_error_code set in this function:
    @li CPL_ERROR_NULL_INPUT    if any input is NULL
*/
cpl_propertylist* kmo_priv_update_header(cpl_propertylist *header)
{
    KMO_TRY
    {
        KMO_TRY_ASSURE(header != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_ASSURE(cpl_propertylist_has(header, CRPIX3) &&
                       cpl_propertylist_has(header, CRVAL3) &&
                       cpl_propertylist_has(header, CDELT3) &&
                       cpl_propertylist_has(header, CTYPE3),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_update_property_double(header,
                                           CRPIX1,
                               cpl_propertylist_get_double(header, CRPIX3),
                               cpl_propertylist_get_comment(header, CRPIX1)));
        cpl_propertylist_erase(header, CRPIX2);
        KMO_TRY_CHECK_ERROR_STATE();
        cpl_propertylist_erase(header, CRPIX3);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_update_property_double(header,
                                           CRVAL1,
                               cpl_propertylist_get_double(header, CRVAL3),
                               cpl_propertylist_get_comment(header, CRVAL3)));
        cpl_propertylist_erase(header, CRVAL2);
        cpl_propertylist_erase(header, CRVAL3);

        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_update_property_double(header,
                                           CDELT1,
                               cpl_propertylist_get_double(header, CDELT3),
                               cpl_propertylist_get_comment(header, CDELT3)));
        cpl_propertylist_erase(header, CDELT2);
        KMO_TRY_CHECK_ERROR_STATE();
        cpl_propertylist_erase(header, CDELT3);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_update_property_string(header,
                                    CTYPE1,
                                    cpl_propertylist_get_string(header, CTYPE3),
                                    "Coordinate system of x-axis"));
        cpl_propertylist_erase(header, CTYPE2);
        KMO_TRY_CHECK_ERROR_STATE();
        cpl_propertylist_erase(header, CTYPE3);
        KMO_TRY_CHECK_ERROR_STATE();

        cpl_propertylist_erase(header, CD1_1);
        KMO_TRY_CHECK_ERROR_STATE();
        cpl_propertylist_erase(header, CD1_2);
        KMO_TRY_CHECK_ERROR_STATE();
        cpl_propertylist_erase(header, CD1_3);
        KMO_TRY_CHECK_ERROR_STATE();
        cpl_propertylist_erase(header, CD2_1);
        KMO_TRY_CHECK_ERROR_STATE();
        cpl_propertylist_erase(header, CD2_2);
        KMO_TRY_CHECK_ERROR_STATE();
        cpl_propertylist_erase(header, CD2_3);
        KMO_TRY_CHECK_ERROR_STATE();
        cpl_propertylist_erase(header, CD3_1);
        KMO_TRY_CHECK_ERROR_STATE();
        cpl_propertylist_erase(header, CD3_2);
        KMO_TRY_CHECK_ERROR_STATE();
        cpl_propertylist_erase(header, CD3_3);
        KMO_TRY_CHECK_ERROR_STATE();
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        header = NULL;
    }

    return header;
}

/** @} */
