
/*********************************************************************
 * E.S.O. - VLT project
 *
 * "@(#) $Id: kmclipm_priv_functions.c,v 1.13 2013-02-05 15:50:31 erw Exp $"
 *
 * Functions that are implemented as recipes. They are also useedd in KMOS
 * data reduction pipeline.
 *
 * who       when        what
 * --------  ----------  ----------------------------------------------
 * aagudo     2007-10-19  created
 * aagudo     2007-12-14  added new function:
 *                        kmclipm_priv_paint_ifu_rectangle_patrol()
 *                        (Makes boundary around IFU-images in patrol-view)
 * aagudo     2008-06-24  added kmclipm_priv_paint_ifu_rectangle_rtd() and
 *                        renamed kmclipm_priv_paint_ifu_rectangle() to
 *                        kmclipm_priv_paint_ifu_rectangle_patrol()
 */

/**
    @internal
    @defgroup kmclipm_priv_functions PRIVATE: Helper functions

    This module provides helper functions needed by the module
    @ref kmclipm_rtd_image .

    @par Synopsis:
    @code
      #include <kmclipm_priv_functions.h>
    @endcode
    @{
 */

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


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

#define _ISOC99_SOURCE

#include <math.h>
#include <float.h>
#include <string.h>
#include <dirent.h>

#include <cpl.h>

#include "kmclipm_constants.h"
#include "kmclipm_functions.h"
#include "kmclipm_priv_constants.h"
#include "kmclipm_priv_functions.h"
#include "kmclipm_priv_error.h"

int         kmclipm_cal_test_mode = -1;   /* initial default to check if */
                                          /* cal_file_path has been set  */
char        kmclipm_cal_file_path[1024];

int         kmclipm_file_path_was_set = FALSE;

/*------------------------------------------------------------------------------
 *                                  Definitions
 *----------------------------------------------------------------------------*/
/**
    @brief  Pastes a single IFU image into an image.
    @param  ifu_img The flattened IFU image to paste.
    @param  out_img Existing image into which the IFU image should be pasted.
    @param  x_pos   The pixel position of the image to be pasted in x-direction.
    @param  y_pos   The pixel position of the image to be pasted in y-direction.

    @return The maximum value of the input IFU image.

    Pastes a single, flattened IFU image into a bigger (alternatively a 
    PatrolView-image or a RTD-image) at a given position.

    Possible cpl_error_code set in this function:
    @li CPL_ERROR_NULL_INPUT if @c ifu_img or @c out_img is NULL
    @li CPL_ERROR_ILLEGAL_INPUT if @c x_pos or @c y_pos exceed image boundaries.

 */
float kmclipm_priv_paste_ifu_images(const cpl_image *ifu_img,
                                    cpl_image **out_img,
                                    const int x_pos,
                                    const int y_pos)
{
    int             x           = 0,
                    y           = 0,
                    c           = 0,
                    ifu_x_size  = 0,
                    ifu_y_size  = 0,
                    out_x_size  = 0,
                    out_y_size  = 0;

    const float     *v          = NULL;

    float           max_val     = 0;

    KMCLIPM_TRY
    {
        /* check for NULL input */
        KMCLIPM_TRY_CHECK_AUTOMSG(ifu_img != NULL,
            CPL_ERROR_NULL_INPUT);

        KMCLIPM_TRY_CHECK_AUTOMSG(*out_img != NULL,
            CPL_ERROR_NULL_INPUT);

        ifu_x_size = cpl_image_get_size_x(ifu_img);
        ifu_y_size = cpl_image_get_size_y(ifu_img);
        out_x_size = cpl_image_get_size_x(*out_img);
        out_y_size = cpl_image_get_size_y(*out_img);

        /* check for negative input or if input exceeds boundaries */
/*        KMCLIPM_TRY_CHECK_AUTOMSG((x_pos >= 0) &&
                        ((x_pos + ifu_x_size - 1) <= out_x_size),
            CPL_ERROR_ILLEGAL_INPUT);

        KMCLIPM_TRY_CHECK_AUTOMSG((y_pos >= 0) &&
                        ((y_pos + ifu_y_size - 1) <= out_y_size),
            CPL_ERROR_ILLEGAL_INPUT);
*/
        KMCLIPM_TRY_EXIT_IFN(
            v = cpl_image_get_data_float_const(ifu_img));
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        for (y = 0; y < ifu_y_size; y++) {
            for (x = 0; x < ifu_x_size; x++) {
                if ((x_pos + x > 0) && (x_pos + x <= out_x_size) &&
                    (y_pos + y > 0) && (y_pos + y <= out_y_size))
                {
                    KMCLIPM_TRY_EXIT_IFN(
                        cpl_image_set(*out_img, x_pos + x,
                                      y_pos + y, v[c]) == CPL_ERROR_NONE);
                }
                if (v[c] > max_val)
                    max_val = v[c];
                c++;
            }
        }
    }
    KMCLIPM_CATCH
    {
        max_val = -1.0;
    }

    return max_val;
}

/**
    @brief  Creates the PatrolView-image.
    @param  ifu_image_list  The image list containing the flattened IFU-images.
                            This list contains only the images which have
                            actually been processed and have to be displayed.
    @param  nominal_pos     The array indicating the nominal positions of
                            the IFUs. The array has to be of type and
                            size <tt>double[2 * KMOS_NR_IFUS + 2]</tt>. The
                            first and second values are not used, so
                            @c nominal_pos[2*i] designates the x-position of
                            IFU i and @c nominal_pos[2*i+1] designates the
                            y-position of IFU i. The unit of the values is in
                            millimeters.
    @param  ifu_id          The array specifying which IFUs of the 24 have to
                            be displayed. The array has to be of type and
                            size <tt>int[KMOS_NR_IFUS + 1]</tt>. The first value
                            is not used.
    @return The PatrolView-image.

    Creates an image with the whole patrol view of KMOS. Since the patrol field
    format covers 7.2arcmin (plus some extra blank space) at a sampling of 0.2''
    which matches that of the individual reconstructed images. Thus it will be
    2200x2200 pixels.. The sub-images are inserted at the nearest integer
    position to their actual locations. This is done to avoid the necessity of
    resampling the reconstructed images and because this accuracy (i.e. to half
    of a pixel, or 0.1'') is sufficient for the purpose of the patrol view.

    The returned image has to be deallocated with @c cpl_image_delete().

    Possible _cpl_error_code_ set in this function:
    @li CPL_ERROR_NULL_INPUT if any input is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if size of input imagelist is larger than
        maximum number of IFUs or if size of input imagelist is not equal the
        number of selected IFUs in ifu_id.
 */
