/* $Id: naco_spc_combine.c,v 1.28 2011-12-22 11:21:03 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:21:03 $
 * $Revision: 1.28 $
 * $Name: not supported by cvs2svn $
 */

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

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

#include "naco_recipe.h"
#include "irplib_distortion.h"

#include "naco_spc.h"

#include <string.h>

/*-----------------------------------------------------------------------------
                            Recipe defines
 -----------------------------------------------------------------------------*/

#define RECIPE_STRING   "naco_spc_combine"

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


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

static cpl_error_code naco_spc_combine_save(cpl_frameset *,
                                            const cpl_parameterlist *,
                                            const cpl_propertylist *,
                                            const cpl_propertylist *,
                                            const cpl_image *,
                                            const cpl_image *,
                                            const cpl_bivector *,
                                            const cpl_imagelist *,
                                            const irplib_framelist *);

static cpl_error_code naco_framelist_fill_onoff_offset(cpl_vector *,
                                                       cpl_vector *,
                                                       const irplib_framelist *);

NACO_RECIPE_DEFINE(naco_spc_combine,
                   NACO_PARAM_XTMIN | NACO_PARAM_XTMAX |
                   NACO_PARAM_PLOT | NACO_PARAM_SAVE,
                   "Combination of spectroscopic science data",
                   RECIPE_STRING
                   " -- NACO Combination of spectroscopic science data.\n" 
                   "The files listed in the Set Of Frames (sof-file) "
                   "must be tagged:\n" 
                   "NACO-raw-file.fits " NACO_SPC_NOD_RAW " or\n"
                   "NACO-flat-file.fits " NACO_CALIB_SPCFLAT " (optional).\n"
                   "For nodded frames (" NACO_SPC_NOD_RAW ") there must be "
                   "an identical number of exposures on each side of the "
                   "center, these pairs of nodded exposures should preferably "
                   "be taken with one immediately after the other. More "
                   "precisely, the i'th A-FRAME will be paired with the i'th "
                   "B-frame");

#if 0
                   "For each " NACO_SPC_JITTER_RAW " frame a "
                   "NACO-raw-file.fits " NACO_SPC_JITTER_SKY "may be "
                   "provided as well.\n"
#endif

/*----------------------------------------------------------------------------*/
/**
 * @defgroup naco_spc_combine   Combination of spectroscopic science data
 */
