/* $Id: visir_utils.c,v 1.145 2012/02/03 14:23:33 llundin Exp $
 *
 * This file is part of the VISIR 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., 51 Franklin St, Fifth Floor, Boston, MA  02111-1307  USA
 */

/*
 * $Author: llundin $
 * $Date: 2012/02/03 14:23:33 $
 * $Revision: 1.145 $
 * $Name: visir-3_5_1 $
 */

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

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

#include <cpl.h>

/* TEMPORARY SUPPORT OF CPL 5.x */
#ifndef CPL_SIZE_FORMAT
#define CPL_SIZE_FORMAT "d"
#define cpl_size int
#endif
/* END TEMPORARY SUPPORT OF CPL 5.x */

#include "visir_utils.h"

#include "irplib_utils.h"

#include "visir_inputs.h"
#include "visir_pfits.h"
#include "visir_spc_distortion.h"

#include <string.h>
#include <math.h>
#include <assert.h>

/*-----------------------------------------------------------------------------
                                   Define
 -----------------------------------------------------------------------------*/

#define VISIR_BACKGD_START  76
#define VISIR_BACKGD_STOP   172

/*----------------------------------------------------------------------------*/
/**
 * @defgroup visir_utils Miscellaneous Utilities
 *
 * TBD
 */
/*----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
                        Private function prototypes
 -----------------------------------------------------------------------------*/


static double visir_great_circle_dist(double, double, double, double);
static double ra_hms2deg(int, int, double);
static double dec_hms2deg(int, int, double);

static const char * visir_get_capa(const cpl_propertylist *);
static double visir_hcycle_background(const irplib_framelist *, int, int);

#ifdef VISIR_MASK_HAS
static cpl_boolean visir_mask_has(const cpl_mask *, cpl_binary, int);
#endif


/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the wavelengths from a frame set
  @param    self   framelist
  @return   The array wavelengths or NULL on error

  FIXME: Pass a CPL table and insert into a column ...

 */