cpl_image* kmclipm_priv_create_patrol_view(const cpl_imagelist *ifu_image_list,
                                           const double *nominal_pos,
                                           const int *ifu_id)
{
    int     half_patrol_size     = PATROL_FIELD_SIZE / 2,
            patrol_size_offset   = half_patrol_size - PATROL_FIELD_OFFSET,
            i                    = 0,
            x                    = 0,
            y                    = 0,
            *ifu_pos_x           = 0,
            *ifu_pos_y           = 0;

    float   max_val              = 1.0,
            *patrol_data         = NULL;

    double  xx                   = 0.0,
            yy                   = 0.0,
            r                    = 0.0,
            offset_ifu           = OFFSET_IFU,
            mm_per_field_of_view = MM_PER_FIELD_OF_VIEW;
            /* the last 2 definitions to prevent excessive inline-multiplications
               in the for-loops*/

    const cpl_image   *ifu_img = NULL;

    cpl_image   *patrol = NULL;

    KMCLIPM_TRY
    {
        /* Validate inputs */
        KMCLIPM_TRY_CHECK_AUTOMSG(ifu_image_list != NULL,
            CPL_ERROR_NULL_INPUT);

        KMCLIPM_TRY_CHECK_AUTOMSG(nominal_pos != NULL,
            CPL_ERROR_NULL_INPUT);

        KMCLIPM_TRY_CHECK_AUTOMSG(ifu_id != NULL,
            CPL_ERROR_NULL_INPUT);

        KMCLIPM_TRY_CHECK_AUTOMSG(cpl_imagelist_get_size(ifu_image_list)
                                                                == KMOS_NR_IFUS,
            CPL_ERROR_ILLEGAL_INPUT);

        KMCLIPM_TRY_EXIT_IFN(
            patrol = cpl_image_new(PATROL_FIELD_SIZE, PATROL_FIELD_SIZE,
                               CPL_TYPE_FLOAT));

        /* calculate integer position where to insert IFUs*/
        KMCLIPM_TRY_EXIT_IFN(
            ifu_pos_x = (int*)cpl_calloc(KMOS_NR_IFUS+1, sizeof(int)));
        KMCLIPM_TRY_EXIT_IFN(
            ifu_pos_y = (int*)cpl_calloc(KMOS_NR_IFUS+1, sizeof(int)));

        for (i = 1; i <= KMOS_NR_IFUS; i++) {
            if (ifu_id[i] > 0) {
                ifu_pos_x[i] = (int)(patrol_size_offset *
                                     (nominal_pos[2*i] - offset_ifu) /
                                     mm_per_field_of_view +
                                     half_patrol_size +
                                     1);
                ifu_pos_y[i] = (int)(patrol_size_offset *
                                     (nominal_pos[2*i+1] - offset_ifu) /
                                     mm_per_field_of_view +
                                     half_patrol_size +
                                     1);
            }
        }

        /* insert IFU-images at given positions */
        for (i = 1; i <= KMOS_NR_IFUS; i++) {
            /* check if nominal positions contain valid values,
             * otherwise no IFU-image will be drawn */
            if (ifu_id[i] > 0) {
                /* get IFU-image & extract data */
                KMCLIPM_TRY_EXIT_IFN(
                    ifu_img = cpl_imagelist_get_const(ifu_image_list, i-1));

                max_val = kmclipm_priv_paste_ifu_images(ifu_img,
                                                        &patrol,
                                                        ifu_pos_x[i],
                                                        ifu_pos_y[i]);
                KMCLIPM_TRY_CHECK_ERROR_STATE();
          }
        }
        KMCLIPM_TRY_EXIT_IFN(
            patrol_data = cpl_image_get_data_float(patrol));

        /* draw circle of patrol field */
        for (x = 0; x < half_patrol_size; x++) {
            for (y = 0; y < half_patrol_size; y++) {
                xx = fabs(x - half_patrol_size);
                yy = fabs(y - half_patrol_size);
                r = sqrt(xx * xx + yy * yy);

                /* following if statement asserts that the circle is 
                   not too narrow */
                if (( r < (double)patrol_size_offset + 0.5) && 
                        ( r > (double)patrol_size_offset - 0.5))
                {
                    /* lower left quadrant */
                    patrol_data[x + y * PATROL_FIELD_SIZE] = max_val;
                    /* upper right quadrant */
                    patrol_data[(2 * half_patrol_size - 1 - x)
                                + (2 * half_patrol_size -1 - y)
                                * PATROL_FIELD_SIZE] = max_val;
                    /* upper left quadrant */
                    patrol_data[x + (2 * half_patrol_size -1 - y)
                                * PATROL_FIELD_SIZE] = max_val;
                    /* lower right quadrant */
                    patrol_data[(2 * half_patrol_size - 1 - x)
                                + y * PATROL_FIELD_SIZE] = max_val;
                }
           }
        }

        /* draw rectangles around IFUs */
        for (i = 1; i <= KMOS_NR_IFUS; i++) {
            if (ifu_id[i] > 0) {
                kmclipm_priv_paint_ifu_rectangle_patrol(&patrol,
                                                        ifu_pos_x[i],
                                                        ifu_pos_y[i],
                                                        max_val);
                KMCLIPM_TRY_CHECK_ERROR_STATE();
            }
        }
    }
    KMCLIPM_CATCH
    {
        cpl_image_delete(patrol); patrol = NULL;
    }

    cpl_free(ifu_pos_x); ifu_pos_x = NULL;
    cpl_free(ifu_pos_y); ifu_pos_y = NULL;

    return patrol;
}

