/* $Id: naco_img_dark.c,v 1.75 2011-12-22 11:12:38 llundin Exp $
 *
 * This file is part of the NACO 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: 2011-12-22 11:12:38 $
 * $Revision: 1.75 $
 * $Name: not supported by cvs2svn $
 */

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

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

#include "naco_recipe.h"

/*-----------------------------------------------------------------------------
                            Defines
 -----------------------------------------------------------------------------*/

#define RECIPE_STRING "naco_img_dark"

/*-----------------------------------------------------------------------------
                            Private Functions prototypes
 -----------------------------------------------------------------------------*/

static cpl_error_code naco_img_dark_reduce(cpl_propertylist *,
                                           const irplib_framelist *,
                                           cpl_image **, cpl_mask **,
                                           cpl_mask **, cpl_mask **);

static cpl_error_code naco_img_dark_qc(cpl_propertylist *,
                                       cpl_propertylist *,
                                       const irplib_framelist *);

static cpl_error_code naco_img_dark_save(cpl_frameset *,
                                         const cpl_parameterlist *,
                                         const cpl_propertylist *,
                                         const cpl_propertylist *,
                                         const cpl_image *, const cpl_mask *,
                                         const cpl_mask *, const cpl_mask *,
                                         int, const irplib_framelist *);

static char * naco_img_dark_make_tag(const cpl_frame*,
                                     const cpl_propertylist *, int);

NACO_RECIPE_DEFINE(naco_img_dark, 
                   NACO_PARAM_REJBORD |                        
                   NACO_PARAM_HOT_LIM |                        
                   NACO_PARAM_COLD_LIM |                       
                   NACO_PARAM_DEV_LIM |                        
                   NACO_PARAM_NSAMPLES |                       
                   NACO_PARAM_HALFSIZE,
                   "Dark recipe",
                   "naco_img_dark -- NACO imaging dark recipe.\n"                       
                   "The files listed in the Set Of Frames (sof-file) "
                   "must be tagged:\n" 
                   "NACO-raw-file.fits " NACO_IMG_DARK_RAW "\n");

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

static struct {
    /* Inputs */
    int         rej_left;
    int         rej_right;
    int         rej_bottom;
    int         rej_top;
    double      hot_thresh;
    double      cold_thresh;
    double      dev_thresh;
    int         hsize;
    int         nsamples;
} naco_img_dark_config;

/*----------------------------------------------------------------------------*/
/**
 * @defgroup naco_img_dark   Dark
 */
/*----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
                                Functions code
 -----------------------------------------------------------------------------*/


/*----------------------------------------------------------------------------*/
/**
  @brief    The recipe data reduction part is implemented here 
  @param    framelist   the frames list
  @param    parlist     the parameters list
  @return   0 iff everything is ok
 */