/*----------------------------------------------------------------------------*/
double * visir_utils_get_wls(const irplib_framelist * self)
{
    /* Get the number of files */
    const int   size = irplib_framelist_get_size(self);
    double    * wls = NULL;
    int         i;


    skip_if (0);

    skip_if(irplib_framelist_contains(self, VISIR_PFITS_DOUBLE_MONOC_POS,
                                      CPL_TYPE_DOUBLE, CPL_FALSE, 0.0));

    /* Allocate the output array */
    wls = cpl_malloc(size * sizeof(double));

    /* Get the wavelengths */
    for (i=0; i < size; i++) {
        const cpl_propertylist * plist
            = irplib_framelist_get_propertylist_const(self, i);

        wls[i] = visir_pfits_get_monoc_pos(plist);

        skip_if (0);
    }

    end_skip;

    if (cpl_error_get_code()) {
        cpl_free(wls);
        wls = NULL;
    }

    return wls;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Create an int image with 0 for the background and 1 for the disk 
  @param    nx      x size of the created image
  @param    ny      y size of the created image
  @param    x_pos   x position of the disk center (FITS convention)
  @param    y_pos   y position of the disk center (FITS convention)
  @param    radius  disk radius
  @return   the newly allocated image 
 */
/*----------------------------------------------------------------------------*/
cpl_image * visir_create_disk_intimage(
        int     nx, 
        int     ny, 
        int     x_pos, 
        int     y_pos, 
        int     radius) 
{
    cpl_image   *   intima;
    int         *   pintima;
    double          dist;
    int             i, j;

    /* Create the empty output image */
    intima = cpl_image_new(nx, ny, CPL_TYPE_INT);
    pintima = cpl_image_get_data_int(intima);

    /* Loop on the pixels */
    for (j=0;j<ny ; j++) {
        for (i=0;i<nx ; i++) {
            dist = (i+1-x_pos)*(i+1-x_pos)+(j+1-y_pos)*(j+1-y_pos);
            if (dist < radius*radius) {
                pintima[i+j*nx] = 1;
            } else {
                pintima[i+j*nx] = 0;
            }
        }
    }
    return intima;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Create an int image with 0 for the background and 1 for the ring 
  @param    nx      x size of the created image
  @param    ny      y size of the created image
  @param    x_pos   x position of the ring center (FITS convention)
  @param    y_pos   y position of the ring center (FITS convention)
  @param    radius1 ring small radius
  @param    radius2 ring big radius
  @return   the newly allocated image 
 */
/*----------------------------------------------------------------------------*/
cpl_image * visir_create_ring_intimage(
        int     nx, 
        int     ny, 
        int     x_pos, 
        int     y_pos, 
        int     radius1, 
        int     radius2)
{
    cpl_image   *   intima;
    int         *   pintima;
    double          dist;
    int             i, j;

    /* Create the empty output image */
    intima = cpl_image_new(nx, ny, CPL_TYPE_INT);
    pintima = cpl_image_get_data_int(intima);

    /* Loop on the pixels */
    for (j=0;j<ny ; j++) {
        for (i=0;i<nx ; i++) {
            dist = (i+1-x_pos)*(i+1-x_pos)+(j+1-y_pos)*(j+1-y_pos);
            if ((dist < radius2*radius2) && (dist > radius1*radius1)) {
                pintima[i+j*nx] = 1;
            } else {
                pintima[i+j*nx] = 0;
            }
        }
    }
    return intima;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the background noise with an iterative sigma clipping 
  @param    self     The image
  @param    pstdev   Set to the stdev of the noise and the background (optional)
  @return   the noise or a negative value on error.
  @note pstdev may be NULL.

  FIXME: This routine uses some numbers set from heuristics

 */
/*----------------------------------------------------------------------------*/
double visir_image_sigma_clip(const cpl_image * self, double * pstdev)
{
    const int      dimx = cpl_image_get_size_x(self);
    const int      dimy = cpl_image_get_size_y(self);
    const cpl_type type = cpl_image_get_type(self);
    /* Apply a 3x3 median filter to the input image  */
    /* FIXME: A wrap (around float/double) would be sufficient */
    cpl_image    * noise = cpl_image_new(dimx, dimy, type);
    cpl_mask     * bpm = NULL;
    cpl_mask     * kernel = cpl_mask_new(3, 3);
    const int      niterations = 5;
    const double   sigma = 3.0;
    const double   median_corr = 0.94;
    double         bgnoise = -1;
    int            i = -1;


    bug_if (0);

    bug_if(cpl_mask_not(kernel));
    bug_if(cpl_image_filter_mask(noise, self, kernel, CPL_FILTER_MEDIAN,
                                 CPL_BORDER_FILTER));

    /* (Inverted) Noise image */
    bug_if (cpl_image_subtract(noise, self));

    for (i=0; i < niterations ; i++) {
        /* Compute mean and stdev */
        cpl_stats * stats =
            cpl_stats_new_from_image(noise, CPL_STATS_MEAN | CPL_STATS_STDEV);

        const double mean  = cpl_stats_get_mean(stats);
        const double stdev = cpl_stats_get_stdev(stats);

        /* Set the thresholds */
        const double low_thresh  = mean - sigma * stdev;
        const double high_thresh = mean + sigma * stdev;


        cpl_stats_delete(stats); /* :-( */

        /* The previous thresholding may have left too few good pixels */
        skip_if (0);

        /* Identify where mean-sigma*stdev < noise < mean+sigma*stdev */
        bpm = cpl_mask_threshold_image_create(noise, low_thresh, high_thresh);

        bug_if (0);

        bug_if (cpl_mask_not(bpm));

        bug_if (cpl_image_reject_from_mask(noise, bpm));

        cpl_mask_delete(bpm);  /* :-( */
        bpm = NULL;

    }

    if (pstdev != NULL) {
        /* Compute the stdev of the noise and the background */

        cpl_stats * stats =
            cpl_stats_new_from_image(noise, CPL_STATS_MEAN | CPL_STATS_STDEV);

        /* Compute mean and stdev */
        bgnoise = cpl_stats_get_stdev(stats);
        *pstdev = cpl_image_get_median(self) - cpl_stats_get_mean(stats);

        cpl_stats_delete(stats);
    } else {
        /* Compute the stdev */
        bgnoise = cpl_image_get_stdev(noise);
    }

    bug_if(0);

    bgnoise /= median_corr;

    end_skip;

    if (cpl_error_get_code()) 
        cpl_msg_error(cpl_func, "Computation of background noise using sigma=%g"
                      " failed in iteration %d of %d", sigma, i+1, niterations);

    cpl_mask_delete(kernel);
    cpl_mask_delete(bpm);
    cpl_image_delete(noise);

    return bgnoise;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the background noise with an iterative sigma clipping 
  @param    self        The image
  @return   the noise or a negative value on error.
  @see visir_image_sigma_clip

  FIXME: Deprecate

 */
/*----------------------------------------------------------------------------*/
double visir_img_phot_sigma_clip(const cpl_image * self)
{

    const double noise = visir_image_sigma_clip(self, NULL);

    cpl_ensure(!cpl_error_get_code(), cpl_error_get_code(), noise);

    return noise;

}

/*----------------------------------------------------------------------------*/
/**
  @brief    Find the index of the star closest to (ra, dec)
  @param    v_ra     Vector of Right Ascensions (ra, dec) [degree]
  @param    v_dec    Vector of Declinations (ra, dec)  [degree]
  @param    ra       Right Ascension of position to search for
  @param    dec      Declination of ditto
  @param    maxdist  Maximum acceptable (non-negative) distance [degree]
  @param    pdist    Actual distance (if not NULL)
  @return   The index (starting from zero), or negative on error.

  The two vectors must be of identical length.

  pdist may be NULL.

  It is an error if no star is found.

 */
/*----------------------------------------------------------------------------*/
int visir_star_find(const cpl_vector * v_ra, const cpl_vector * v_dec,
                    double ra, double dec, double maxdist, double * pdist)
{
    const int    nra   = cpl_vector_get_size(v_ra);
    const int    ndec  = cpl_vector_get_size(v_dec);
    double       dmin = 0.0;  /* Avoid (false) uninit warning */
    int          minind = 0;
    int          i;


    /* Catch NULL input */
    cpl_ensure(nra  > 0,     cpl_error_get_code(), -2);
    cpl_ensure(ndec > 0,     cpl_error_get_code(), -3);

    /* It would be natural to use a cpl_bivector for the positions,
       but since CPL cannot ensure that a bivector comprises two vectors
       of the same length this would be pointless :-( */
    cpl_ensure(nra == ndec,  CPL_ERROR_INCOMPATIBLE_INPUT, -4);

    cpl_ensure(maxdist >= 0, CPL_ERROR_ILLEGAL_INPUT, -5);

    /* Find the index of the star closest to the given coordinate */
    for (i=0 ; i < nra ; i++) {
        const double rai  = cpl_vector_get(v_ra,  i);
        const double deci = cpl_vector_get(v_dec, i);
        const double gdist = visir_great_circle_dist(rai, deci, ra, dec);

        cpl_msg_debug(cpl_func, "DISTANCE (RAi,DECi)=(%g,%g) <=> "
                      "(RA,DEC)=(%g,%g): %g", rai, deci, ra, dec, gdist);

        if (i == 0 || gdist < dmin) {
            minind = i;
            dmin = gdist;
        }
    }

    if (pdist != NULL) *pdist = dmin;

    /* Check that it is close enough */
    if (dmin > maxdist) {
        cpl_msg_error(cpl_func, "Nearest standard star (%d of %d) at (RA,DEC)="
                      "(%g,%g) is too distant from (RA,DEC)=(%g, %g): %g > %g",
                      1+minind, nra, cpl_vector_get(v_ra,  minind),
                      cpl_vector_get(v_dec,  minind), ra, dec, dmin, maxdist);
        cpl_ensure(0, CPL_ERROR_DATA_NOT_FOUND, -1);
    }

    return minind;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Check and Convert RA and DEC
  @param    line     Text line available for error messages
  @param    ra_hh    hours of RA
  @param    ra_mm    minutes of RA
  @param    ra_ss    seconds of RA
  @param    isign    Sign of Declination ('+' or '-')
  @param    dec_hh   hours of DEC
  @param    dec_mm   minutes of DEC
  @param    dec_ss   seconds of DEC
  @param    jys      The array of Jansky values
  @param    njys     Number of Jansky values
  @param    pra      RA in degrees
  @param    pdec     DEC in degrees
  @return   CPL_ERROR_NONE iff the conversion went OK
  @note The function will assert() on NULL input or non-positive njys

 */
/*----------------------------------------------------------------------------*/
cpl_error_code visir_star_convert(const char * line, int ra_hh, int ra_mm,
                                  double ra_ss, char isign, int dec_hh,
                                  int dec_mm, double dec_ss,
                                  const double * jys, int njys,
                                  double * pra, double * pdec)
{

    double sign;
    int i;

    assert( line );
    assert( pra );
    assert( pdec );
    assert( jys );
    assert( njys > 0 );


    if (isign == '+')
      sign = 1.0;
    else if (isign == '-')
      sign = -1.0;
    else {
        cpl_msg_error(cpl_func, "Line has illegal declination-sign character "
                      "(%c): %s", isign, line);
        cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
    }

    if (ra_hh < 0) {
        cpl_msg_error(cpl_func, "Line has negative RA hh (%d): %s",
                      ra_hh, line);
        cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
    }
    if (ra_mm < 0) {
        cpl_msg_error(cpl_func, "Line has negative RA mm (%d): %s",
                      ra_hh, line);
        cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
    }
    if (ra_mm >= 60) {
        cpl_msg_error(cpl_func, "Line has too large RA mm (%d): %s ",
                      ra_mm, line);
        cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
    }
    if (ra_ss < 0) {
        cpl_msg_error(cpl_func, "Line has negative RA ss (%g): %s",
                      ra_ss, line);
        cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
    }
    if (ra_ss >= 60) {
        cpl_msg_error(cpl_func, "Line has too large RA ss (%g): %s ",
                      ra_ss, line);
        cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
    }

    if (dec_hh < 0) {
        cpl_msg_error(cpl_func, "Line has negative DEC hh (%d): %s",
                      dec_hh, line);
        cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
    }
    if (dec_mm < 0) {
        cpl_msg_error(cpl_func, "Line has negative DEC mm (%d): %s",
                      dec_hh, line);
        cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
    }
    if (dec_mm >= 60) {
        cpl_msg_error(cpl_func, "Line has too large DEC mm (%d): %s ",
                      dec_mm, line);
        cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
    }
    if (dec_ss < 0) {
        cpl_msg_error(cpl_func, "Line has negative DEC ss (%g): %s",
                      dec_ss, line);
        cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
    }
    if (dec_ss >= 60) {
        cpl_msg_error(cpl_func, "Line has too large DEC ss (%g): %s ",
                      dec_ss, line);
        cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
    }

    *pra = ra_hms2deg(ra_hh, ra_mm, ra_ss);
    if (*pra >= 360.0) {
        cpl_msg_error(cpl_func, "Line has too large RA (%g): %s ",
                      *pra, line);
        cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
    }

    *pdec = sign * dec_hms2deg(dec_hh, dec_mm, dec_ss);
    if (*pdec > 90.0) {
        cpl_msg_error(cpl_func, "Line has too large RA (%g): %s ",
                      *pdec, line);
        cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
    }
    if (*pdec < -90.0) {
        cpl_msg_error(cpl_func, "Line has too small RA (%g): %s ",
                      *pdec, line);
        cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
    }

    for (i=0; i < njys; i++) if (jys[i] <= 0.0) {
        cpl_msg_error(cpl_func,"Line has non-positive Jy value (%g) at %d: %s ",
                      jys[i], i+1, line);
        cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
    }

    return CPL_ERROR_NONE;

}

/*----------------------------------------------------------------------------*/
/**
  @brief    Create a table of positions of apertures of maximal flux
  @param    images List of images
  @param    label  The type of value to insert, FLUX or FWHM.
  @return   A newly created cpl_table with columns of doubles or NULL on error

  A table row is created for each image.
  The table labels are: X_POS, Y_POS and either FLUX or X_FWHM and Y_FWHM.
  Pixel-position (-1,-1) indicate an invalid table row.
  A non-positive flux leads to an invalid row.

 */
/*----------------------------------------------------------------------------*/
cpl_table * visir_table_new_xypos(const cpl_imagelist * images,
                                  const char * label)
{
    cpl_errorstate cleanstate = cpl_errorstate_get();
    const int    nsize = cpl_imagelist_get_size(images);
    double       psigmas[] = {5, 2, 1, 0.5}; /* Actually not modified */
    cpl_vector * sigmas = NULL;
    cpl_table  * self  = NULL;
    const int           nsigmas = sizeof(psigmas)/sizeof(double);
    int isflux = 0;
    int nfail, i;

    cpl_ensure(nsize > 0, cpl_error_get_code(), NULL);
    cpl_ensure(label,     CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(!strcmp(label, "FLUX") || !strcmp(label, "FWHM"),
               CPL_ERROR_UNSUPPORTED_MODE, NULL);

    self = cpl_table_new(nsize);

    skip_if (cpl_table_new_column(self, "X_POS", CPL_TYPE_DOUBLE));
    skip_if (cpl_table_new_column(self, "Y_POS", CPL_TYPE_DOUBLE));

    if (!strcmp(label,"FLUX")) {
        isflux = 1;
        skip_if (cpl_table_new_column(self, label,    CPL_TYPE_DOUBLE));
    } else {
        skip_if (cpl_table_new_column(self, "X_FWHM", CPL_TYPE_DOUBLE));
        skip_if (cpl_table_new_column(self, "Y_FWHM", CPL_TYPE_DOUBLE));
    }

    sigmas = cpl_vector_wrap(4, psigmas);
    skip_if (sigmas == NULL);

    cpl_msg_info(cpl_func, "Detecting apertures using %d sigma-levels "
                 "ranging from %g down to %g", nsigmas, psigmas[0],
                 psigmas[nsigmas-1]);

    /* Object detection */
    nfail = 0;
    for (i=0 ; i < nsize ; i++) {
        const cpl_image * image = cpl_imagelist_get_const(images, i);
        cpl_apertures   * apert;
        cpl_size          isigma;


        double posx  = -1;
        double posy  = -1;
        double fwhmx = -1;
        double fwhmy = -1;
        double flux  = -1;
        int iflux;

        skip_if (0);

        /* Find any apertures in each image */
        apert = cpl_apertures_extract(image, sigmas, &isigma);

        if (apert != NULL && cpl_error_get_code()) {
            /* FIX for DFS 2616 */
            cpl_msg_error(cpl_func, "cpl_apertures_extract() returned non-NULL "
                          "while setting the CPL error-state to '%s' at '%s'",
                          cpl_error_get_message(), cpl_error_get_where());
            cpl_msg_debug(cpl_func, "Deleting the spurious aperture list at %p:",
                          (void*)apert);
            if (cpl_msg_get_level() <= CPL_MSG_DEBUG)
                cpl_apertures_dump(apert, stdout);
            cpl_apertures_delete(apert);
            apert = NULL;
        }

        if (apert != NULL &&
            !irplib_apertures_find_max_flux(apert, &iflux, 1) &&
            cpl_apertures_get_flux(apert, iflux) > 0) {

            posx = cpl_apertures_get_centroid_x(apert, iflux);
            posy = cpl_apertures_get_centroid_y(apert, iflux);
            flux = cpl_apertures_get_flux(apert, iflux);
            if (!isflux)
                cpl_image_get_fwhm(image, (int)posx, (int)posy, &fwhmx, &fwhmy);

            cpl_msg_info(cpl_func, "Detected an aperture with flux=%g at "
                         "sigma=%g, at position: %g %g", flux,
                         psigmas[isigma], posx, posy);
    
        }

        if (apert == NULL || cpl_error_get_code()) {
            visir_error_reset("Aperture detection in image %d of %d failed",
                             i+1, nsize);
            nfail++;
        } else if (flux <= 0) {
            cpl_msg_warning(cpl_func, "Ignoring %d-pixel aperture %d (out of "
                            "%d) in file %d of %d with non-positive flux: %g",
                            (int)cpl_apertures_get_npix(apert, iflux), iflux,
                            (int)cpl_apertures_get_size(apert), i+1, nsize,
                            flux);
            nfail++;
        }

        cpl_apertures_delete(apert);
        apert = NULL;

        skip_if (cpl_table_set_double(self, "X_POS", i, posx));
        skip_if (cpl_table_set_double(self, "Y_POS", i, posy));

        if (isflux)
            skip_if (cpl_table_set_double(self, "FLUX",  i, flux));
        else {
            skip_if (cpl_table_set_double(self, "X_FWHM", i, fwhmx));
            skip_if (cpl_table_set_double(self, "Y_FWHM", i, fwhmy));
        }

    }

    /* Check if some detections were successful */
    if (nfail == nsize) {
        cpl_msg_error(cpl_func, "Aperture detection failed in all %d images",
                      nsize);
        visir_error_set(CPL_ERROR_DATA_NOT_FOUND);
        skip_if(1);
    }

    end_skip;

    cpl_vector_unwrap(sigmas);

    if (self && cpl_error_get_code()) {
        cpl_table_delete(self);
        self = NULL;
    }

    return self;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Find the (first) index with the minimum value
  @param    v  The vector
  @return   Index (starting with zero), or negative on error.

 */
/*----------------------------------------------------------------------------*/
int visir_vector_minpos(const cpl_vector * v)
{
    const double * x     = cpl_vector_get_data_const(v);
    const int      n     = cpl_vector_get_size(v);
    int minpos = 0;
    int i;

    cpl_ensure(x, CPL_ERROR_NULL_INPUT, -1);

    for (i = 1; i < n; i++) if (x[i] < x[minpos]) minpos = i;

    return minpos;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Set a bivector with values from an ASCII file
  @param    self     Bivector to set
  @param    stream   Input stream
  @return   CPL_ERROR_NONE or the relevant #_cpl_error_code_ on error
  @see cpl_bivector_read

  In addition to normal files, FIFO (see man mknod) are also supported.

  Possible #_cpl_error_code_ set in this function:
  - CPL_ERROR_NULL_INPUT if an input pointer is NULL
  - CPL_ERROR_FILE_IO if the file cannot be read
  - CPL_ERROR_BAD_FILE_FORMAT if the file contains no valid lines
 */
/*----------------------------------------------------------------------------*/
cpl_error_code visir_bivector_load(cpl_bivector * self, FILE * stream)
{
    cpl_vector * v1;
    cpl_vector * v2;
    int          np = 0;
    int          xsize, ysize;
    char         line[1024];

    cpl_ensure_code(self,   CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(stream, CPL_ERROR_NULL_INPUT);

    /* Create and fill the vectors */
    v1 = cpl_bivector_get_x(self);
    v2 = cpl_bivector_get_y(self);

    xsize = cpl_vector_get_size(v1);
    ysize = cpl_vector_get_size(v2);

    while (fgets(line, 1024, stream) != NULL) {
        double x, y;
        if (line[0] != '#' && sscanf(line, "%lg %lg", &x, &y) == 2) {
            /* Found new element-pair
               - increase vector sizes if necessary,
               - insert element at end and
               - increment size counter */
            if (np == xsize) {
                xsize *= 2;
                cpl_vector_set_size(v1, xsize);
            }
            if (np == ysize) {
                ysize *= 2;
                cpl_vector_set_size(v2, ysize);
            }
            cpl_vector_set(v1, np, x);
            cpl_vector_set(v2, np, y);
            np++;
        }
    }

    /* Check that the loop ended due to eof and not an error */
    cpl_ensure_code(!ferror(stream), CPL_ERROR_FILE_IO);

    /* Check that the file was not empty and set the size to its true value */
    if (np == 0 || cpl_vector_set_size(v1, np) || cpl_vector_set_size(v2, np)) {
        cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
    }

    return CPL_ERROR_NONE;

}

/*----------------------------------------------------------------------------*/
/**
  @brief   Find the pair of locations with the minimum distance
  @param   pras    Right ascension of locations [degrees]
  @param   pdecs   Declination of locations [degrees]
  @param   nloc    Number of locations
  @param   piloc1  First member of pair (0 for first)
  @param   piloc2  Second member of pair (0 for first), *piloc1 < *piloc2
  @return  Non-negative distance [degrees]
  @note The function will assert() on NULL input or non-positive nloc
  @see     http://en.wikipedia.org/wiki/Great-circle_distance (on 2005-10-23)

 */
/*----------------------------------------------------------------------------*/
double visir_star_dist_min(const double * pras, const double * pdecs, int nloc,
                           int * piloc1, int * piloc2)
{

    int i, j;
    double dmin = 180;


    assert( pras != NULL);
    assert( pdecs != NULL);
    assert( piloc1 != NULL);
    assert( piloc2 != NULL);
    assert( nloc > 0 );

    for (j = 0; j < nloc; j++) {
        for (i = 0; i < j; i++) {
            const double dist = visir_great_circle_dist(pras[i], pdecs[i],
                                                        pras[j], pdecs[j]);
            if (dist < dmin) {
                dmin = dist;
                *piloc1 = i;
                *piloc2 = j;
            }
            if (dist < VISIR_STAR_MAX_RADIUS)
                cpl_msg_warning(cpl_func,"The two stars (%d,%d) have a distance"
                                ": %g < %g", i, j, dist, VISIR_STAR_MAX_RADIUS);
        }
    }

    return dmin;
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Retag a framelist according to the given tagging function
  @param    self   Framelist with frames to retag
  @param    pftag  Function to create a new tag for one frame
  @param    pntags On success, number of new tags in framelist, otherwise undef
  @return   List of new tags or NULL on error
  @note (*pftag)() must return a newly allocated pointer. It must indicate an
        error by returning NULL and by setting a CPL error code.
        On error some of the the tags in the framelist may be modified.
        The integer parameter to (*pftag)() is its index in the framelist
        (starting with zero).

 */
/*----------------------------------------------------------------------------*/
const char ** visir_framelist_set_tag(irplib_framelist * self,
                                      char * (*pftag)(const cpl_frame *,
                                                      const cpl_propertylist *,
                                                      int),
                                      int *pntags)
{

    /* FIXME: Copied from NACO - move to irplib */

    const char ** taglist = NULL; /* Must be initialized due to realloc call */
    int iframe, size;

    cpl_ensure(!cpl_error_get_code(), cpl_error_get_code(), NULL);
    cpl_ensure(self   != NULL, CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(pftag  != NULL, CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(pntags != NULL, CPL_ERROR_NULL_INPUT, NULL);

    size = irplib_framelist_get_size(self);

    cpl_ensure(size > 0, CPL_ERROR_DATA_NOT_FOUND, NULL);

    *pntags = 0;

    for (iframe = 0; iframe < size ; iframe++) {
        cpl_frame  * frame = irplib_framelist_get(self, iframe);
        const cpl_propertylist * plist
            = irplib_framelist_get_propertylist_const(self, iframe);
        char       * tag;
        const char * newtag;
        int          i;


        /* This should really be an assert() */
        cpl_ensure(frame != NULL, CPL_ERROR_ILLEGAL_INPUT, NULL);
        cpl_ensure(plist != NULL, CPL_ERROR_ILLEGAL_INPUT, NULL);

        tag = (*pftag)(frame, plist, iframe);

        cpl_ensure(tag != NULL, cpl_error_get_code(), NULL);

        /* From this point on failures should not really happen */

        (void)cpl_frame_set_tag(frame, tag);
        cpl_free(tag);

        newtag = cpl_frame_get_tag(frame);

        cpl_ensure(!cpl_error_get_code(), cpl_error_get_code(), NULL);

        /* Compare the new tags with those of previous frames */
        for (i=0; i < *pntags; i++)
            if (strcmp(newtag, taglist[i]) == 0) break;

        if (i == *pntags) {
            /* The new tag is different from the previous ones
               - add it to the list */
            (*pntags)++;
            taglist = (const char **)cpl_realloc(taglist, *pntags *
                                                 sizeof(const char *));
            taglist[i] = newtag;
        }

    }

    return taglist;

}

/*----------------------------------------------------------------------------*/
/**
  @brief    Append the background to the propertylist as "ESO QC BACKGD MEAN"
  @param    self      Propertylist to append to
  @param    rawframes List of rawframes and their propertylists
  @param    icol1     First column to use or zero
  @param    icol2     Last column to use or zero
  @return   CPL_ERROR_NONE or the relevant CPL error code on error

 */
/*----------------------------------------------------------------------------*/
cpl_error_code visir_qc_append_background(cpl_propertylist * self,
                                          const irplib_framelist * rawframes,
                                          int icol1, int icol2)
{

    /* Compute the background values of the HCYCLE frames */
    const double bg_mean = visir_hcycle_background(rawframes, icol1, icol2);

    skip_if (0);

    bug_if (cpl_propertylist_append_double(self, "ESO QC BACKGD MEAN",
                                           bg_mean));

    end_skip;

    return cpl_error_get_code();

}


/*----------------------------------------------------------------------------*/
/**
  @brief    Append the capa value to the propertylist as "ESO QC CAPA"
  @param    self      Propertylist to append to
  @param    rawframes List of rawframes and their propertylists
  @note     It is a bug in the pipeline if this functions fails
  @return   CPL_ERROR_NONE or the relevant CPL error code on error

 */
/*----------------------------------------------------------------------------*/
cpl_error_code visir_qc_append_capa(cpl_propertylist * self,
                                    const irplib_framelist * rawframes)
{

    cpl_errorstate cleanstate = cpl_errorstate_get();
    const cpl_propertylist * plist
        = irplib_framelist_get_propertylist_const(rawframes, 0);
    const char             * capa;


    bug_if (0);

    capa = visir_get_capa(plist);

    if (cpl_error_get_code()) {
        visir_error_reset("Could not determine capa");
    } else {
        bug_if (cpl_propertylist_append_string(self, "ESO QC CAPA", capa));
    }

    end_skip;

    return cpl_error_get_code();

}

/*----------------------------------------------------------------------------*/
/**
  @brief    Append the filter name to the propertylist as "ESO QC FILTER"
  @param    self      Propertylist to append to
  @param    rawframes List of rawframes and their propertylists
  @return   CPL_ERROR_NONE or the relevant CPL error code on error

 */
/*----------------------------------------------------------------------------*/
cpl_error_code visir_qc_append_filter(cpl_propertylist * self,
                                      const irplib_framelist * rawframes)
{

    const cpl_propertylist * plist
        = irplib_framelist_get_propertylist_const(rawframes, 0);
    const char             * value = visir_pfits_get_filter(plist);


    skip_if (0);
 
    bug_if (cpl_propertylist_append_string(self, "ESO QC FILTER", value));

    end_skip;

    return cpl_error_get_code();

}

/*----------------------------------------------------------------------------*/
/**
  @brief    Append the exposure time  to the propertylist as "ESO QC EXPTIME"
  @param    self      Propertylist to append to
  @param    rawframes List of rawframes and their propertylists
  @return   CPL_ERROR_NONE or the relevant CPL error code on error

 */
/*----------------------------------------------------------------------------*/
cpl_error_code visir_qc_append_exptime(cpl_propertylist * self,
                                       const irplib_framelist * rawframes)
{

    const cpl_propertylist * plist
        = irplib_framelist_get_propertylist_const(rawframes, 0);

    /* Get the total exposure time */
    /* DIT */
    const double dit = visir_pfits_get_dit(plist);
    /* NDIT */
    const int ndit = visir_pfits_get_ndit(plist);
    /* NNOD */
    const int nnod = irplib_framelist_get_size(rawframes);
    /* Number of chopping cycles */
    const int ncycles = visir_pfits_get_chop_ncycles(plist);

    /* Exptime * 2 because of chopping */
    const double value = 2 * dit * ndit * nnod * ncycles;


    skip_if (0);
    
    if (value <= 0) {
        cpl_msg_error(cpl_func, "Illegal exposure time "
                      "(dit=%g:ndit=%d:ncycles=%d:nnod=%d): %g",
                      dit, ndit, ncycles, nnod, value);
        skip_if(1);
    }

    bug_if (cpl_propertylist_append_double(self, "ESO QC EXPTIME", value));

    end_skip;

    return cpl_error_get_code();

}

/**@}*/

/*----------------------------------------------------------------------------*/
/**
  @brief   Compute the great-circle distance between two points on a sphere
  @param   ra1    Right ascension of first point [degrees]
  @param   dec1   Declination of first point [degrees]
  @param   ra2    Right ascension of second point [degrees]
  @param   dec2   Declination of second point [degrees]
  @return  Non-negative distance [degrees].
  @see     http://en.wikipedia.org/wiki/Great-circle_distance (on 2005-10-23)

 */
/*----------------------------------------------------------------------------*/
static double visir_great_circle_dist(double ra1, double dec1,
                                      double ra2, double dec2)
{

  /* Convert all input from degrees to radian - and back for the result */
  const double dra  = sin( CPL_MATH_RAD_DEG * (ra2  - ra1 )/2.0 );
  const double ddec = sin( CPL_MATH_RAD_DEG * (dec2 - dec1)/2.0 );

  dec1 *= CPL_MATH_RAD_DEG;
  dec2 *= CPL_MATH_RAD_DEG;

  return 2.0 * asin(sqrt( ddec*ddec + cos(dec1)*cos(dec2)*dra*dra))
      * CPL_MATH_DEG_RAD;

}

/*----------------------------------------------------------------------------*/
/**
  @brief    Convert the RA from hh mm ss to degrees 
  @param    hh      hours
  @param    mm      minutes 
  @param    ss      seconds 
  @return   RA in degrees 

  An arc-hour is 15 degrees,
  60 arc-minutes is one arc-hour and
  60 arc-seconds is one arc-minute.
  
 */
/*----------------------------------------------------------------------------*/
static double ra_hms2deg(int hh, int mm, double ss)
{
    return 15.0 * dec_hms2deg(hh, mm, ss);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Convert the DEC from dd mm ss to degrees
  @param    dd      degrees
  @param    mm      minutes 
  @param    ss      seconds 
  @return   DEC in degrees

  60 arc-minutes is one degree and
  60 arc-seconds is one arc-minute.

 */
/*----------------------------------------------------------------------------*/
static double dec_hms2deg(int dd, int mm, double ss)
{
    return ((double)ss/60.0 + (double)mm)/60.0 + dd;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the background value from the HCYCLE frames
  @param    rawframes Input raw frames
  @param    icol1     First column to use or zero
  @param    icol2     Last column to use or zero
  @return   The mean value of all median values of all HCYCLE frames

  The function returns a negative number on error.

  Use zero to indicate the default column.

 */
/*----------------------------------------------------------------------------*/
static double visir_hcycle_background(const irplib_framelist * rawframes,
                               int icol1, int icol2)
{
    cpl_imagelist * iset = NULL;
    /* Get the number of files */
    const int       nfiles = irplib_framelist_get_size(rawframes);
    double          bgsum  = 0;
    double          bgmean = -1;
    int             nsum  = 0;
    int             i, j;

    
    skip_if (nfiles < 1);

    if (icol1 == 0) icol1 = VISIR_BACKGD_START;
    if (icol2 == 0) icol2 = VISIR_BACKGD_STOP;

    cpl_msg_info(cpl_func, "Computing Half-cycle background level from column %d "
                 "through %d", icol1, icol2);

    /* Loop on the hcycles images */
    for (i=0; i < nfiles; i++) {

        iset = visir_load_hcycle(rawframes, i);

        skip_if (0);

        for (j = 0; j < cpl_imagelist_get_size(iset) ; j++) {
            const double median =
                cpl_image_get_median_window(cpl_imagelist_get(iset, j),
                                            VISIR_BACKGD_START, icol1,
                                            VISIR_BACKGD_STOP, icol2);

            skip_if (0);

            if (median != median) {
                const cpl_frame * frame = irplib_framelist_get_const(rawframes,
                                                                     i);
                /* Some Comm. I data contains NaNs */
                cpl_msg_error(cpl_func, "Image window (%d, %d, %d, %d) "
                              "(image %d of %d) in %s (frame %d of %d) "
                              "has NaN median",
                              VISIR_BACKGD_START, icol1,
                              VISIR_BACKGD_STOP, icol2,
                              j+1, (int)cpl_imagelist_get_size(iset),
                              cpl_frame_get_filename(frame), i+1, nfiles);
                visir_error_set(CPL_ERROR_BAD_FILE_FORMAT);
                skip_if(1);
            }
            bgsum += median;
        }
        nsum += j;
        cpl_imagelist_delete(iset);
        iset = NULL;
    }

    /* Test if there are some HCYCLE frames */
    skip_if (nsum < 1);

    bgmean = bgsum / nsum;

    end_skip;

    cpl_imagelist_delete(iset);

    /* The background was requested to not include the offset correction */
    return bgmean - VISIR_HCYCLE_OFFSET;
}


/*----------------------------------------------------------------------------*/
/**
  @brief    get subpixel maximum by looking at neighbooring pixels
  @param    img          image
  @param    x            max x
  @param    y            max y
  @param    xsub         out, subpixel increment to x
  @param    ysub         out, subpixel increment to y
  @return CPL_ERROR_NONE iff OK, otherwise the relevant CPL error code.

 */
/*----------------------------------------------------------------------------*/
cpl_error_code
visir_get_subpixel_maxpos(const cpl_image * img, cpl_size x, cpl_size y,
                          double * xsub, double * ysub)
{
    int bad;
    const cpl_size nx = cpl_image_get_size_x(img);
    const cpl_size ny = cpl_image_get_size_y(img);

    *xsub = 0;
    *ysub = 0;
    if (x - 1 > 0 && x + 1 <= nx) {
        double sub[] = {
            cpl_image_get(img, x - 1, y, &bad),
            cpl_image_get(img, x - 0, y, &bad),
            cpl_image_get(img, x + 1, y, &bad),
        };
        if (!bad)
            *xsub = 0.5 * (sub[0] - sub[2])/(sub[0] - 2*sub[1] + sub[2]);
    }
    if (y - 1 > 0 && y + 1 <= ny) {
        double sub[] = {
            cpl_image_get(img, x, y - 1, &bad),
            cpl_image_get(img, x, y - 0, &bad),
            cpl_image_get(img, x, y + 1, &bad),
        };
        if (!bad)
            *ysub = 0.5 * (sub[0] - sub[2])/(sub[0] - 2*sub[1] + sub[2]);
    }

    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    cross correlate image against a template using fft
  @param    atemplate    template to correlate against
  @param    aimg         image to correlate against template
  @param    normalize    normalize images before correlating
  @param    xshift       out, x-shift of aimg against template
  @param    yshift       out, y-shift of aimg against template
  @return CPL_ERROR_NONE iff OK, otherwise the relevant CPL error code.

 */
/*----------------------------------------------------------------------------*/
cpl_error_code
visir_fftxcorrelate(const cpl_image * atemplate, const cpl_image * aimg,
                    cpl_boolean normalize, double * xshift, double * yshift)
{
    cpl_size Nx = cpl_image_get_size_x(aimg);
    cpl_size Ny = cpl_image_get_size_y(aimg);
    const cpl_size nsquare = abs(Nx-Ny);
    cpl_size Nxe = 2 * (Nx + (Ny > Nx ? nsquare : 0)) - 1;
    cpl_size Nye = 2 * (Ny + (Nx > Ny ? nsquare : 0)) - 1;
    cpl_size txshift, tyshift;
    double subx = 0, suby = 0;
    double * bzimg = cpl_calloc(Nxe * Nye, sizeof(double));
    double * bztemp = cpl_calloc(Nxe * Nye, sizeof(double));
    cpl_image * template = cpl_image_cast(atemplate, CPL_TYPE_DOUBLE);
    cpl_image * img = NULL;
    cpl_image * zimg = NULL;
    cpl_image * ztemp = NULL;
    cpl_image * fft1 = NULL;
    cpl_image * fft2 = NULL;
    cpl_image * res = NULL;

    zimg = cpl_image_wrap(Nxe, Nye, CPL_TYPE_DOUBLE, bzimg);
    ztemp = cpl_image_wrap(Nxe, Nye, CPL_TYPE_DOUBLE, bztemp);

    if (Nx > Ny)
        Ny += nsquare;
    else
        Nx += nsquare;
    /* FIXME: zero pads to square images due to DFS10988 */
    cpl_ensure_code(Nx == Ny, CPL_ERROR_ILLEGAL_INPUT);

    if (cpl_image_get_type(aimg) != CPL_TYPE_DOUBLE)
        img = cpl_image_cast(aimg, CPL_TYPE_DOUBLE);
    else
        img = cpl_image_duplicate(aimg);

    skip_if(img == NULL);

    cpl_image_fill_rejected(img, 0);
    cpl_image_fill_rejected(template, 0);

    /* normalize */
    if (normalize) {
        skip_if(cpl_image_subtract_scalar(img, cpl_image_get_mean(img)));
        skip_if(cpl_image_divide_scalar(img, cpl_image_get_stdev(img)));
        skip_if(cpl_image_subtract_scalar(template, cpl_image_get_mean(template)));
        skip_if(cpl_image_divide_scalar(template, cpl_image_get_stdev(template)));
    }

    /* zero pad */
    skip_if(cpl_image_copy(zimg, img, 1, 1));
    skip_if(cpl_image_copy(ztemp, template, Nx, Ny));

    fft1 = cpl_image_new(Nxe, Nye, CPL_TYPE_DOUBLE_COMPLEX);
    fft2 = cpl_image_new(Nxe, Nye, CPL_TYPE_DOUBLE_COMPLEX);
    res = cpl_image_new(Nxe, Nye, CPL_TYPE_DOUBLE);

    /* flip first image to row major
     * second image is already flipped as it is column major
     * so we have a fft correlation instead of a convolution */
    skip_if(cpl_image_flip(zimg, 1));
    skip_if(cpl_image_flip(zimg, 3));

    skip_if(cpl_fft_image(fft1, zimg, CPL_FFT_FORWARD));
    skip_if(cpl_fft_image(fft2, ztemp, CPL_FFT_FORWARD));

    /* correlate, no conjugation necessary due to flipping */
    skip_if(cpl_image_multiply(fft1, fft2));

    skip_if(cpl_fft_image(res, fft1, CPL_FFT_BACKWARD));

    skip_if(cpl_image_get_maxpos(res, &txshift, &tyshift));
    skip_if(visir_get_subpixel_maxpos(res, txshift, tyshift, &subx, &suby));
    *xshift = txshift - (Nx - 1) + subx;
    *xshift *= -1;
    *yshift = tyshift - (Ny - 1) + suby;
    *yshift *= -1;

    end_skip;

    cpl_image_delete(fft1);
    cpl_image_delete(fft2);
    cpl_image_delete(img);
    cpl_image_delete(template);
    cpl_image_delete(zimg);
    cpl_image_delete(ztemp);
    cpl_image_delete(res);

    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Find out the capa value from several keywords values
  @param    plist       property list to read from
  @return   A pointer to a static string
 */
/*----------------------------------------------------------------------------*/
static const char * visir_get_capa(const cpl_propertylist * plist)
{
    const char  * capa = "Pb with Capa";
    const char  * sval;
    double        mean;


    skip_if (0);


    /* Get the instrument mode */
    sval = visir_pfits_get_insmode(plist);
    skip_if (0);

    /* Identify the mode */
    if (!strcmp(sval, "IMG")) {
        /* Imaging mode */
        mean  = visir_pfits_get_volt1dcta9(plist);
        mean += visir_pfits_get_volt1dctb9(plist);
    } else if (!strcmp(sval, "SPC") || !strcmp(sval, "SPCIMG")) {
        /* Spectro mode */
        mean  = visir_pfits_get_volt2dcta9(plist);
        mean += visir_pfits_get_volt2dctb9(plist);
    } else
        skip_if (1);

    skip_if (0);

    mean *= 0.5;

    /* Compute Capa value */
    if (mean < 1.0) {
        capa = "Large Capa";
    } else if (mean > 4.5) {
        capa = "Small Capa";
    }

    end_skip;    

    return capa;
}

#ifdef VISIR_MASK_HAS
/*----------------------------------------------------------------------------*/
/**
  @brief    Determine if the mask has a sufficient number of good/bad pixels
  @param    self      The mask to search
  @param    value     The value to look for
  @param    ngood     The minimum required number of pixels with the given value
  @return   CPL_TRUE iff the mask has at least ngood good pixels
  @note The function always return CPL_TRUE when ngood is zero.

  Example of usage:
    @code

        if (!visir_mask_has(mask, CPL_BINARY_0, 1)) {
            // mask does not have a single good pixel,
            // i.e. all pixels are bad
        }

   @endcode

 */
/*----------------------------------------------------------------------------*/
static cpl_boolean visir_mask_has(const cpl_mask * self, cpl_binary value,
                                  int ngood)
{
    const cpl_binary * pself = cpl_mask_get_data_const(self);
    int                size  = cpl_mask_get_size_x(self)
                             * cpl_mask_get_size_y(self);
    int i;

    cpl_ensure(self,          CPL_ERROR_NULL_INPUT,          CPL_FALSE);
    cpl_ensure(ngood >= 0,    CPL_ERROR_ILLEGAL_INPUT,       CPL_FALSE);
    cpl_ensure(ngood <= size, CPL_ERROR_ACCESS_OUT_OF_RANGE, CPL_FALSE);
    cpl_ensure(value == CPL_BINARY_0 || value == CPL_BINARY_1,
               CPL_ERROR_INCOMPATIBLE_INPUT, CPL_FALSE);

    for (i = 0; i < ngood; i++) {
        /* Assume NULL is returned if size == 0 */
        const cpl_binary * ppos = memchr(pself, value, (size_t)size);
        if (ppos == NULL) break;

        size -= 1 + (int)(ppos - pself);
        pself = 1 + ppos;
    }

    return i == ngood ? CPL_TRUE : CPL_FALSE;
}
#endif