/**
    @brief  Paints borders around IFU-image in patrol-image.
    @param  out_img Existing image into which the borders should be painted.
    @param  x_pos   The pixel position of the border.
    @param  y_pos   The pixel position of the border.
    @param  val     The brightness of the rectangle to draw.

    Draws a border around an IFU-image into a PatrolView-image.

    Possible _cpl_error_code_ set in this function:
    @li CPL_ERROR_NULL_INPUT if @c out_img is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if @c x_pos or @c y_pos exceed image boundaries.

 */
void kmclipm_priv_paint_ifu_rectangle_patrol(cpl_image **out_img,
                                             const int x_pos,
                                             const int y_pos,
                                             float val)
{
    int     x           = 0,
            y           = 0,
            i           = 0,
            out_x_size  = 0,
            out_y_size  = 0,
            my_x        = 0,
            my_y        = 0;

    float  *patrol_data = NULL;

    KMCLIPM_TRY
    {
        /* check for NULL input */
        KMCLIPM_TRY_CHECK_AUTOMSG(*out_img != NULL,
            CPL_ERROR_NULL_INPUT);

        /* check for negative input or if input exceeds boundaries */
/*        KMCLIPM_TRY_CHECK_AUTOMSG((x_pos >= 1) &&
            ((x_pos + KMOS_SLITLET_X + PATROL_FIELD_BORDER - 1) <=
                                                cpl_image_get_size_x(*out_img)),
            CPL_ERROR_ILLEGAL_INPUT);

        KMCLIPM_TRY_CHECK_AUTOMSG((y_pos >= 1) &&
            ((y_pos + KMOS_SLITLET_Y + PATROL_FIELD_BORDER - 1) <=
                                                cpl_image_get_size_y(*out_img)),
            CPL_ERROR_ILLEGAL_INPUT);
*/
        KMCLIPM_TRY_EXIT_IFN(
            patrol_data = cpl_image_get_data_float(*out_img));

        out_x_size = cpl_image_get_size_x(*out_img);
        out_y_size = cpl_image_get_size_y(*out_img);

        KMCLIPM_TRY_CHECK_AUTOMSG((out_x_size == PATROL_FIELD_SIZE) &&
                                  (out_y_size == PATROL_FIELD_SIZE),
                                  CPL_ERROR_ILLEGAL_INPUT);

        for (x = x_pos - PATROL_FIELD_BORDER;
             x < x_pos - PATROL_FIELD_BORDER + KMOS_SLITLET_X +
                                                        2 * PATROL_FIELD_BORDER;
             x++) {
            /* draw horizontal lines */
            for (i = 0; i < PATROL_FIELD_BORDER; i++) {
                my_x = x - 1;
                my_y = y_pos - 1 - PATROL_FIELD_BORDER + i;
                if ((my_x >= 0) && (my_x < out_x_size) &&
                    (my_y >= 0) && (my_y < out_y_size))
                {
                    patrol_data[(int)my_x + my_y*PATROL_FIELD_SIZE] = val;
                }
            }
            for (i = 0; i < PATROL_FIELD_BORDER; i++) {
                my_x = x - 1;
                my_y = y_pos - 2 + KMOS_SLITLET_Y + PATROL_FIELD_BORDER - i;
                if ((my_x >= 0) && (my_x < out_x_size) &&
                    (my_y >= 0) && (my_y < out_y_size))
                {
                    patrol_data[(int)my_x + my_y*PATROL_FIELD_SIZE] = val;
                }
            }
        }
       for (y = y_pos - PATROL_FIELD_BORDER;
            y < y_pos - PATROL_FIELD_BORDER + KMOS_SLITLET_Y +
                                                  2 * (PATROL_FIELD_BORDER - 1);
            y++) {
            /* draw vertical lines */
            for (i = 0; i < PATROL_FIELD_BORDER; i++) {
                my_x = x_pos - 1 - PATROL_FIELD_BORDER + i;
                my_y = y;
                if ((my_x >= 0) && (my_x < out_x_size) &&
                    (my_y >= 0) && (my_y < out_y_size))
                {
                    patrol_data[(int)my_x + my_y*PATROL_FIELD_SIZE] = val;
                }
            }
            for (i = 0; i < PATROL_FIELD_BORDER; i++) {
                my_x = x_pos - 2 + KMOS_SLITLET_Y + PATROL_FIELD_BORDER - i;
                my_y = y;
                if ((my_x >= 0) && (my_x < out_x_size) &&
                    (my_y >= 0) && (my_y < out_y_size))
                {
                    patrol_data[(int)my_x + my_y*PATROL_FIELD_SIZE] = val;
                }
            }
        }
    }
    KMCLIPM_CATCH
    {
    }
}

/**
    @brief  Paints borders around IFU-image in RTD-image.
    @param  rtd_img Existing image into which the borders should be painted.
    @param  ifu_id The array indicating which IFUs actually have to be processed
                   and if target and sky are switched. The array has to be of
                   type and size <tt>int[KMOS_NR_IFUS + 1]</tt>. The first
                   value is not used, so <tt>ifu_id[i]</tt> designates IFU @c i.
                   If the IFU should be processed, set the corresponding value
                   to @c 1, otherwise to @c 0 . If target and sky are switched
                   the value must be @c 2 .
    @param val     The brightness of the rectangle to draw.

    Draws a border around an IFU-image in a RTD-image.

    Possible _cpl_error_code_ set in this function:
    @li CPL_ERROR_NULL_INPUT if @c rtd_img or @c ifu_id is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if the height or width of @c rtd_img is incorrect.
 */
