/* $Id: hdrl_utils.c,v 1.2 2013-10-16 11:31:07 cgarcia Exp $
 *
 * This file is part of the HDRL 
 * Copyright (C) 2012,2013 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

/*
 * $Author: cgarcia $
 * $Date: 2013-10-16 11:31:07 $
 * $Revision: 1.2 $
 * $Name: not supported by cvs2svn $
 */

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

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

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 500 /* posix 2001, mkstemp */
#endif

#include "hdrl_utils.h"
#include "hdrl_elemop.h"
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <math.h>

/*-----------------------------------------------------------------------------
                                   Static
 -----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/**
  @defgroup hdrl_utils   UTILITIES Module
  
  This module contain various utilities functions that might be used in several
  of the HDRL modules
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @brief	Get the pipeline copyright and license
  @return   The copyright and license string

  The function returns a pointer to the statically allocated license string.
  This string should not be modified using the returned pointer.
 */
/*----------------------------------------------------------------------------*/
const char * hdrl_get_license(void)
{
    const char  *   hdrl_license =
        "This file is part of the HDRL Instrument Pipeline\n"
        "Copyright (C) 2012,2013 European Southern Observatory\n"
        "\n"
        "This program is free software; you can redistribute it and/or modify\n"
        "it under the terms of the GNU General Public License as published by\n"
        "the Free Software Foundation; either version 2 of the License, or\n"
        "(at your option) any later version.\n"
        "\n"
        "This program is distributed in the hope that it will be useful,\n"
        "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
        "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
        "GNU General Public License for more details.\n"
        "\n"
        "You should have received a copy of the GNU General Public License\n"
        "along with this program; if not, write to the Free Software\n"
        "Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, \n"
        "MA  02110-1301  USA";

    return hdrl_license;
}

/*-----------------------------------------------------------------------------
                Rectangular Region Parameters Definition
 -----------------------------------------------------------------------------*/
typedef struct {
    HDRL_PARAMETER_HEAD;
    cpl_size    llx ;
    cpl_size    lly ;
    cpl_size    urx ;
    cpl_size    ury ;
} hdrl_rect_region_parameter;
/* Parameter type */
static hdrl_parameter_typeobj hdrl_rect_region_parameter_type = {
    (hdrl_alloc *)&cpl_malloc,      /* fp_alloc */
    (hdrl_free *)&cpl_free,         /* fp_free */
    NULL,                           /* fp_destroy */
    sizeof(hdrl_rect_region_parameter), /* obj_size */
};

/*----------------------------------------------------------------------------*/
/**
  @brief    Creates Rect Region Parameters object
  @param    llx
  @param    lly
  @param    urx
  @param    ury
  @return   the Rect Region parameters
 */