/*----------------------------------------------------------------------------*/
static int naco_img_dark(cpl_frameset            * framelist,
                         const cpl_parameterlist * parlist)
{
    irplib_framelist* allframes = NULL;
    irplib_framelist* rawframes = NULL;
    const char     ** taglist   = NULL;
    const char      * rej_bord;
    irplib_framelist* f_one = NULL;
    cpl_imagelist   * i_one = NULL;
    cpl_image       * avg = NULL;
    cpl_mask        * hot = NULL;
    cpl_mask        * cold = NULL;
    cpl_mask        * dev = NULL;
    cpl_propertylist * qclist  = cpl_propertylist_new();
    cpl_propertylist * paflist = cpl_propertylist_new();
    int               nsets;
    int               i;
    int               nb_good = 0;


    /* Retrieve input parameters */
    rej_bord = naco_parameterlist_get_string(parlist, RECIPE_STRING, NACO_PARAM_REJBORD);
    skip_if (0);
    skip_if_ne(sscanf(rej_bord, "%d %d %d %d",
                      &naco_img_dark_config.rej_left,
                      &naco_img_dark_config.rej_right,
                      &naco_img_dark_config.rej_bottom,
                      &naco_img_dark_config.rej_top), 4,
               "number(s) in string parameter (%s): \"%s\"",
               CPL_XSTRINGIFY(NACO_PARAM_REJBORD), rej_bord);

    naco_img_dark_config.hot_thresh =
        naco_parameterlist_get_double(parlist, RECIPE_STRING, NACO_PARAM_HOT_LIM);
    naco_img_dark_config.dev_thresh =
        naco_parameterlist_get_double(parlist, RECIPE_STRING, NACO_PARAM_DEV_LIM);
    naco_img_dark_config.cold_thresh =
        naco_parameterlist_get_double(parlist, RECIPE_STRING, NACO_PARAM_COLD_LIM);
    naco_img_dark_config.hsize =
        naco_parameterlist_get_int(parlist, RECIPE_STRING, NACO_PARAM_HALFSIZE);
    naco_img_dark_config.nsamples =
        naco_parameterlist_get_int(parlist, RECIPE_STRING, NACO_PARAM_NSAMPLES);

    skip_if (0);

    /* Identify the RAW and CALIB frames in the input frameset */
    skip_if (naco_dfs_set_groups(framelist));

    allframes = irplib_framelist_cast(framelist);
    skip_if(allframes == NULL);

    rawframes = irplib_framelist_extract(allframes, NACO_IMG_DARK_RAW);
    skip_if(rawframes == NULL);
    irplib_framelist_empty(allframes);

    skip_if(irplib_framelist_load_propertylist_all(rawframes, 0, "^(" 
                                                   IRPLIB_PFITS_REGEXP_RECAL "|"
                                                   NACO_PFITS_REGEXP_DARK "|"
                                                   NACO_PFITS_REGEXP_DARK_PAF
                                                   ")$", CPL_FALSE));

    taglist = naco_framelist_set_tag(rawframes, naco_img_dark_make_tag, &nsets);
    skip_if(taglist == NULL);

    cpl_msg_info(cpl_func, "Identified %d setting(s) in %d frames",
                 nsets, irplib_framelist_get_size(rawframes));

    /* Extract settings and reduce each of them */
    for (i=0 ; i < nsets ; i++) {

        /* Reduce data set nb i */
        f_one = irplib_framelist_extract(rawframes, taglist[i]);

        /* Reset the tag */
        skip_if(irplib_framelist_set_tag_all(f_one, NACO_IMG_DARK_RAW));

        cpl_msg_info(cpl_func, "Reducing frame set %d of %d (size=%d) with "
                     "setting: %s", i+1, nsets,
                     irplib_framelist_get_size(f_one), taglist[i]);

        skip_if (f_one == NULL);
        
        /* At least 2 frames required */
        if (irplib_framelist_get_size(f_one) < 2) {
            cpl_msg_warning(cpl_func, "Setting %d skipped (Need at least 2 "
                            "frames)", i+1);
            irplib_framelist_delete(f_one);
            f_one = NULL;
            continue;
        }

        skip_if(naco_img_dark_reduce(qclist, f_one, &avg, &hot, &cold, &dev));

        skip_if(naco_img_dark_qc(qclist, paflist, f_one));

        /* Save the products */
        skip_if (naco_img_dark_save(framelist, parlist, qclist, paflist,
                                    avg, hot, cold, dev, i+1, f_one));

        nb_good++;

        cpl_image_delete(avg);
        cpl_mask_delete(hot);
        cpl_mask_delete(cold);
        cpl_mask_delete(dev);
        irplib_framelist_delete(f_one);
        cpl_propertylist_empty(qclist);
        cpl_propertylist_empty(paflist);
        avg   = NULL;
        cold  = NULL;
        hot   = NULL;
        dev   = NULL;
        f_one = NULL;
    }

    skip_if (nb_good == 0);

    end_skip;

    cpl_imagelist_delete(i_one);
    cpl_free(taglist);
    cpl_image_delete(avg);
    cpl_mask_delete(hot);
    cpl_mask_delete(cold);
    cpl_mask_delete(dev);
    irplib_framelist_delete(f_one);
    irplib_framelist_delete(allframes);
    irplib_framelist_delete(rawframes);
    cpl_propertylist_delete(qclist);
    cpl_propertylist_delete(paflist);

    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    The recipe data reduction of frames with one setting
  @param    qclist    List of QC parameters
  @param    f_one     the list of frames with one setting
  @param    pavg       the averaged image
  @param    phot       the hot pixel mask
  @param    pcol       the cold pixel mask
  @param    pdev       the deviant pixel mask
  @return   0 iff everything is ok
  @note On error the modifiable images/masks may have been allocated.

 */
/*----------------------------------------------------------------------------*/
static cpl_error_code naco_img_dark_reduce(cpl_propertylist * qclist,
                                           const irplib_framelist * f_one,
                                           cpl_image ** pavg, cpl_mask ** phot,
                                           cpl_mask ** pcold, cpl_mask ** pdev)
{

    cpl_image * dark = NULL;
    cpl_image * diff  = NULL;
    char      * ron_key = NULL;
    double      rms;
    double      lower, upper;
    double      dark_med;
    double      mean;
    int         ndevpix;
    cpl_size    zone[4];
    int         coldpix_nb;
    int         hotpix_nb;
    int nfiles;
    int i;

    skip_if (f_one == NULL);

    nfiles = irplib_framelist_get_size(f_one);

    skip_if (nfiles < 2);

    skip_if (irplib_framelist_contains(f_one, "NAXIS1",
                                       CPL_TYPE_INT, CPL_TRUE, 0.0));
   
    skip_if (irplib_framelist_contains(f_one, "NAXIS2",
                                       CPL_TYPE_INT, CPL_TRUE, 0.0));
 
    for (i=0 ; i < nfiles ; i++) {
        const cpl_frame        * frame = irplib_framelist_get_const(f_one, i);
        const char             * name  = cpl_frame_get_filename(frame);

        cpl_image_delete(diff);
        diff = dark;
        irplib_check(dark = cpl_image_load(name, CPL_TYPE_FLOAT, 0, 0),
                     "Could not load FITS-image from %s", name);

        if (i == 0) {
            const int nx = cpl_image_get_size_x(dark);
            const int ny = cpl_image_get_size_y(dark);

            zone[0] = naco_img_dark_config.rej_left+1;
            zone[1] = nx - naco_img_dark_config.rej_right;
            zone[2] = naco_img_dark_config.rej_bottom+1;
            zone[3] = ny - naco_img_dark_config.rej_top;

            *pavg = cpl_image_duplicate(dark);
            skip_if(*pavg == NULL);
        } else {
            const cpl_propertylist * plist
                = irplib_framelist_get_propertylist_const(f_one, i-1);
            const int    ndit = naco_pfits_get_ndit(plist);
            const char   ron_format[] = "ESO QC RON%d";
            double       ron;

            skip_if(0);

            irplib_ensure(ndit > 0, CPL_ERROR_ILLEGAL_INPUT,
                          NACO_PFITS_INT_NDIT " must be positive, not %d",
                           ndit);

            skip_if(cpl_image_subtract(diff, dark));

            /* Compute the read-out noise */
            irplib_check(cpl_flux_get_noise_window(diff, zone,
                                                   naco_img_dark_config.hsize,
                                                   naco_img_dark_config.nsamples,
                                                   &rms, NULL),
                         "Cannot compute the RON for difference between images "
                          "%d and %d", i, i+1);

            /* Normalise the RON with NDIT */
            ron = rms * sqrt(ndit/2.0);

            /* Add QC parameters */
            cpl_free(ron_key);
            ron_key = cpl_sprintf(ron_format, i);

            bug_if(ron_key == NULL);

            skip_if(cpl_propertylist_append_double(qclist, ron_key, ron));

            /* Sum up the darks */
            skip_if(cpl_image_add(*pavg, dark));

        }

    }
    cpl_image_delete(dark);
    dark = NULL;

    mean = cpl_image_get_mean(diff);

    /* Use the rms of the difference of the last two images to
       create the deviant pixel map */
    lower = mean - rms * naco_img_dark_config.dev_thresh;
    upper = mean + rms * naco_img_dark_config.dev_thresh;
    cpl_mask_delete(*pdev);
    irplib_check(*pdev = cpl_mask_threshold_image_create(diff, lower, upper),
                 "Cannot compute the deviant pixel map");
    cpl_image_delete(diff);
    diff = NULL;
    
    skip_if (cpl_mask_not(*pdev));
    ndevpix = cpl_mask_count(*pdev);
    skip_if (0);
    
    /* Average it to the master dark */
    skip_if(cpl_image_divide_scalar(*pavg, (double)nfiles));

    /* Compute median-rms of the central part of the dark  */
    dark_med = cpl_image_get_median_window(*pavg, zone[0], zone[2], zone[1],
                                           zone[3]);

    irplib_check (cpl_flux_get_noise_window(*pavg, zone,
                                            naco_img_dark_config.hsize,
                                            naco_img_dark_config.nsamples,
                                            &rms, NULL),
                  "Cannot compute the RON of the master dark");

    lower = dark_med - rms * naco_img_dark_config.cold_thresh;
    upper = dark_med + rms * naco_img_dark_config.hot_thresh;

    /* Create the cold pixel map */
    cpl_mask_delete(*pcold);
    irplib_check(*pcold = cpl_mask_threshold_image_create(*pavg, -FLT_MAX,
                                                          lower),
                 "Cannot compute the cold pixel map");
    coldpix_nb = cpl_mask_count(*pcold);
    skip_if (0);

    /* Create the hot pixel map */
    cpl_mask_delete(*phot);
    irplib_check(*phot = cpl_mask_threshold_image_create(*pavg, upper, DBL_MAX),
                 "Cannot compute the hot pixel map");
    hotpix_nb = cpl_mask_count(*phot);
    skip_if (0);

    /* Add QC parameters */

    skip_if(cpl_propertylist_append_double(qclist, "ESO QC DARKMED", dark_med));
    skip_if(cpl_propertylist_append_int(qclist, "ESO QC NBCOLPIX", coldpix_nb));
    skip_if(cpl_propertylist_append_int(qclist, "ESO QC NBHOTPIX", hotpix_nb));
    skip_if(cpl_propertylist_append_int(qclist, "ESO QC NBDEVPIX", ndevpix));

    end_skip;

    cpl_image_delete(dark);
    cpl_image_delete(diff);
    cpl_free(ron_key);

    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Append the QC parameters to the propertylist
  @param    qclist    List of QC parameters
  @param    paflist   List of PAF parameters
  @param    rawframes List of rawframes and their propertylists
  @return   0 iff everything is ok
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code naco_img_dark_qc(cpl_propertylist       * qclist,
                                       cpl_propertylist       * paflist,
                                       const irplib_framelist * rawframes)
{

    const cpl_propertylist * reflist
        = irplib_framelist_get_propertylist_const(rawframes, 0);
    const char               pafcopy[] = "^(" NACO_PFITS_REGEXP_DARK_PAF ")$";


    bug_if (0);

    bug_if (cpl_propertylist_copy_property_regexp(paflist, reflist, pafcopy,
                                                  0));
    bug_if (cpl_propertylist_append(paflist, qclist));

    bug_if (cpl_propertylist_copy_property_regexp(qclist, reflist, "^("
                                                  IRPLIB_PFITS_REGEXP_RECAL 
                                                  ")$", 0));

    end_skip;

    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Save the dark recipe products on disk
  @param    set_tot The total input frame set to register the products
  @param    parlist The input list of parameters
  @param    qclist  List of QC parameters
  @param    paflist List of PAF parameters
  @param    avg     the average image
  @param    hot     the hot pixels map
  @param    cold    the cold pixels map
  @param    dev     the deviant pixels map (can be NULL)
  @param    set_nb  the current setting number
  @param    f_one   the frame set for the current setting
  @return   0 iff everything is ok
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code naco_img_dark_save(cpl_frameset            * set_tot,
                                         const cpl_parameterlist * parlist,
                                         const cpl_propertylist  * qclist,
                                         const cpl_propertylist  * paflist,
                                         const cpl_image         * avg,
                                         const cpl_mask          * hot,
                                         const cpl_mask          * cold,
                                         const cpl_mask          * dev,
                                         int                       set_nb,
                                         const irplib_framelist  * f_one)
{
    cpl_frameset * set_one = irplib_frameset_cast(f_one);
    cpl_image    * image   = NULL;
    char         * filename = NULL;


    bug_if (0);

    /* Write the average image */
    filename = cpl_sprintf(RECIPE_STRING "_set%02d_avg" CPL_DFS_FITS,
                              set_nb);
    skip_if (irplib_dfs_save_image(set_tot, parlist, set_one, avg,
                               CPL_BPP_IEEE_FLOAT, RECIPE_STRING,
                               NACO_IMG_DARK_AVG, qclist, NULL, naco_pipe_id,
                               filename));

    /* Write the hotpixel map */
    image = cpl_image_new_from_mask(hot);
    bug_if(0);

    cpl_free(filename);
    filename = cpl_sprintf(RECIPE_STRING "_set%02d_hotpix" CPL_DFS_FITS,
                              set_nb);
    skip_if (irplib_dfs_save_image(set_tot, parlist, set_one, image,
                               CPL_BPP_8_UNSIGNED, RECIPE_STRING, NACO_IMG_DARK_HOT,
                               qclist, NULL, naco_pipe_id, filename));

    /* Write the coldpixel map */
    cpl_image_delete(image);
    image = cpl_image_new_from_mask(cold);
    bug_if(0);

    cpl_free(filename);
    filename = cpl_sprintf(RECIPE_STRING "_set%02d_coldpix" CPL_DFS_FITS,
                              set_nb);
    skip_if (irplib_dfs_save_image(set_tot, parlist, set_one, image,
                               CPL_BPP_8_UNSIGNED, RECIPE_STRING,
                               NACO_IMG_DARK_COLD, qclist, NULL, naco_pipe_id,
                               filename));

    /* Write the deviant pixel map */
    cpl_image_delete(image);
    image = cpl_image_new_from_mask(dev);
    bug_if(0);

    cpl_free(filename);
    filename = cpl_sprintf(RECIPE_STRING "_set%02d_devpix" CPL_DFS_FITS,
                              set_nb);
    skip_if (irplib_dfs_save_image(set_tot, parlist, set_one, image,
                               CPL_BPP_8_UNSIGNED, RECIPE_STRING, NACO_IMG_DARK_DEV,
                               qclist, NULL, naco_pipe_id, filename));
    cpl_image_delete(image);
    image = NULL;

#ifdef NACO_SAVE_PAF
    /* THE PAF FILE FOR QC PARAMETERS */

    /* PRO.CATG */
    bug_if(cpl_propertylist_append_string(paflist, CPL_DFS_PRO_CATG,
                                          NACO_IMG_DARK_AVG));

    cpl_free(filename);
    filename = cpl_sprintf(RECIPE_STRING "_set%02d" CPL_DFS_PAF, set_nb);
    skip_if (cpl_dfs_save_paf("NACO", RECIPE_STRING, paflist, filename));
#else
    bug_if(paflist == NULL);
#endif

    end_skip;

    cpl_image_delete(image);
    cpl_frameset_delete(set_one);
    cpl_free(filename);

    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Create a string suitable for frame comparison
  @param    self   Frame to create the new tag from
  @param    plist  The propertylist of the frame
  @param    dummy  A non-negative number (required in the API, but unused here)
  @return   Comparison string or NULL on error
  @note The comparison string must be deallocated with cpl_free().

 */
/*----------------------------------------------------------------------------*/
static char * naco_img_dark_make_tag(const cpl_frame* self,
                                     const cpl_propertylist * plist, int dummy)
{

    char       * tag = NULL;
    const char * mode;
    const char * name;
    double       etime;
    int          irom;


    skip_if (cpl_error_get_code());

    skip_if(self  == NULL);
    skip_if(plist == NULL);
    skip_if(dummy < 0); /* Avoid warning of unused variable */

    /* exposure time */
    etime = naco_pfits_get_exptime(plist);
    skip_if(cpl_error_get_code());

    /* readout mode */
    irom = naco_pfits_get_rom(plist);
    skip_if(cpl_error_get_code());


    /* detector mode */
    mode = naco_pfits_get_mode(plist);
    skip_if(cpl_error_get_code());

    /* Compare the camera */
    name = naco_pfits_get_opti7_name(plist);
    skip_if(cpl_error_get_code());

    tag = cpl_sprintf("%s:%s:%d:%.5f", name, mode, irom,
                                        etime);
    bug_if(tag == NULL);

    end_skip;

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

    return tag;

}