void kmclipm_priv_paint_ifu_rectangle_rtd(cpl_image **rtd_img,
                                          const int *ifu_id,
                                          float val)
{
    int     i               = 0,
            x               = 0,
            y               = 0,
            rtd_width       = 0,
            rtd_height      = 0;

    float   *rtd_img_data   = NULL;

    KMCLIPM_TRY
    {
        /* check for NULL input */
        KMCLIPM_TRY_CHECK_AUTOMSG(*rtd_img != NULL,
            CPL_ERROR_NULL_INPUT);

        KMCLIPM_TRY_CHECK_AUTOMSG(ifu_id != NULL,
            CPL_ERROR_NULL_INPUT);

        rtd_width = kmclipm_priv_get_rtd_width();
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        KMCLIPM_TRY_CHECK_AUTOMSG(rtd_width == cpl_image_get_size_x(*rtd_img),
            CPL_ERROR_ILLEGAL_INPUT);

        rtd_height = kmclipm_priv_get_rtd_height();
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        KMCLIPM_TRY_CHECK_AUTOMSG(rtd_height == cpl_image_get_size_y(*rtd_img),
            CPL_ERROR_ILLEGAL_INPUT);

        KMCLIPM_TRY_EXIT_IFN(
                rtd_img_data = cpl_image_get_data_float(*rtd_img));

        for (i = 1; i <= KMOS_NR_IFUS; i++) {
            /* if no IFU-image is available, a rectangle is drawn */
            if (ifu_id[i] == 0) {
                for (x = kmclipm_priv_ifu_pos_x(i - 1);
                     x < kmclipm_priv_ifu_pos_x(i - 1) + KMOS_SLITLET_X;
                     x++) {
                    /* draw horizontal lines */
                    rtd_img_data[(int)(x - 1) + (kmclipm_priv_ifu_pos_y(i - 1)
                                                        - 1) * rtd_width] = val;

                    rtd_img_data[(int)(x - 1) + (kmclipm_priv_ifu_pos_y(i - 1)
                                          + KMOS_SLITLET_Y - 1 - 1)
                                                             * rtd_width] = val;
                }
                for (y = kmclipm_priv_ifu_pos_y(i - 1) + 1;
                     y < kmclipm_priv_ifu_pos_y(i - 1) + KMOS_SLITLET_Y - 1;
                     y++) {
                    /* draw vertical lines */
                    rtd_img_data[(kmclipm_priv_ifu_pos_x(i -1) - 1)
                                              + (int)(y - 1) * rtd_width] = val;

                    rtd_img_data[(kmclipm_priv_ifu_pos_x(i -1)
                                 + KMOS_SLITLET_X - 1 - 1) + (int)(y - 1)
                                                             * rtd_width] = val;
                }
            }
        }
    }
    KMCLIPM_CATCH
    {
    }
}

/**
    @internal
    @brief  Describes a 2D gauss function without rotation.
    @param  x       The values.
    @param  a       The parameters to be fitted.
    @param  result  The result.

    @return Returns always 0.

    This function describes a 2D gauss function. It is needed
    in kmclipm_gaussfit_2d().

    Function that evaluates the fit function at the position specified by the
    first argument (an array of size D) using the fit parameters specified by the
    second argument (an array of size M). The result must be output using the
    third parameter, and the function must return zero iff the evaluation succeded.
*/
int kmclipm_priv_gauss2d_fnc(const double x[], const double a[], double *result)
{
    double height  = a[0]; /* height of gaussian */
    double xcenter = a[1]; /* center_x */
    double ycenter = a[2]; /* center_y */
    double fwhm    = a[3]; /* fwhm */
    double offset  = a[4]; /* constant offset */

    double fwhm2 = fwhm * fwhm;
    double dx2 = (x[0] - xcenter) * (x[0] - xcenter);
    double dy2 = (x[1] - ycenter) * (x[1] - ycenter);
    double four_ln2 = -4.0 * log(2.0);
    double Y = exp(four_ln2 / fwhm2 * (dx2 + dy2));

    *result = height * Y + offset;

    return 0;
}

/**
    @internal
    @brief  Describes the derivative of a 2D gauss function without rotation.
    @param  x       The values.
    @param  a       The parameters to be fitted.
    @param  result  The result.

    @return Returns always 0.

    This function describes the derivative of a 2D gauss function. It is needed
    in kmclipm_gaussfit_2d().

    Function that evaluates the first order partial derivatives of the fit
    function with respect to the fit parameters at the position specified by the
    first argument (an array of size D) using the parameters specified by the
    second argument (an array of size M). The result must be output using the
    third parameter (array of size M), and the function must return zero if the
    evaluation succeded.
 */
int kmclipm_priv_gauss2d_fncd(const double x[], const double a[], double result[])
{
    double height  = a[0]; /* height of gaussian */
    double xcenter = a[1]; /* center_x */
    double ycenter = a[2]; /* center_y */
    double fwhm    = a[3]; /* fwhm */

    double fwhm2 = fwhm * fwhm;
    double dx2 = (x[0] - xcenter) * (x[0] - xcenter);
    double dy2 = (x[1] - ycenter) * (x[1] - ycenter);
    double four_ln2 = -4.0 * log(2.0);
    double Y = exp(four_ln2 / fwhm2 * (dx2 + dy2));

    /* derivative in heigth */
    result[0] = Y;

    /* derivative in xcenter */
    result[1] = -2 * height * four_ln2 * Y * (x[0] - xcenter) / fwhm2;

    /* derivative in ycenter */
    result[2] = -2 * height * four_ln2 * Y * (x[1] - ycenter) / fwhm2;

    /* derivative in fwhm */
    result[3] = -2 * height * four_ln2 * Y * (dx2 + dy2) / (fwhm2 * fwhm);

    /* derivative in offset */
    result[4] = 1;

    return 0;
}