/*----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
                                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_spc_combine(cpl_frameset            * framelist,
                            const cpl_parameterlist * parlist)
{
    cpl_errorstate     cleanstate = cpl_errorstate_get();
    irplib_framelist * allframes = NULL;
    irplib_framelist * rawframes = NULL;
    irplib_framelist * skyframes = NULL;
    const char       * flat;
    cpl_image        * flatimg   = NULL;
    cpl_propertylist * qclist    = cpl_propertylist_new();
    cpl_propertylist * paflist   = cpl_propertylist_new();
    cpl_propertylist * onoffkeys = cpl_propertylist_new();
    cpl_imagelist    * objimages = cpl_imagelist_new();
    cpl_imagelist    * offimages = cpl_imagelist_new();
    cpl_stats        * stats     = NULL;
    cpl_bivector     * offcorr   = NULL;
    cpl_image       ** saapair   = NULL;
    cpl_vector       * offcorx;
    cpl_vector       * offcory;
    int                npairs;
    int                nx, ny;
    const int          nplot
        = naco_parameterlist_get_int(parlist, RECIPE_STRING, NACO_PARAM_PLOT);
    const int          xtmin
        = naco_parameterlist_get_int(parlist, RECIPE_STRING, NACO_PARAM_XTMIN);
    const int          xtmax
        = naco_parameterlist_get_int(parlist, RECIPE_STRING, NACO_PARAM_XTMAX);


    skip_if(0);
    error_if(xtmin < 1, CPL_ERROR_ILLEGAL_INPUT,
             "xtmin=%d is less than 1", xtmin);
    error_if(xtmin > xtmax, CPL_ERROR_ILLEGAL_INPUT,
             "xtmin = %d is greater than xtmax=%d", xtmin, xtmax);    

    /* 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_regexp(allframes, "^("
                                                NACO_SPC_NOD_RAW "|"
                                                NACO_SPC_JITTER_RAW ")$",
                                                CPL_FALSE);
    skip_if(rawframes == NULL);

    skip_if(irplib_framelist_load_propertylist(rawframes, 0, 0, "^("
                                               IRPLIB_PFITS_WCS_REGEXP "|"
                                               NACO_PFITS_REGEXP_SPC_COMBINE_PAF
                                               ")$", CPL_FALSE));

    nx = irplib_pfits_get_int
        (irplib_framelist_get_propertylist_const(rawframes, 0), "NAXIS1");
    ny = irplib_pfits_get_int
        (irplib_framelist_get_propertylist_const(rawframes, 0), "NAXIS2");

    error_if(xtmax > nx, CPL_ERROR_ILLEGAL_INPUT,
             "xtmax=%d exceeds the image size of %d", xtmax, nx);

    skip_if(irplib_framelist_load_propertylist_all(rawframes, 0, "^("
                                                   NACO_PFITS_REGEXP_SPC_COMBINE
                                                   ")$", CPL_FALSE));

    /* Optional flatfield calibration file */
    flat = irplib_frameset_find_file(framelist, NACO_CALIB_SPCFLAT);

    if (flat != NULL) {
        flatimg = cpl_image_load(flat, CPL_TYPE_FLOAT, 0, 0);
        skip_if (flatimg == NULL);

        error_if(cpl_image_get_size_x(flatimg) != nx,
                 CPL_ERROR_INCOMPATIBLE_INPUT, "Flat image has NAXIS1=%d, not "
                 "%d", (int)cpl_image_get_size_x(flatimg), nx);
        error_if(cpl_image_get_size_y(flatimg) != ny,
                 CPL_ERROR_INCOMPATIBLE_INPUT, "Flat image has NAXIS2=%d, not "
                 "%d", (int)cpl_image_get_size_y(flatimg), ny);
    }

    if (cpl_frameset_find(framelist, NACO_SPC_JITTER_RAW)) {

        /* Jittered frames processed here */

        skip_if (cpl_frameset_find(framelist, NACO_SPC_NOD_RAW));
        skyframes = irplib_framelist_extract(allframes, NACO_SPC_JITTER_SKY);
        if (skyframes == NULL) {
            irplib_error_recover(cleanstate, "No sky frames");
        } else {
            skip_if(irplib_framelist_load_propertylist_all
                    (skyframes, 0, "^("
                     NACO_PFITS_REGEXP_SPC_COMBINE
                     ")$", CPL_FALSE));
        }

        bug_if(1);
    } else {

        /* Nodded frames processed here */

        skip_if (cpl_frameset_find(framelist, NACO_SPC_JITTER_RAW));

        /* On-frames have positive CUMOFFSETX, off-frames have negative */
        bug_if(cpl_propertylist_append_int(onoffkeys,
                                           NACO_PFITS_DOUBLE_CUMOFFSETX, 1));

        skip_if(naco_imagelist_load_diff(objimages, rawframes, onoffkeys));

        npairs = cpl_imagelist_get_size(objimages);

        error_if(2 * npairs < irplib_framelist_get_size(rawframes),
                 CPL_ERROR_ILLEGAL_INPUT,
                 "The %d frames contain only %d pair(s) of on/off frames",
                 irplib_framelist_get_size(rawframes), npairs);

        cpl_msg_info(cpl_func, "Loaded %d nodded images", npairs);

        if (flatimg) {
            cpl_msg_info(cpl_func, "Flat-fielding %d nodded images", npairs);
            skip_if(cpl_imagelist_divide_image(objimages, flatimg));
        }

        bug_if(naco_imagelist_append_invert(objimages));

        offcorr = cpl_bivector_new(2 * npairs);
        offcorx = cpl_bivector_get_x(offcorr);
        offcory = cpl_bivector_get_y(offcorr);

        /* The same reordering is done for the offsets */
        skip_if(naco_framelist_fill_onoff_offset(offcorx, offcory, rawframes));


    }

    if (cpl_msg_get_level() <= CPL_MSG_DEBUG) {
        cpl_bivector_dump(offcorr, stdout);
    }

    skip_if(naco_vector_correlate_imagelist_1d(offcorx, offcorx, CPL_FALSE,
                                               objimages));

    skip_if(naco_vector_correlate_imagelist_1d(offcory, offcory, CPL_TRUE,
                                               objimages));


    if (cpl_msg_get_level() <= CPL_MSG_INFO) {
        cpl_bivector_dump(offcorr, stdout);
    }

    /* The offset are relative to the 1st frame */
    bug_if(cpl_vector_set(offcorx, 0, 0.0));
    bug_if(cpl_vector_set(offcory, 0, 0.0));

    saapair = cpl_geom_img_offset_saa(objimages, offcorr, CPL_KERNEL_DEFAULT,
                                      0, 0, CPL_GEOM_FIRST, NULL, NULL);
    skip_if(saapair == NULL);


    /* bug_if(cpl_image_threshold(saapair[0], 0.0, FLT_MAX, 0.0, 0.0)); */

    if (nplot > 0) {
      cpl_errorstate prestate = cpl_errorstate_get();
      cpl_image * img1d = cpl_image_collapse_window_create(saapair[0], xtmin, 1,
                                                           xtmax, ny, 1);

      cpl_plot_image_col("set grid;", "t '1D-Spectrum' w linespoints", "",
                         img1d, 1, 1, 1);

      cpl_image_delete(img1d);

      img1d = cpl_image_collapse_window_create(saapair[0], xtmin, 1, xtmax,
                                               ny, 0);

      cpl_plot_image_row("set grid;", "t '1D-Profile' w linespoints", "",
                         img1d, 1, 1, 1);

      cpl_image_delete(img1d);
      if (!cpl_errorstate_is_equal(prestate)) {
        cpl_errorstate_set(prestate);
      }
    }

    stats = cpl_stats_new_from_image(saapair[0], CPL_STATS_ALL);
    bug_if(stats == NULL);

    if (cpl_msg_get_level() <= CPL_MSG_INFO) {
        cpl_stats_dump(stats, CPL_STATS_ALL, stdout);
    }

    skip_if(naco_spc_combine_qc(qclist, paflist, rawframes));

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

    skip_if(naco_spc_combine_save(framelist, parlist, qclist, paflist,
                                  saapair[0], saapair[1], offcorr, objimages,
                                  rawframes));
    
    end_skip;

    if (saapair != NULL) {
        cpl_image_delete(saapair[0]);
        cpl_image_delete(saapair[1]);
        cpl_free(saapair);
    }
    cpl_bivector_delete(offcorr);
    cpl_stats_delete(stats);
    cpl_imagelist_delete(objimages);
    cpl_imagelist_delete(offimages);
    cpl_image_delete(flatimg);
    irplib_framelist_delete(allframes);
    irplib_framelist_delete(rawframes);
    irplib_framelist_delete(skyframes);
    cpl_propertylist_delete(onoffkeys);
    cpl_propertylist_delete(qclist);
    cpl_propertylist_delete(paflist);

    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_spc_combine_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_SPCWAVE_PAF ")$";
    const char * filter;

    bug_if (0);

    filter = naco_pfits_get_filter(reflist);
    skip_if(filter == NULL);

    bug_if(cpl_propertylist_append_string(qclist, "ESO QC FILTER OBS", filter));

    /* THE PAF FILE FOR QC PARAMETERS */
    skip_if (cpl_propertylist_copy_property_regexp(paflist, reflist, pafcopy,
                                                   0));
    skip_if (cpl_propertylist_append(paflist, qclist));

    bug_if (cpl_propertylist_copy_property_regexp(qclist, reflist, "^("
                                                  IRPLIB_PFITS_WCS_REGEXP "|"
                                                  IRPLIB_PFITS_REGEXP_RECAL_LAMP
                                                  ")$", 0));
    end_skip;

    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Save the combine recipe products on disk
  @param    set_tot   the total input frame set
  @param    parlist   the input list of parameters
  @param    qclist    List of QC parameters
  @param    paflist   List of PAF parameters
  @param    combined  The combined image
  @param    contrib   The contribution map
  @param    offcorr   The combination offsets
  @param    objimages The list of objimages
  @param    rawframes the list of frames with the current settings
  @return   0 iff everything is ok
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code naco_spc_combine_save(cpl_frameset           * set_tot,
                                            const cpl_parameterlist* parlist,
                                            const cpl_propertylist * qclist,
                                            const cpl_propertylist * paflist,
                                            const cpl_image        * combined,
                                            const cpl_image        * contrib,
                                            const cpl_bivector     * offcorr,
                                            const cpl_imagelist    * objimages,
                                            const irplib_framelist * rawframes)
{
    const int          nsave
        = naco_parameterlist_get_int(parlist, RECIPE_STRING, NACO_PARAM_SAVE);
    cpl_frameset * proframes = irplib_frameset_cast(rawframes);
    cpl_table    * offtable  = NULL;
    cpl_propertylist * xtlist = cpl_propertylist_new();

    /* This will catch rawframes == NULL */
    bug_if(cpl_propertylist_append_string(xtlist, "EXTNAME",
                                          "Contribution Map"));

    /* The combined image */
    skip_if (irplib_dfs_save_image(set_tot, parlist, proframes, combined,
                               CPL_BPP_IEEE_FLOAT, RECIPE_STRING,
                               NACO_SPC_NOD_COMBINE, qclist, NULL,
                               naco_pipe_id, RECIPE_STRING CPL_DFS_FITS));


    /* Append the contribution map */
    skip_if (cpl_image_save(contrib, RECIPE_STRING CPL_DFS_FITS,
                            CPL_BPP_16_UNSIGNED, xtlist, CPL_IO_EXTEND));


    /* Append the combination offsets as a table */
    offtable = cpl_table_new(cpl_bivector_get_size(offcorr));
    bug_if(0);
    bug_if(cpl_table_wrap_double
           (offtable, (double*)cpl_bivector_get_x_data_const(offcorr),
            "OFFSETX"));

    bug_if(cpl_table_wrap_double
           (offtable, (double*)cpl_bivector_get_y_data_const(offcorr),
            "OFFSETY"));

    bug_if(cpl_table_set_column_unit(offtable, "OFFSETX", "pixel"));
    bug_if(cpl_table_set_column_unit(offtable, "OFFSETY", "pixel"));

    bug_if(cpl_propertylist_set_string(xtlist, "EXTNAME",
                                       "Image combination shifts"));

    skip_if(cpl_table_save(offtable, NULL, xtlist, RECIPE_STRING CPL_DFS_FITS,
                           CPL_IO_EXTEND));

    if (nsave > 0) {

        skip_if(irplib_dfs_save_imagelist(set_tot, parlist, proframes, objimages,
                                       CPL_BPP_IEEE_FLOAT, RECIPE_STRING,
                                       NACO_SPC_NOD_SUBTRACT, qclist, NULL,
                                       naco_pipe_id, RECIPE_STRING
                                       "_subtracted" CPL_DFS_FITS));


    }


#ifdef NACO_SAVE_PAF
    /* The PAF */
    skip_if (cpl_dfs_save_paf("NACO", RECIPE_STRING, paflist,
                              RECIPE_STRING CPL_DFS_PAF));
#else
    bug_if(paflist == NULL);
#endif

    end_skip;

    cpl_propertylist_delete(xtlist);

    cpl_frameset_delete(proframes);
    if (offtable != NULL) {
        (void)cpl_table_unwrap(offtable, "OFFSETX");
        (void)cpl_table_unwrap(offtable, "OFFSETY");
        cpl_table_delete(offtable);
    }

    return cpl_error_get_code();

}