/*----------------------------------------------------------------------------*/
hdrl_parameter * hdrl_rect_region_parameter_create(
        cpl_size    llx,
        cpl_size    lly,
        cpl_size    urx,
        cpl_size    ury)
{
    hdrl_rect_region_parameter * p = (hdrl_rect_region_parameter *)
               hdrl_parameter_new(&hdrl_rect_region_parameter_type);
    p->llx = llx ;
    p->lly = lly ;
    p->urx = urx ;
    p->ury = ury ;
    return (hdrl_parameter *)p;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Update Rect Region Parameters object
  @param    rect_region     The region to update
  @param    llx
  @param    lly
  @param    urx
  @param    ury
  @return   the error code in case of error or CPL_ERROR_NONE
 */
/*----------------------------------------------------------------------------*/
cpl_error_code hdrl_rect_region_parameter_update(
        hdrl_parameter  *   rect_region,
        cpl_size            llx,
        cpl_size            lly,
        cpl_size            urx,
        cpl_size            ury)
{
    hdrl_rect_region_parameter * p = (hdrl_rect_region_parameter *)rect_region ;
    p->llx = llx ;
    p->lly = lly ;
    p->urx = urx ;
    p->ury = ury ;
    return hdrl_rect_region_parameter_verify(rect_region, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Check that the parameter is hdrl_rect_region parameter
  @return   the parameter to check
 */
/*----------------------------------------------------------------------------*/
cpl_boolean hdrl_rect_region_parameter_check(const hdrl_parameter * self)
{
    return hdrl_parameter_check_type(self, &hdrl_rect_region_parameter_type);
}

/*-----------------------------------------------------------------------------
                        Rect Region Parameters Accessors
 -----------------------------------------------------------------------------*/
cpl_size hdrl_rect_region_get_llx(const hdrl_parameter * p) 
{
    cpl_ensure(p, CPL_ERROR_NULL_INPUT, -1) ;
    return ((hdrl_rect_region_parameter *)p)->llx;
}
cpl_size hdrl_rect_region_get_lly(const hdrl_parameter * p) 
{
    cpl_ensure(p, CPL_ERROR_NULL_INPUT, -1) ;
    return ((hdrl_rect_region_parameter *)p)->lly;
}
cpl_size hdrl_rect_region_get_urx(const hdrl_parameter * p) 
{
    cpl_ensure(p, CPL_ERROR_NULL_INPUT, -1) ;
    return ((hdrl_rect_region_parameter *)p)->urx;
}
cpl_size hdrl_rect_region_get_ury(const hdrl_parameter * p) 
{
    cpl_ensure(p, CPL_ERROR_NULL_INPUT, -1) ;
    return ((hdrl_rect_region_parameter *)p)->ury;
}
/*----------------------------------------------------------------------------*/
/**
  @brief    Verify basic correctness of the parameters
  @param    param   rect region parameters
  @param    max_x   max value for upper x bound, set to < 0 to skip check
  @param    max_y   max value for upper y bound, set to < 0 to skip check
  @return   CPL_ERROR_NONE if everything is ok, an error code otherwise
 */
/*----------------------------------------------------------------------------*/
cpl_error_code hdrl_rect_region_parameter_verify(
        const hdrl_parameter    *   param,
        const cpl_size              max_x,
        const cpl_size              max_y)
{
    const hdrl_rect_region_parameter * param_loc =
        (hdrl_rect_region_parameter *)param ;

    cpl_error_ensure(param != NULL, CPL_ERROR_NULL_INPUT,
            return CPL_ERROR_NULL_INPUT, "NULL Input Parameters");
    cpl_error_ensure(hdrl_rect_region_parameter_check(param),
            CPL_ERROR_ILLEGAL_INPUT, return CPL_ERROR_ILLEGAL_INPUT, 
            "Expected Rect Region parameter") ;
    cpl_error_ensure(param_loc->llx >= 1 && param_loc->lly >= 1 &&
            param_loc->urx >= 1 && param_loc->ury >= 1, CPL_ERROR_ILLEGAL_INPUT,
            return CPL_ERROR_ILLEGAL_INPUT,
            "Coordinates must be strictly positive");

    cpl_error_ensure(param_loc->urx >= param_loc->llx, CPL_ERROR_ILLEGAL_INPUT,
                     return CPL_ERROR_ILLEGAL_INPUT,
                     "urx (%ld) must be larger equal than llx (%ld)",
                     (long)param_loc->urx, (long)param_loc->llx);
    cpl_error_ensure(param_loc->ury >= param_loc->lly, CPL_ERROR_ILLEGAL_INPUT,
                     return CPL_ERROR_ILLEGAL_INPUT,
                     "ury (%ld) must be larger equal than lly (%ld)",
                     (long)param_loc->ury, (long)param_loc->lly);
    if (max_x > 0)
        cpl_error_ensure(param_loc->urx <= max_x, CPL_ERROR_ILLEGAL_INPUT,
                         return CPL_ERROR_ILLEGAL_INPUT,
                         "urx %zu larger than maximum %zu",
                         (size_t)param_loc->urx, (size_t)max_x);
    if (max_y > 0)
        cpl_error_ensure(param_loc->urx <= max_x, CPL_ERROR_ILLEGAL_INPUT,
                         return CPL_ERROR_ILLEGAL_INPUT,
                         "ury %zu larger than maximum %zu",
                         (size_t)param_loc->ury, (size_t)max_y);
    return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    wrap negative or zero entries around full image size
  @param    region  rect region to wrap
  @param    nx   image size in x, nx is added to negative or zero entries
  @param    ny   image size in y, ny is added to negative or zero entries
  @return  cpl_error_code

  allows reverse indexing: 0 would be nx, -2 would be nx - 2 etc
  Wrapping is based in FITS convention: 1 first pixel, nx last pixel inclusive
 */
/*----------------------------------------------------------------------------*/
cpl_error_code hdrl_rect_region_wrap(
        hdrl_parameter    *     rect_region,
        const cpl_size          nx,
        const cpl_size          ny)
{
    hdrl_rect_region_parameter * rr_loc =
        (hdrl_rect_region_parameter *)rect_region ;

    cpl_error_ensure(rect_region != 0, CPL_ERROR_NULL_INPUT,
            return CPL_ERROR_NULL_INPUT, "region input must not be NULL");
    cpl_error_ensure(hdrl_rect_region_parameter_check(rect_region),
            CPL_ERROR_ILLEGAL_INPUT, return CPL_ERROR_ILLEGAL_INPUT,
            "Expected Rect Region parameter") ;

    if (nx > 0 && rr_loc->llx < 1) rr_loc->llx += nx;
    if (ny > 0 && rr_loc->lly < 1) rr_loc->lly += ny;
    if (nx > 0 && rr_loc->urx < 1) rr_loc->urx += nx;
    if (ny > 0 && rr_loc->ury < 1) rr_loc->ury += ny;

    return hdrl_rect_region_parameter_verify(rect_region, nx, ny);
}

/* ---------------------------------------------------------------------------*/
/**
  @brief Create parameter list for hdrl_rect_region
  @param full_prefix    prefix for parameter name
  @param alias_prefix   prefix for cli alias 
  @param name_prefix    prefix for the parameter names (can be "")
  @param context        context of parameter
  @param defaults       default parameters values
  @return cpl_parameterlist
 
  Creates a parameterlist with one parameter per point in a rectangle
 */
/* ---------------------------------------------------------------------------*/
cpl_parameterlist * hdrl_rect_region_parameter_create_parlist(
        const char              *   full_prefix,
        const char              *   alias_prefix,
        const char              *   name_prefix,
        const char              *   context,
        const hdrl_parameter    *   defaults)
{
    cpl_ensure(full_prefix && context && alias_prefix && name_prefix &&defaults,
            CPL_ERROR_NULL_INPUT, NULL);
    const char          *   full_sep = strlen(full_prefix) > 0 ? "." : "";
    const char          *   alias_sep = strlen(alias_prefix) > 0 ? "." : "";
    cpl_parameterlist   *   parlist = cpl_parameterlist_new();

    /* --prefix.llx */
    hdrl_setup_vparameter(parlist, full_prefix, full_sep, name_prefix,
                         alias_prefix, alias_sep, "llx", context,
                         "Lower left x pos. (FITS) defining the region",
                         CPL_TYPE_INT, hdrl_rect_region_get_llx(defaults));
    /* --prefix.lly */
    hdrl_setup_vparameter(parlist, full_prefix, full_sep, name_prefix,
                         alias_prefix, alias_sep, "lly", context,
                         "Lower left y pos. (FITS) defining the region",
                         CPL_TYPE_INT, hdrl_rect_region_get_lly(defaults));

    /* --prefix.urx */
    hdrl_setup_vparameter(parlist, full_prefix, full_sep, name_prefix,
                         alias_prefix, alias_sep, "urx", context,
                         "Upper right x pos. (FITS) defining the region",
                         CPL_TYPE_INT, hdrl_rect_region_get_urx(defaults));

    /* --prefix.ury */
    hdrl_setup_vparameter(parlist, full_prefix, full_sep, name_prefix,
                         alias_prefix, alias_sep, "ury", context,
                         "Upper right y pos. (FITS) defining the region",
                         CPL_TYPE_INT, hdrl_rect_region_get_ury(defaults));

    if (cpl_error_get_code()) {
        cpl_parameterlist_delete(parlist);
        return NULL;
    }

    return parlist;
}

/* ---------------------------------------------------------------------------*/
/**
 @brief parse parameterlist for rectangle parameters
 @param parlist    parameter list to parse
 @param prefix     prefix of parameter name
 @see   hdrl_rect_get_parlist()
 @return A newly allocated hdrl_rect_region parameter or NULL in error case
    
 The returned object must be deallocated with hdrl_parameter_delete()
 parameterlist should have been created with hdrl_rect_get_parlist or have the 
 same name hierachy
 */
/* ---------------------------------------------------------------------------*/
hdrl_parameter * hdrl_rect_region_parameter_parse_parlist(
        const cpl_parameterlist *   parlist,
        const char              *   prefix,
        const char              *   name_prefix)
{
    cpl_size llx, lly, urx, ury ;
    cpl_error_ensure(prefix && parlist, CPL_ERROR_NULL_INPUT,
            return NULL, "NULL Input Parameters");
    char * name;
    const char * sep = strlen(prefix) > 0 ? "." : "";
    const char * points[] = {"llx", "lly", "urx", "ury"};
    cpl_size * dest[] = {&llx, &lly, &urx, &ury};
    for (size_t i = 0; i < 4; i++) {
        name = cpl_sprintf("%s%s%s%s", prefix, sep, name_prefix, points[i]);
        const cpl_parameter * par = cpl_parameterlist_find_const(parlist, name);
        *(dest[i]) = cpl_parameter_get_int(par);
        cpl_free(name);
    }
    return hdrl_rect_region_parameter_create(llx, lly, urx, ury) ;
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief return file descriptor of a temporary file
 * @param dir     directory prefix to place the file in, may be NULL
 * @param unlink  unlink the file from the filesystem immediately
 * @return file descriptor or -1 on error
 * @note if dir is NULL or not writable it tries to create a file in following
 *       directories in decreasing order of preference:
 *       $TMPDIR, /var/tmp, /tmp, $PWD
 */
/* ---------------------------------------------------------------------------*/
int hdrl_get_tempfile(const char * dir, cpl_boolean unlink)
{
    /* options in decreasing preference */
    const char * tmpdirs[] = {
        /* user override */
        getenv("TMPDIR"),
        /* large file temp */
        "/var/tmp/",
        /* small file temp, may be tmpfs */
        "/tmp/"
    };
    const size_t ndirs = sizeof(tmpdirs) / sizeof(tmpdirs[0]);
    const char * tmpdir = NULL;

    if (dir && access(dir, W_OK) == 0) {
        tmpdir = dir;
    }
    else {
        for (size_t i = 0; i < ndirs; i++) {
            if (tmpdirs[i] && access(tmpdirs[i], W_OK) == 0) {
                tmpdir = tmpdirs[i];
                break;
            }
        }
    }

    {
        /* try $PWD if no tmpdir found */
        char * template = tmpdir ? cpl_sprintf("%s/hdrl_tmp_XXXXXX", tmpdir) :
                                   cpl_strdup("hdrl_tmp_XXXXXX");
        int fd = mkstemp(template);
        if (fd == -1) {
            cpl_error_set_message(cpl_func, CPL_ERROR_FILE_IO,
                                  "Temporary file creation failed: %s",
                                  strerror(errno));
            cpl_free(template);
            return -1;
        }

        cpl_msg_debug(cpl_func, "Created tempfile %s", template);

        if (unlink) {
            remove(template);
        }
        cpl_free(template);

        return fd;
    }
}
/*----------------------------------------------------------------------------*/
/**
  @internal
  @brief    compress an image to a vector removing bad pixels
  @param    source     image to compress
  @param    bpm        optional bpm to use instead of the one in the image
  @return  cpl_vector or NULL if no good pixels or error
  @note    vector can't have size 0 so NULL is returned if all pixels are bad
 */
/*----------------------------------------------------------------------------*/
cpl_vector * hdrl_image_to_vector(
        const cpl_image     *   source, 
        const cpl_mask      *   bpm)
{
    cpl_ensure(source != NULL, CPL_ERROR_NULL_INPUT, NULL);
    cpl_vector * vec_source = NULL;
    /* only cast if required, extra copying of non double data is still
     * faster than a loop with cpl_image_get */
    cpl_image * d_img = cpl_image_get_type(source) == CPL_TYPE_DOUBLE ?
        (cpl_image *)source : cpl_image_cast(source, CPL_TYPE_DOUBLE);

    const cpl_size naxis1 = cpl_image_get_size_x(source);
    const cpl_size naxis2 = cpl_image_get_size_y(source);

    /* get raw data */
    const double * restrict sdata = cpl_image_get_data_double_const(d_img);
    const cpl_binary * restrict bpmd = NULL;

    /* allocate output buffer */
    double * restrict ddata = cpl_malloc(naxis1 * naxis2 *
                                         sizeof(ddata[0]));
    long j = 0;

    if (bpm)
        bpmd = cpl_mask_get_data_const(bpm);
    else if (cpl_image_get_bpm_const(source) != NULL)
        bpmd = cpl_mask_get_data_const(cpl_image_get_bpm_const(source));

    if (bpmd == NULL) {
        memcpy(ddata, sdata, naxis1 * naxis2 * sizeof(ddata[0]));
        j = naxis1 * naxis2;
    }
    else {
        /* copy only good pixels */
        for (long i = 0; i < naxis1 * naxis2; i++) {
            if (bpmd[i] == CPL_BINARY_0) {
                ddata[j] = sdata[i];
                j++;
            }
        }
    }

    assert(j == naxis1 * naxis2 -
           (bpm ? cpl_mask_count(bpm) : cpl_image_count_rejected(source)));

    if (j > 0) {
        vec_source = cpl_vector_wrap(j, ddata);
    }
    else {
        cpl_free(ddata);
    }

    if (d_img != source) cpl_image_delete(d_img);

    return vec_source;
}


/*----------------------------------------------------------------------------*/
/**
  @internal
  @brief    compress an imagelist to a vector via z axis, removing bad pixels
  @param    list     imagelist to compress
  @param    x        x coordinate to compress over (FITS convention)
  @param    y        y coordinate to compress over (FITS convention)
  @return  cpl_vector or NULL if no good pixels or error
  @note    vector can't have size 0 so NULL is returned if all pixels are bad
 */
/*----------------------------------------------------------------------------*/
cpl_vector * hdrl_imagelist_to_vector(const cpl_imagelist * list,
                                     const cpl_size x,
                                     const cpl_size y)
{
    const long nz  = list ? cpl_imagelist_get_size(list) : -1;
    cpl_vector * vec = NULL;
    double * restrict ddata = NULL;
    long j = 0;

    cpl_ensure(list != NULL, CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(nz > 0, CPL_ERROR_ILLEGAL_INPUT, NULL);
    cpl_ensure(x > 0, CPL_ERROR_ACCESS_OUT_OF_RANGE, NULL);
    cpl_ensure(y > 0, CPL_ERROR_ACCESS_OUT_OF_RANGE, NULL);

    {
        const cpl_image  * img = cpl_imagelist_get_const(list, 0);
        const cpl_size nx = cpl_image_get_size_x(img);
        const cpl_size ny = cpl_image_get_size_y(img);
        cpl_ensure(x <= nx, CPL_ERROR_ACCESS_OUT_OF_RANGE, NULL);
        cpl_ensure(y <= ny, CPL_ERROR_ACCESS_OUT_OF_RANGE, NULL);
    }

    ddata = cpl_malloc(nz * sizeof(ddata[0]));

    for (long k = 0; k < nz; k++) {
        int rej;
        const cpl_image * img = cpl_imagelist_get_const(list, k);
        cpl_image * d_img = cpl_image_get_type(img) == CPL_TYPE_DOUBLE ?
            (cpl_image *)img : cpl_image_cast(img, CPL_TYPE_DOUBLE);

        double v = cpl_image_get(img, x, y, &rej);
        if (!rej) {
            ddata[j] = v;
            j++;
        }

        if (d_img != img) cpl_image_delete(d_img);
    }

    if (j > 0) {
        vec = cpl_vector_wrap(j, ddata);
    }
    else {
        cpl_free(ddata);
    }

    return vec;
}

/**
 * @brief
 *   Computes the mean of an image and propagates the associated error
 *
 * @internal
 * @param  ima      The image where to calculate the mean
 * @param  err      The associated error image (optional, can also be NULL)
 * @param  location function pointer to calculate location (mean)
 * @param  error    function pointer to calculate error of location
 * @param  mean     The calculated mean
 * @param  meanerr  The propagated error (if no error is passed it is set to NAN)
 *
 * @return A CPL error code
 *
 */
static cpl_error_code
hdrl_calc_location_image_(
    const cpl_image * ima,
    const cpl_image * err,
    double(*location)(const cpl_image * img),
    double(*error)(const cpl_image * err, const cpl_size contrib),
    double * mean,
    double * meanerr)
{
    const cpl_size contribution = hdrl_get_image_good_npix(ima);

    if (contribution == 0) {
        *mean = NAN;
        *meanerr = NAN;
        return cpl_error_get_code();
    }

    *mean = location(ima);

    if (!err) {
        /* No error propagation done */
        *meanerr = NAN;
    }
    else {
        const cpl_mask  * loc_mask = cpl_image_get_bpm_const(ima);
        cpl_image * loc_error = NULL;
        cpl_ensure_code(cpl_image_get_size_x(ima) == cpl_image_get_size_x(err),
                        CPL_ERROR_INCOMPATIBLE_INPUT);
        cpl_ensure_code(cpl_image_get_size_y(ima) == cpl_image_get_size_y(err),
                        CPL_ERROR_INCOMPATIBLE_INPUT);

        loc_error = cpl_image_cast(err, CPL_TYPE_DOUBLE);

        if (loc_mask) {
            cpl_image_reject_from_mask(loc_error, loc_mask);
        }

        *meanerr = error(loc_error, contribution);

        cpl_image_delete(loc_error);
    }

    return cpl_error_get_code();
}


/* ---------------------------------------------------------------------------*/
/**
 * @brief propagation error of mean of image with errors
 *
 * @internal
 * @param err            input errors
 * @param contribution   number of good pixels
 *
 * @return error
 */
/* ---------------------------------------------------------------------------*/
static double calc_error_mean(const cpl_image * err,
                              const cpl_size contribution)
{
    return sqrt(cpl_image_get_sqflux(err)) / contribution;
}


/* ---------------------------------------------------------------------------*/
/**
 * @brief propagation error of median of image with errors
 *
 * @param err            input errors
 * @param contribution   number of good pixels
 *
 * @return error
 *
 *  The error is the error of a mean scaled by the sqrt of the statistical
 *  efficiency (pi / 2) of the median for normal distributed data
 */
/* ---------------------------------------------------------------------------*/
static double calc_error_median(const cpl_image * err,
                                const cpl_size contribution)
{
    if (contribution <= 2) {
        return calc_error_mean(err, contribution);
    }
    else {
        return sqrt(CPL_MATH_PI / 2.) * calc_error_mean(err, contribution);
    }
}


/**
 * @brief
 *   Computes the mean of an image and propagates the associated error
 *
 * @param  ima     The image where to calculate the mean
 * @param  err     The associated error image (optional, can also be NULL)
 * @param  mean    The calculated mean
 * @param  meanerr The propagated error (if no error is passed it is set to NAN)
 *
 * @return A CPL error code
 *
 */
cpl_error_code hdrl_calc_mean_image(const cpl_image * ima,
                                    const cpl_image * err, double * mean,
                                    double * meanerr)
{
    return hdrl_calc_location_image_(ima, err,
                                     cpl_image_get_mean, calc_error_mean,
                                     mean, meanerr);
}


/**
 * @brief
 *   Computes the median of an image and propagates the associated error
 *
 * @param  ima       The image where to calculate the median
 * @param  err       The associated error image (optional, can also be NULL)
 * @param  median    The calculated median
 * @param  medianerr The propagated error (if no error is passed it is set to NAN)
 *
 * @return A CPL error code
 *
 */
cpl_error_code hdrl_calc_median_image( const cpl_image * ima,
                                       const cpl_image * err, double * median,
                                       double * medianerr)
{
    return hdrl_calc_location_image_(ima, err,
                                     cpl_image_get_median, calc_error_median,
                                     median, medianerr);
}

/**
 * @brief
 *   Computes the weighted mean of an image and propagates the associated error
 *
 * @param  ima      The image where to calculate the weighted mean
 * @param  err    The associated error image (mandatory)
 * @param  wmean    The calculated weighted mean
 * @param  wmeanerr The propagated error
 *
 * @return A CPL error code
 *
 * @doc
 * weighted mean and associated error are computed with standard formulae
 *
 * \f$
 *   x_{mean}=\frac{(\sum_{i}^{n} w_{i} \cdot x_{i})} { \sum_{i}^{n} w_{i} }
 * \f$
 *
 * \f$
 *   \sigma_{x}=\frac{ 1 } { \sqrt{  \sum_{i}^{n} w_{i}^{2} } }
 * \f$
 */
cpl_error_code hdrl_calc_weighted_mean_image( const cpl_image * ima,
                                      const cpl_image * err, double * wmean,
                                      double * wmeanerr)
{

    cpl_ensure_code(cpl_image_get_size_x(ima) == cpl_image_get_size_x(err),
                    CPL_ERROR_INCOMPATIBLE_INPUT);
    cpl_ensure_code(cpl_image_get_size_y(ima) == cpl_image_get_size_y(err),
                    CPL_ERROR_INCOMPATIBLE_INPUT);


    const cpl_size contribution = hdrl_get_image_good_npix(ima);

    if (contribution == 0) {
        *wmean = NAN;
        *wmeanerr = NAN;
        return cpl_error_set_message(cpl_func, CPL_ERROR_NULL_INPUT,
                        "All pixels are flagged as bad");
    }

    /* (\Sum_i^n w_i * x_i) / (\Sum_i^n w_i) */
    /* 1 / \sqrt(\Sum_i^n w_i^2) */

    cpl_image * ima_loc = cpl_image_cast(ima, CPL_TYPE_DOUBLE);
    cpl_image * err_loc = cpl_image_cast(err, CPL_TYPE_DOUBLE);

    const cpl_mask * mask=cpl_image_get_bpm_const(ima);
    if(mask != NULL){
        cpl_image_reject_from_mask(err_loc, mask);
    }

    cpl_image_power(err_loc, -2);
    cpl_image_multiply(ima_loc, err_loc);
    double sum_weights = cpl_image_get_flux(err_loc);
    double sum_ima_weights = cpl_image_get_flux(ima_loc);
    cpl_image_delete(ima_loc);
    cpl_image_delete(err_loc);

    *wmean = sum_ima_weights / sum_weights;
    *wmeanerr = 1 / sqrt(sum_weights);
    return cpl_error_get_code();

}


/* ---------------------------------------------------------------------------*/
/**
 * @brief scale a imagelist using scaling factors in a vector
 *
 * @param scale      vector containing scaling factors, the first element
 *                   defines the value to scale to
 * @param scale_e    errors of the scaling factors
 * @param scale_type scale type, additive or multiplicative
 * @param data       imagelist to be scaled
 * @param errors     errors of imagelist to be scaled
 *
 * If scale_type is HDRL_SCALE_ADDITIVE the difference of each value in the
 * scaling vector is subtracted from the first element of the vector and added
 * to each image in the imagelist.
 *
 * for i in vector.size:
 *   data[i] += vector[0] - vector[i]
 *
 * If scale_type is HDRL_SCALE_MULTIPLICATIVE the quotient of each value in
 * the scaling vector to the first element of the vector is multiplied
 * to each image in the imagelist.
 * If an element of the scaling vector is zero the image will have all its
 * pixels marked as bad in its bad pixel map.
 *
 * for i in vector.size:
 *   data[i] *= vector[0] / vector[i]
 *
 * The errors are propagated using error propagation of first order
 * and assuming no correlations between the values.
 */
/* ---------------------------------------------------------------------------*/
cpl_error_code
hdrl_normalize_imagelist_by_vector(const cpl_vector * scale,
                                   const cpl_vector * scale_e,
                                   const hdrl_scale_type scale_type,
                                   cpl_imagelist * data,
                                   cpl_imagelist * errors)
{
    cpl_ensure_code(scale, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(scale_e, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(data, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(errors, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(cpl_vector_get_size(scale) == cpl_imagelist_get_size(data),
                    CPL_ERROR_ILLEGAL_INPUT);
    cpl_ensure_code(cpl_vector_get_size(scale_e) == cpl_vector_get_size(scale),
                    CPL_ERROR_ILLEGAL_INPUT);
    cpl_ensure_code(cpl_imagelist_get_size(errors) ==
                    cpl_imagelist_get_size(data), CPL_ERROR_ILLEGAL_INPUT);

    for (size_t i = 1; i < (size_t)cpl_imagelist_get_size(data); i++) {
        const double dfirst = cpl_vector_get(scale, 0);
        const double efirst = cpl_vector_get(scale_e, 0);
        cpl_image * dimg = cpl_imagelist_get(data, i);
        cpl_image * eimg = cpl_imagelist_get(errors, i);
        if (scale_type == HDRL_SCALE_ADDITIVE) {
            double dscale_o = cpl_vector_get(scale, i);
            double escale_o =  cpl_vector_get(scale_e, i);

            /* dscale = dfirst - dscale_o */
            double dscale = dfirst;
            double escale = efirst;
            hdrl_elemop_sub(&dscale, &escale, 1, &dscale_o, &escale_o, 1, NULL);

            /* dimg = dimg + dscale */
            hdrl_elemop_image_add_scalar(dimg, eimg, dscale, escale);
        }
        else if (scale_type == HDRL_SCALE_MULTIPLICATIVE) {
            double dscale_o = cpl_vector_get(scale, i);
            double escale_o = cpl_vector_get(scale_e, i);

            if (dscale_o == 0.) {
                cpl_msg_warning(cpl_func, "scale factor of image %zu is "
                                "not a number", i);
                cpl_image_add_scalar(dimg, NAN);
                cpl_image_add_scalar(eimg, NAN);
                cpl_image_reject_value(dimg, CPL_VALUE_NAN);
                cpl_image_reject_value(eimg, CPL_VALUE_NAN);
                continue;
            }

            /* dscale = dfirst / dscale_o */
            double dscale = dfirst;
            double escale = efirst;
            hdrl_elemop_div(&dscale, &escale, 1, &dscale_o, &escale_o, 1, NULL);

            /* dimg = dimg * dscale */
            hdrl_elemop_image_mul_scalar(dimg, eimg, dscale, escale);
        }
        else {
            return cpl_error_set_message(cpl_func, CPL_ERROR_UNSUPPORTED_MODE,
                                         "Unsupported scale type");
        }

        if (cpl_error_get_code())
            break;
    }

    return cpl_error_get_code();
}


/* ---------------------------------------------------------------------------*/
/**
 * @brief scale a imagelist using scaling images
 *
 * @param scale      imagelist containing scaling factors, the first element
 *                   defines the value to scale to
 * @param scale_e    errors of the scaling factors
 * @param scale_type scale type, additive or multiplicative
 * @param data       imagelist to be scaled
 * @param errors     errors of imagelist to be scaled
 *
 * If scale_type is HDRL_SCALE_ADDITIVE the difference of each value in the
 * scaling imagelist is subtracted from the first element of the vector and
 * added to each image in the imagelist.
 *
 * for i in vector.size:
 *   data[i] += scale[0] - scale[i]
 *
 * If scale_type is HDRL_SCALE_MULTIPLICATIVE the quotient of each value in
 * the scaling vector to the first element of the imagelist is multiplied
 * to each image in the imagelist.
 * If an element of the scaling vector is zero the image will have all its
 * pixels marked as bad in its bad pixel map.
 *
 * for i in vector.size:
 *   data[i] *= scale[0] / scale[i]
 *
 * The errors are propagated using error propagation of first order
 * and assuming no correlations between the values.
 */
/* ---------------------------------------------------------------------------*/
cpl_error_code
hdrl_normalize_imagelist_by_imagelist(const cpl_imagelist * scale,
                                      const cpl_imagelist * scale_e,
                                      const hdrl_scale_type scale_type,
                                      cpl_imagelist * data,
                                      cpl_imagelist * errors)
{
    cpl_ensure_code(scale, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(scale_e, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(data, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(errors, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(cpl_imagelist_get_size(scale) ==
                    cpl_imagelist_get_size(data), CPL_ERROR_ILLEGAL_INPUT);
    cpl_ensure_code(cpl_imagelist_get_size(scale_e) ==
                    cpl_imagelist_get_size(scale), CPL_ERROR_ILLEGAL_INPUT);
    cpl_ensure_code(cpl_imagelist_get_size(errors) ==
                    cpl_imagelist_get_size(data), CPL_ERROR_ILLEGAL_INPUT);

    for (size_t i = 1; i < (size_t)cpl_imagelist_get_size(data); i++) {
        cpl_image * dscale =
            cpl_image_duplicate(cpl_imagelist_get_const(scale, 0));
        cpl_image * escale =
            cpl_image_duplicate(cpl_imagelist_get_const(scale_e, 0));
        cpl_image * dimg = cpl_imagelist_get(data, i);
        cpl_image * eimg = cpl_imagelist_get(errors, i);
        const cpl_image * dscale_o = cpl_imagelist_get_const(scale, i);
        const cpl_image * escale_o = cpl_imagelist_get_const(scale_e, i);

        if (scale_type == HDRL_SCALE_ADDITIVE) {
            /* dscale = dfirst - dscale_o */
            hdrl_elemop_image_sub_image(dscale, escale, dscale_o, escale_o);

            /* dimg = dimg + dscale */
            hdrl_elemop_image_add_image(dimg, eimg, dscale, escale);
        }
        else if (scale_type == HDRL_SCALE_MULTIPLICATIVE) {
            /* dscale = dfirst / dscale_o */
            hdrl_elemop_image_div_image(dscale, escale, dscale_o, escale_o);

            /* dimg = dimg * dscale */
            hdrl_elemop_image_mul_image(dimg, eimg, dscale, escale);
        }
        else {
            cpl_image_delete(dscale);
            cpl_image_delete(escale);
            return cpl_error_set_message(cpl_func, CPL_ERROR_UNSUPPORTED_MODE,
                                         "Unsupported scale type");
        }

        cpl_image_delete(dscale);
        cpl_image_delete(escale);

        if (cpl_error_get_code())
            break;
    }

    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief   compute photon count error in [ADU]
  @param   ima_data in [ADU]
  @param   gain detector's gain in [e- / ADU]
  @param   ron  detector's read out noise in [ADU]
  @param   ima_errs output error image in [ADU]
  @return  cpl_error_code
  @note ima_errs need to be deallocated
        ima_data must contain the photon counts with no offsets
        this usually means the image must be overscan and bias corrected
        Then the shot noise can be calculated from the poissonian distribution
        as sqrt(electron-counts). To this (transformed back into ADUs) the
        readout noise is added in quadrature.
  @doc
  error is computed with standard formula

  \f$ err_{ADU} = \sqrt{ \frac{ counts }{ gain } + ron^{ 2 } } \f$

  If an image value is negative the associated error is set to RON
 */
/*----------------------------------------------------------------------------*/
cpl_error_code
hdrl_detector_shotnoise_model(const cpl_image* ima_data, const double gain,
                                  const double ron, cpl_image ** ima_errs)
{
    cpl_ensure_code(ima_data, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(ima_errs, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(gain > 0., CPL_ERROR_ILLEGAL_INPUT);
    cpl_ensure_code(ron > 0., CPL_ERROR_ILLEGAL_INPUT);

    *ima_errs = cpl_image_duplicate(ima_data);
    /* set negative values (= zero measurable electrons) to read out noise */
    cpl_image_threshold(*ima_errs, 0., INFINITY, ron, ron);

    /* err_ADU = sqrt(counts/gain + ron * ron)*/

    cpl_image_divide_scalar(*ima_errs, gain);
    cpl_image_add_scalar(*ima_errs, ron * ron);
    cpl_image_power(*ima_errs, 0.5);

    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief   compute error in [ADU]
  @param   ima_data in [ADU]
  @param   ron  detector's read out noise in [ADU]
  @param   ima_errs output error image in [ADU]
  @return  cpl_error_code
  @note ima_errs need to be deallocated
        The returned error image is filled by the RON in ADUs
 */
/*----------------------------------------------------------------------------*/
cpl_error_code
hdrl_detector_shotnoise_model_bias(const cpl_image* ima_data, const double ron,
                                   cpl_image ** ima_errs)
{
    cpl_ensure_code(ima_data, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(ima_errs, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(ron > 0., CPL_ERROR_ILLEGAL_INPUT);

    /* Create new image initialised to 0 */
    *ima_errs = cpl_image_new(cpl_image_get_size_x(ima_data),
                              cpl_image_get_size_y(ima_data), CPL_TYPE_DOUBLE);

    if (cpl_image_get_bpm_const(ima_data))
        cpl_image_reject_from_mask(*ima_errs,
                                   cpl_image_get_bpm_const(ima_data));

    cpl_image_add_scalar(*ima_errs, ron);

    return cpl_error_get_code();
}

/**@}*/