/**
    @brief
        All values contained in @c vec are printed for debugging purposes.

    @param vec The vector to print.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT if @c img is NULL.
*/
cpl_error_code kmclipm_priv_debug_vector(const cpl_vector *vec)
{
    int             x           = 0,
                    size        = 0;
    cpl_error_code  ret_error   = CPL_ERROR_NONE;
    const double    *pvec       = NULL;

    KMCLIPM_TRY
    {
        KMCLIPM_TRY_CHECK(vec != NULL,
                          CPL_ERROR_NULL_INPUT,
                          NULL,
                          "No input data provided!");

        KMCLIPM_TRY_EXIT_IFN(
            pvec = cpl_vector_get_data_const(vec));

        cpl_msg_debug("", "     ====== START VECTOR ======\n");
        size = cpl_vector_get_size(vec);
        for (x = 0; x < size; x++) {
            cpl_msg_debug("", "%g   \n", pvec[x]);
        }
        cpl_msg_debug("", "     ====== END VECTOR ======\n");
        KMCLIPM_TRY_CHECK_ERROR_STATE();
    }
    KMCLIPM_CATCH
    {
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        All values contained in @c img are printed for debugging purposes.

    @param img The image to print.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT if @c img is NULL.
*/
cpl_error_code kmclipm_priv_debug_image(const cpl_image *img)
{
    int             x           = 0,
                    y           = 0;
    int             gaga        = 0;
    cpl_error_code  ret_error   = CPL_ERROR_NONE;

    KMCLIPM_TRY
    {
        KMCLIPM_TRY_CHECK(img != NULL,
                          CPL_ERROR_NULL_INPUT,
                          NULL,
                          "No input data provided!");

        cpl_msg_debug("", "     ====== START IMAGE ======\n");
        for (y = 1; y <= cpl_image_get_size_y(img); y++) {
            for (x = 1; x <= cpl_image_get_size_x(img); x++) {
                cpl_msg_debug("", "%f   ", cpl_image_get(img, x, y, &gaga));
                KMCLIPM_TRY_CHECK_ERROR_STATE();
            }
            cpl_msg_debug("", "\n");
        }
        cpl_msg_debug("", "     ====== END IMAGE ======\n");
        KMCLIPM_TRY_CHECK_ERROR_STATE();
    }
    KMCLIPM_CATCH
    {
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        All images contained in @c imglist are printed for debugging purposes.

    @param imglist The imagelist to print.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT if @c imglist is NULL.
*/
cpl_error_code kmclipm_priv_debug_cube(const cpl_imagelist *imglist)
{
    int             i           = 0;
    cpl_error_code  ret_error   = CPL_ERROR_NONE;

    KMCLIPM_TRY
    {
        KMCLIPM_TRY_CHECK(imglist != NULL,
                          CPL_ERROR_NULL_INPUT,
                          NULL,
                          "No input data provided!");

        cpl_msg_debug("", "====== START IMAGELIST ======\n");
        for (i = 0; i < cpl_imagelist_get_size(imglist); i++) {
            KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                kmclipm_priv_debug_image(cpl_imagelist_get_const(imglist, i)));
        }
        cpl_msg_debug("", "====== END IMAGELIST ======\n");
        KMCLIPM_TRY_CHECK_ERROR_STATE();
    }
    KMCLIPM_CATCH
    {
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

typedef struct {
    double angle;
    double angle_Stripped;
} angleStruct;

/**
    @brief
        Find suiting cal-files for a given NAANGLE

    @param find_angle  The actual NAANGLE to match to existing cal-files.
    @param filt        The filter (may be changed to "K" for test mode)
    @param grat        The filter (may be changed to "K" for test mode)
    @param fn_xcal     (Output) The identified xcal-path
    @param fn_ycal     (Output) The identified ycal-path
    @param fn_lcal     (Output) The identified lcal-path
    @param fn_lut      (Output) The identified lut-path

    This function doesn't assume that XCAL files are named xcal_xxx_xxx_x.fits.
    It parses all fits-files found in the calib-directory!

    The output pointers have to be deallocated with cpl_free() again!

    @return
        CPL_ERROR_NONE or applicable error code

    @li CPL_ERROR_NULL_INPUT     if any of the inputs is NULL.
*/
cpl_error_code kmclipm_priv_find_angle(double find_angle,
                                       char **filt,
                                       char **grat,
                                       char **fn_xcal,
                                       char **fn_ycal,
                                       char **fn_lcal,
                                       char **fn_lut)
{
    cpl_error_code      err                 = CPL_ERROR_NONE;
    DIR                 *d                  = NULL;
    struct dirent       *dir                = NULL;
    int                 nr_found            = 0,
                        smallest_index      = 0,
                        i                   = 0;
    cpl_propertylist    *pl                 = NULL;
    double              smallest_diff       = DBL_MAX,
                        tmp_dbl             = find_angle,
                        find_angle_stripped = 0;
    char                *tmp_path           = NULL,
                        *filt1              = NULL,
                        *filt2              = NULL,
                        *filt3              = NULL,
                        *grat1              = NULL,
                        *grat2              = NULL,
                        *grat3              = NULL,
                        *fn_lut_tmp         = NULL;

    angleStruct         *angles             = NULL;
    int                 anglesCnt           = 60;
    KMCLIPM_TRY
    {
        KMCLIPM_TRY_CHECK((filt != NULL) &&
                          (grat != NULL) &&
                          (*filt != NULL) &&
                          (*grat != NULL) &&
                          (fn_xcal != NULL) &&
                          (fn_ycal != NULL) &&
                          (fn_lcal != NULL) &&
                          (fn_lut != NULL),
                          CPL_ERROR_NULL_INPUT,
                          NULL,
                          "Not all input data provided!");

        if (kmclipm_cal_test_mode == TRUE) {
            /* we are in test mode here so set filter & grating to "K"
               and return test-names for calibration-files */
            strncpy(*filt, "K", 1);
            if (sizeof(*filt)>=2) {
                (*filt)[1]='\0';
            }
            strncpy(*grat, "K", 1);
            if (sizeof(*grat)>=2) {
                (*grat)[1]='\0';
            }

            KMCLIPM_TRY_EXIT_IFN(
                *fn_xcal = cpl_sprintf("%sxcal_test.fits", kmclipm_get_cal_path()));
            KMCLIPM_TRY_EXIT_IFN(
                *fn_ycal = cpl_sprintf("%sycal_test.fits", kmclipm_get_cal_path()));
            KMCLIPM_TRY_EXIT_IFN(
                *fn_lcal = cpl_sprintf("%slcal_test.fits", kmclipm_get_cal_path()));
            KMCLIPM_TRY_EXIT_IFN(
                *fn_lut = cpl_sprintf("%slut_KKK_0", kmclipm_get_cal_path()));
        } else {
            KMCLIPM_TRY_EXIT_IFN(
                filt1 = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, 1,
                                    IFU_FILTID_POSTFIX));
            KMCLIPM_TRY_EXIT_IFN(
                filt2 = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, 2,
                                    IFU_FILTID_POSTFIX));
            KMCLIPM_TRY_EXIT_IFN(
                filt3 = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, 3,
                                    IFU_FILTID_POSTFIX));
            KMCLIPM_TRY_EXIT_IFN(
                grat1 = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX, 1,
                                    IFU_GRATID_POSTFIX));
            KMCLIPM_TRY_EXIT_IFN(
                grat2 = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX, 2,
                                    IFU_GRATID_POSTFIX));
            KMCLIPM_TRY_EXIT_IFN(
                grat3 = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX, 3,
                                    IFU_GRATID_POSTFIX));

            /* prepare struct */
            KMCLIPM_TRY_EXIT_IFN(
                angles = (angleStruct*)cpl_calloc(anglesCnt,
                                                  sizeof(angleStruct)));

            find_angle_stripped = kmclipm_strip_angle(&tmp_dbl);
            KMCLIPM_TRY_CHECK_ERROR_STATE();

            /* examine cal_path for all xcal-files and find out needed angle
               (stripped and non-stripped) */
            d = opendir(kmclipm_get_cal_path());
            if (d) {
                while ((dir = readdir(d)) != NULL) {
                    tmp_path = cpl_sprintf("%s%s", kmclipm_get_cal_path(),
                                                 dir->d_name);
                    pl = cpl_propertylist_load(tmp_path, 0);

                    if (pl != NULL) {
                        /* found fits-file, check if:
                           - it is a XCAL-file
                           - it has ROTANGLE keyword
                           - if filter and grating match for all detectors */
                        if (cpl_propertylist_has(pl, CPL_DFS_PRO_CATG) &&
                            (strcmp(cpl_propertylist_get_string(pl, CPL_DFS_PRO_CATG),  "XCAL") == 0) &&
                            cpl_propertylist_has(pl, ROTANGLE) &&
                            cpl_propertylist_has(pl, filt1) &&
                            (strcmp(cpl_propertylist_get_string(pl, filt1),  *filt) == 0) &&
                            cpl_propertylist_has(pl, filt2) &&
                            (strcmp(cpl_propertylist_get_string(pl, filt2),  *filt) == 0) &&
                            cpl_propertylist_has(pl, filt3) &&
                            (strcmp(cpl_propertylist_get_string(pl, filt3),  *filt) == 0) &&
                            cpl_propertylist_has(pl, grat1) &&
                            (strcmp(cpl_propertylist_get_string(pl, grat1),  *grat) == 0) &&
                            cpl_propertylist_has(pl, grat2) &&
                            (strcmp(cpl_propertylist_get_string(pl, grat2),  *grat) == 0) &&
                            cpl_propertylist_has(pl, grat3) &&
                            (strcmp(cpl_propertylist_get_string(pl, grat3),  *grat) == 0))
                        {
                            double tmp_dbl = cpl_propertylist_get_double(pl,
                                                                         ROTANGLE);
                            angles[nr_found].angle = tmp_dbl;
                            angles[nr_found].angle_Stripped = kmclipm_strip_angle(&tmp_dbl);
                            nr_found++;
                            if (nr_found == anglesCnt) {
                                anglesCnt +=20;
                                KMCLIPM_TRY_EXIT_IFN(
                                    angles = (angleStruct*)cpl_realloc(angles,
                                                                       anglesCnt));
                            }
                            KMCLIPM_TRY_CHECK_ERROR_STATE();
                        }

                        cpl_propertylist_delete(pl); pl = NULL;
                        KMCLIPM_TRY_CHECK_ERROR_STATE();
                    } else {
                        /* found file isn't a FITS file */
                        cpl_error_reset();
                    } /* if pl == NULL */
                    cpl_free(tmp_path); tmp_path = NULL;
                    KMCLIPM_TRY_CHECK_ERROR_STATE();
                } /* while readdir() */
                closedir(d);

                if (nr_found > 0) {
                    /* fix singularity in 0/360 (if angle is e.g. -1)
                       add additional angle with smallest stripped value, add 360 */
                    smallest_diff = angles[0].angle_Stripped;
                    smallest_index = 0;
                    for (i = 1; i < nr_found; i++) {
                        if (angles[i].angle_Stripped < smallest_diff) {
                          smallest_diff = angles[i].angle_Stripped;
                          smallest_index = i;
                        }
                    }

                    angles[nr_found].angle = angles[smallest_index].angle;
                    angles[nr_found].angle_Stripped = angles[smallest_index].angle_Stripped + 360.;
                    nr_found++;

                    /* find smallest difference of stripped angles */
                    smallest_diff = fabs(angles[0].angle_Stripped-find_angle_stripped);
                    smallest_index = 0;
                    for (i = 1; i < nr_found; i++) {
                        double tmp_diff = fabs(angles[i].angle_Stripped-find_angle_stripped);
                        if ((tmp_diff < smallest_diff) ||
                            ((tmp_diff == smallest_diff) &&
                             (angles[i].angle_Stripped < angles[smallest_index].angle_Stripped)))
                        {
                            smallest_diff = tmp_diff;
                            smallest_index = i;
                        }
                    }

                    /* the wanted angle is angles[smallest_index].angle,
                       the wanted stripped angle is angles[smallest_index].angle_Stripped */
                    double tmptmp = angles[smallest_index].angle_Stripped;
                    KMCLIPM_TRY_EXIT_IFN(
                        fn_lut_tmp = cpl_sprintf("lut_%s%s%s_%d",
                                                 *grat, *grat, *grat,
                                                 (int)kmclipm_strip_angle(&tmptmp)));

                    /* now go through directory again and retrieve XCAL-, YCAL-, LCAL- and lut-filenames */
                    d = opendir(kmclipm_get_cal_path());
                    if (d) {
                        while ((dir = readdir(d)) != NULL) {
                            tmp_path = cpl_sprintf("%s%s", kmclipm_get_cal_path(),
                                                         dir->d_name);
                            pl = cpl_propertylist_load(tmp_path, 0);

                            if (pl != NULL) {
                                /* found fits-file, check if:
                                   - it has PRO CATG & ROTANGLE keywords
                                   - if filter and grating match for all detectors */
                                if (cpl_propertylist_has(pl, CPL_DFS_PRO_CATG) &&
                                    cpl_propertylist_has(pl, ROTANGLE) &&
                                    cpl_propertylist_has(pl, filt1) &&
                                    (strcmp(cpl_propertylist_get_string(pl, filt1),  *filt) == 0) &&
                                    cpl_propertylist_has(pl, filt2) &&
                                    (strcmp(cpl_propertylist_get_string(pl, filt2),  *filt) == 0) &&
                                    cpl_propertylist_has(pl, filt3) &&
                                    (strcmp(cpl_propertylist_get_string(pl, filt3),  *filt) == 0) &&
                                    cpl_propertylist_has(pl, grat1) &&
                                    (strcmp(cpl_propertylist_get_string(pl, grat1),  *grat) == 0) &&
                                    cpl_propertylist_has(pl, grat2) &&
                                    (strcmp(cpl_propertylist_get_string(pl, grat2),  *grat) == 0) &&
                                    cpl_propertylist_has(pl, grat3) &&
                                    (strcmp(cpl_propertylist_get_string(pl, grat3),  *grat) == 0))
                                {
                                    /* it can be an XCAL-, YCAL-, LCAL-file */
                                    double tmp_dbl = cpl_propertylist_get_double(pl,
                                                                                 ROTANGLE);
                                    if (fabs(tmp_dbl - angles[smallest_index].angle) < 0.01) {
                                        /* found file with matching ROTANGLE and PRO CATG */
                                        if (strcmp(cpl_propertylist_get_string(pl,
                                                CPL_DFS_PRO_CATG),  "XCAL") == 0)  {
                                            KMCLIPM_TRY_EXIT_IFN(
                                                *fn_xcal = cpl_sprintf("%s", tmp_path));
                                        } else if (strcmp(cpl_propertylist_get_string(pl,
                                                CPL_DFS_PRO_CATG),  "YCAL") == 0)  {
                                            KMCLIPM_TRY_EXIT_IFN(
                                                *fn_ycal = cpl_sprintf("%s", tmp_path));
                                        } else if (strcmp(cpl_propertylist_get_string(pl,
                                                CPL_DFS_PRO_CATG),  "LCAL") == 0)  {
                                            KMCLIPM_TRY_EXIT_IFN(
                                                *fn_lcal = cpl_sprintf("%s", tmp_path));
                                        } else {
                                        }
                                    }
                                }

                                cpl_propertylist_delete(pl); pl = NULL;
                                KMCLIPM_TRY_CHECK_ERROR_STATE();
                            } else {
                                /* found file isn't a FITS file */
                                cpl_error_reset();
                            } /* if pl == NULL */

                            cpl_free(tmp_path); tmp_path = NULL;
                            KMCLIPM_TRY_CHECK_ERROR_STATE();
                        } /* while readdir() */
                        closedir(d);
                    } else {
                        KMCLIPM_ERROR_SET_MSG(CPL_ERROR_FILE_IO,
                                        "",
                                        "The calibration path must be set using "
                                        "kmclipm_set_cal_path()!");
                        KMCLIPM_TRY_CHECK_ERROR_STATE();
                    } /* end if (d) */

                    /* always return lut-path, even if it is not available,
                       otherwise it can't be created!*/
                    KMCLIPM_TRY_EXIT_IFN(
                        *fn_lut = cpl_sprintf("%s%s",
                                              kmclipm_get_cal_path(),
                                              fn_lut_tmp));
                } /* end if nr_found > 0 */
            } else {
                KMCLIPM_ERROR_SET_MSG(CPL_ERROR_FILE_IO,
                                "",
                                "The calibration path must be set using "
                                "kmclipm_set_cal_path()!");
                KMCLIPM_TRY_CHECK_ERROR_STATE();
            }/* end if (d) */
        }


        if (*fn_xcal == NULL)
        {
            cpl_free(tmp_path); tmp_path = NULL;
            tmp_path = cpl_sprintf("No XCAL calibration file couldn't be identified! "
                                   "(with matching filter %s and grating %s)",
                                   *filt, *grat);
            KMCLIPM_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_OUTPUT,
                            "",
                            tmp_path);
        } else if (*fn_ycal == NULL)
        {
            cpl_free(tmp_path); tmp_path = NULL;
            tmp_path = cpl_sprintf("No YCAL calibration file couldn't be identified! "
                                   "(with matching filter %s and grating %s)",
                                   *filt, *grat);
            KMCLIPM_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_OUTPUT,
                            "",
                            tmp_path);
        } else if (*fn_lcal == NULL)
        {
            cpl_free(tmp_path); tmp_path = NULL;
            tmp_path = cpl_sprintf("No LCAL calibration file couldn't be identified! "
                                   "(with matching filter %s and grating %s)",
                                   *filt, *grat);
            KMCLIPM_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_OUTPUT,
                            "",
                            tmp_path);
        }
    }
    KMCLIPM_CATCH
    {
        cpl_msg_error(cpl_func, \
                      "%s (Code %d) in %s", \
                      cpl_error_get_message(), \
                      cpl_error_get_code(), \
                      cpl_error_get_where()); \
        err = cpl_error_get_code();
        if (fn_xcal != NULL) {
            cpl_free(*fn_xcal); *fn_xcal = NULL;
        }
        if (fn_ycal != NULL) {
            cpl_free(*fn_ycal); *fn_ycal = NULL;
        }
        if (fn_lcal != NULL) {
            cpl_free(*fn_lcal); *fn_lcal = NULL;
        }
        if (fn_lut != NULL) {
            cpl_free(*fn_lut); *fn_lut = NULL;
        }
    }

    cpl_free(tmp_path); tmp_path = NULL;
    cpl_free(fn_lut_tmp); fn_lut_tmp = NULL;
    cpl_free(filt1); filt1 = NULL;
    cpl_free(filt2); filt2 = NULL;
    cpl_free(filt3); filt3 = NULL;
    cpl_free(grat1); grat1 = NULL;
    cpl_free(grat2); grat2 = NULL;
    cpl_free(grat3); grat3 = NULL;
    cpl_free(angles); angles = NULL;