/*----------------------------------------------------------------------------*/
/**
  @internal
  @brief    Fill the vector(s) with the CUMOFFSETX/Y
  @param    offset   The X-values
  @param    offsetY   Optional, if non-NULL, the Y-values
  @param    self      The imagelist to read from
  @return   CPL_ERROR_NONE on success, otherwise the relevant CPL error code

  The pairs of on/off frames must come right after one another in the framelist.

  The offsets are ordered with all the positive ones first, followed by all the
  negative ones.

 */
/*----------------------------------------------------------------------------*/
static
cpl_error_code naco_framelist_fill_onoff_offset(cpl_vector * offset,
                                                cpl_vector * offsety,
                                                const irplib_framelist * self)
{
    const int nframes = irplib_framelist_get_size(self);
    int       i;


    skip_if (0);
    skip_if (self    == NULL);
    skip_if (offset == NULL);

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

    bug_if(cpl_vector_set_size(offset, nframes));

    if (offsety != NULL) {
        skip_if (irplib_framelist_contains(self, NACO_PFITS_DOUBLE_CUMOFFSETY,
                                           CPL_TYPE_DOUBLE, CPL_FALSE, 0.0));
        bug_if(cpl_vector_set_size(offsety, nframes));
    }

   
    for (i = 0; i < nframes; i += 2) {
        const cpl_propertylist * plist1
            = irplib_framelist_get_propertylist_const(self, i);
        const cpl_propertylist * plist2
            = irplib_framelist_get_propertylist_const(self, i+1);

        const double xoff1
            = irplib_pfits_get_double(plist1, NACO_PFITS_DOUBLE_CUMOFFSETX);
        const double xoff2
            = irplib_pfits_get_double(plist2, NACO_PFITS_DOUBLE_CUMOFFSETX);
        int i0, i1;

        if (xoff1 > xoff2) {
            i0 = i/2;
            i1 = i/2 + nframes/2;
        } else {
            i1 = i/2;
            i0 = i/2 + nframes/2;
        }
        bug_if(cpl_vector_set(offset, i0,   xoff1));
        bug_if(cpl_vector_set(offset, i1,   xoff2));

        if (offsety != NULL) {
            const double yoff1
                = irplib_pfits_get_double(plist1, NACO_PFITS_DOUBLE_CUMOFFSETY);
            const double yoff2
                = irplib_pfits_get_double(plist2, NACO_PFITS_DOUBLE_CUMOFFSETY);

            bug_if(cpl_vector_set(offsety, i0,   yoff1));
            bug_if(cpl_vector_set(offsety, i1,   yoff2));
        }
    }

    end_skip;

    return cpl_error_get_code();

}