/*if (fn_xcal != NULL) printf(">>> fnx:   >%s<\n", *fn_xcal);
if (fn_ycal != NULL) printf(">>> fny:   >%s<\n", *fn_ycal);
if (fn_lcal != NULL) printf(">>> fnl:   >%s<\n", *fn_lcal);
if (fn_lut != NULL) printf(">>> fnlut: >%s<\n", *fn_lut);
printf(">>> cal: >%s<, mode: %d\n",kmclipm_get_cal_path(), kmclipm_cal_test_mode);*/
    return err;
}

/**
    @brief
        Get path for calibration files.

    @return path or NULL

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if @c path  is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT  if @c test_mode isn't TRUE or FALSE.
*/
const char* kmclipm_get_cal_path()
{
    KMCLIPM_TRY
    {
        if (!kmclipm_file_path_was_set) {
            strncpy(kmclipm_cal_file_path, "", 1);
            kmclipm_file_path_was_set = TRUE;
        }
    }
    KMCLIPM_CATCH
    {
    }

    return kmclipm_cal_file_path;
}

/*
cpl_image*      kmclipm_imagelist_get_stderr(
                        const cpl_imagelist *cube)
{
    cpl_image       *img        = NULL;
    const cpl_image *tmpimg     = NULL;
    float           *pimg       = NULL;
    const float     *ptmpimg    = NULL;
    int             ix          = 0,
                    iy          = 0,
                    iz          = 0,
                    nx          = 0,
                    ny          = 0,
                    nz          = 0;
    kmclipm_vector  **vec       = NULL;
    double        ss          = 0.;

    KMCLIPM_TRY
    {
        KMCLIPM_TRY_CHECK(cube != NULL,
                          CPL_ERROR_NULL_INPUT,
                          NULL,
                          "Not all input data provided!");

        nz = cpl_imagelist_get_size(cube);
        KMCLIPM_TRY_EXIT_IFN(
            tmpimg = cpl_imagelist_get_const(cube, 1));
        nx = cpl_image_get_size_x(tmpimg);
        ny = cpl_image_get_size_y(tmpimg);
        ss = sqrt(nz);
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        KMCLIPM_TRY_EXIT_IFN(
            vec = (kmclipm_vector**)cpl_malloc(nx*ny*sizeof(kmclipm_vector*)));

        for (ix = 0; ix < nx*ny; ix++) {
            vec[ix] = kmclipm_vector_new(nz);
        }
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        for (iz = 0; iz < nz; iz++) {
            KMCLIPM_TRY_EXIT_IFN(
                tmpimg = cpl_imagelist_get_const(cube, iz));
            KMCLIPM_TRY_EXIT_IFN(
                ptmpimg = cpl_image_get_data_const(tmpimg));
            for (ix = 0; ix < nx; ix++) {
                for (iy = 0; iy < ny; iy++) {
                    kmclipm_vector_set(vec[ix+iy*nx], iz, ptmpimg[ix+iy*nx]);
                }
            }
        }
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        KMCLIPM_TRY_EXIT_IFN(
            img = cpl_image_new(nx, ny, CPL_TYPE_FLOAT));
        KMCLIPM_TRY_EXIT_IFN(
            pimg = cpl_image_get_data(img));
        for (ix = 0; ix < nx; ix++) {
            for (iy = 0; iy < ny; iy++) {
                pimg[ix+iy*nx] = kmclipm_vector_get_stdev(vec[ix+iy*nx])/ss;
                KMCLIPM_TRY_CHECK_ERROR_STATE();
            }
        }
        KMCLIPM_TRY_CHECK_ERROR_STATE();
    }
    KMCLIPM_CATCH
    {
        cpl_image_delete(img); img = NULL;
    }

    for (ix = 0; ix < nx*ny; ix++) {
        kmclipm_vector_delete(vec[ix]); vec[ix] = NULL;
    }
    cpl_free(vec); vec = NULL;

    return img;
}
*/

/** @} */
