/*                                                                              *
 *   This file is part of the ESO UVES Pipeline                                 *
 *   Copyright (C) 2004,2005 European Southern Observatory                      *
 *                                                                              *
 *   This library 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, 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA          *
 */
 
/*
 * $Author: amodigli $
 * $Date: 2010-09-24 09:31:30 $
 * $Revision: 1.13 $
 * $Name: not supported by cvs2svn $
 * $Log: not supported by cvs2svn $
 * Revision 1.11  2008/09/29 06:53:00  amodigli
 * add #include <string.h>
 *
 * Revision 1.10  2008/03/28 08:53:02  amodigli
 * IRPLIB_CONCAT2X-->UVES_CONCAT2X
 *
 * Revision 1.9  2008/03/25 07:38:11  amodigli
 * fixed crush on reduction chain (related to DATAMD5 addition)
 *
 * Revision 1.8  2007/11/13 16:24:02  amodigli
 * added DATAMD5 to calib products and changed PRO.CATG naming convention for single fibre order table to FIB_ORD_TAB_x to remove name clash between ECHELLE and FIBRE mode
 *
 * Revision 1.7  2007/10/01 17:22:41  amodigli
 * added full list of prep_sff_ofpos products to make work science
 *
 * Revision 1.6  2007/08/21 13:08:26  jmlarsen
 * Removed irplib_access module, largely deprecated by CPL-4
 *
 * Revision 1.5  2007/07/03 14:04:04  jmlarsen
 * Fixed bug in input validation
 *
 * Revision 1.4  2007/07/03 12:41:15  amodigli
 * added FLAMES_SCI_SIM_RED
 *
 * Revision 1.3  2007/06/28 09:33:10  jmlarsen
 * Bugfix when having multiple products with same tag
 *
 * Revision 1.2  2007/06/26 13:35:24  jmlarsen
 * Implemented
 *
 * Revision 1.35  2007/06/11 13:28:26  jmlarsen
 * Changed recipe contact address to cpl at eso.org
 *
 * Revision 1.34  2007/06/08 13:06:16  jmlarsen
 * Send bug reports to Andrea
 *
 * Revision 1.33  2007/06/06 08:17:33  amodigli
 * replace tab with 4 spaces
 *
 * Revision 1.32  2007/05/22 09:11:06  jmlarsen
 * Reverse logic bugfix
 *
 * Revision 1.31  2007/05/22 08:13:19  amodigli
 * allow to process only blu or red data
 *
 * Revision 1.30  2007/05/14 08:09:48  amodigli
 * updated input frames and tag description in recipe man page
 *
 * Revision 1.29  2007/05/03 16:03:58  jmlarsen
 * uves_obs_redchain: Implemented option to skip final science reduction
 *
 * Revision 1.28  2007/02/09 13:40:26  jmlarsen
 * Use defines for recipe id
 *
 * Revision 1.27  2007/02/09 08:58:51  jmlarsen
 * Use define's rather than hard-coded recipe names
 *
 * Revision 1.26  2006/11/24 16:21:39  jmlarsen
 * Added FIB_LINE_TABLE_x
 *
 * Revision 1.25  2006/11/15 15:02:15  jmlarsen
 * Implemented const safe workarounds for CPL functions
 *
 * Revision 1.23  2006/11/15 14:04:08  jmlarsen
 * Removed non-const version of parameterlist_get_first/last/next which is
 * already in CPL, added const-safe wrapper, unwrapper and deallocator functions
 *
 * Revision 1.22  2006/11/13 12:47:08  jmlarsen
 * Changed syntax of UVES_ARC_LAMP to support FLAMES
 *
 * Revision 1.21  2006/11/06 15:19:41  jmlarsen
 * Removed unused include directives
 *
 * Revision 1.20  2006/10/24 14:06:27  jmlarsen
 * Added flames=false where relevant
 *
 * Revision 1.19  2006/10/19 13:53:25  jmlarsen
 * Changed guess line table tag to LINE_GUESS_TAB
 *
 * Revision 1.18  2006/10/17 12:33:02  jmlarsen
 * Added semicolon at UVES_RECIPE_DEFINE invocation
 *
 * Revision 1.17  2006/10/09 13:01:13  jmlarsen
 * Use macro to define recipe interface functions
 *
 * Revision 1.16  2006/10/02 08:37:49  jmlarsen
 * Added REF_TFLAT
 *
 * Revision 1.15  2006/09/27 15:08:45  jmlarsen
 * Fixed doc. bug
 *
 * Revision 1.14  2006/09/14 08:46:51  jmlarsen
 * Added support for TFLAT, SCREEN_FLAT frames
 *
 * Revision 1.13  2006/08/17 13:56:53  jmlarsen
 * Reduced max line length
 *
 * Revision 1.12  2006/08/16 11:58:02  jmlarsen
 * Fixed trivial but harmful buffer overrun
 *
 * Revision 1.11  2006/08/11 14:56:05  amodigli
 * removed Doxygen warnings
 *
 * Revision 1.10  2006/07/14 12:30:34  jmlarsen
 * Compute PRO CATG depending on DO CATG
 *
 * Revision 1.9  2006/07/03 13:27:10  jmlarsen
 * Minor doc update
 *
 * Revision 1.8  2006/07/03 13:16:47  amodigli
 * updated description
 *
 * Revision 1.7  2006/06/16 08:25:45  jmlarsen
 * Manually propagate ESO.DET. keywords from 1st/2nd input header
 *
 * Revision 1.6  2006/06/13 12:00:54  jmlarsen
 * Support ORDER_GUESS_TAB
 *
 * Revision 1.5  2006/04/24 09:23:33  jmlarsen
 * Recognize PDARK, DFLAT, IFLAT as products
 *
 * Revision 1.4  2006/04/06 12:56:50  jmlarsen
 * Added support for PDARK, IFLAT, DLFAT frames
 *
 * Revision 1.3  2006/04/06 11:48:17  jmlarsen
 * Support for SCI_POINT_-, SCI_EXTND_- and SCI_SLICER-frames
 *
 * Revision 1.2  2006/04/06 08:47:16  jmlarsen
 * Support DFLAT, IFLAT, PDARK
 *
 * Revision 1.1  2006/02/03 07:46:30  jmlarsen
 * Moved recipe implementations to ./uves directory
 *
 * Revision 1.14  2006/01/19 08:47:24  jmlarsen
 * Inserted missing doxygen end tag
 *
 * Revision 1.13  2005/12/20 08:11:44  jmlarsen
 * Added CVS  entry
 *
 */

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


/*----------------------------------------------------------------------------*/
/**
 * @defgroup flames_redchain  Recipe: Reduction Chain
 *
 * This recipe executes a reduction chain. See man-page for details.
 */
/*----------------------------------------------------------------------------*/

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

#include <flames.h>
#include <uves.h>
#include <uves_parameters.h>
#include <uves_utils.h>
#include <uves_utils_wrappers.h>
#include <uves_dfs.h>
#include <uves_recipe.h>
#include <uves_error.h>
#include <uves_msg.h>

/* Library */
#include <cpl.h>
#include <string.h>
/*-----------------------------------------------------------------------------
                            Local constants
 -----------------------------------------------------------------------------*/
static const bool flames = true;
static const bool blue = false;

/*-----------------------------------------------------------------------------
                            Functions prototypes
 -----------------------------------------------------------------------------*/
static cpl_error_code
execute_recipe(int (*get_info)(cpl_pluginlist *), 
               const char *recipe_domain,
               cpl_frameset *frames, const cpl_parameterlist *parameters,
               const char *products[],
               int n_products,
               bool reclassify);

static bool is_missing(const cpl_frameset *frames, const char *frame1, const char *frame2);
static void remove_input_frame(cpl_frameset *frames, const char *tag);

static int flames_obs_redchain_define_parameters(cpl_parameterlist *parameters);

/*-----------------------------------------------------------------------------
                            Recipe standard code
 -----------------------------------------------------------------------------*/
#define cpl_plugin_get_info flames_obs_redchain_get_info
UVES_RECIPE_DEFINE(
    FLAMES_OBS_REDCHAIN_ID, FLAMES_OBS_REDCHAIN_DOM, flames_obs_redchain_define_parameters,
    "Jonas M. Larsen", "cpl@eso.org",
    "Runs the full UVES-FIBRE reduction chain",
    "This recipe does a complete science reduction. It runs all necessary\n"
    "calibration recipes depending on the availability of raw/processed\n"
    "calibration frames.\n"
    "Input frames are all UVES-FIBER raw and reference frames:\n"
    "formatchecks, FIB_ARC_LAMP_FORM_RED,\n"
    "order definition frames, FIB_ORDER_FLAT_RED,\n"
    "biases, BIAS_RED,\n"
    "darks, DARK_RED,\n"
    "flats, SFLAT_RED,\n"
    "arc lamps, FIB_ARC_LAMP_RED,\n"
    "standard stars, FIB_STANDARD_RED\n"
    "a wavelength catalogue table,LINE_REFER_TABLE, \n"
    "and optionally a wavelength table of bright lines,LINE_INTMON_TABLE, \n"
    "used only for computing Quality Control parameters.\n"
    "a reference standard star flux table, FLUX_STD_TABLE, \n"
    "a table describing the atmospheric extintion,EXTCOEFF_TABLE.\n"
    "Optionally, science frames, SCIENCE_xxx, or UVES_SCI_POINT_xxx, \n"
    "or UVES_SCI_EXTND_xxx, or UVES_SCI_SLICER_xxx.\n"
    "For further details on the data reduction and the input frame types\n"
    "refer to the man page of the individual recipes.\n");

/**@{*/

/*-----------------------------------------------------------------------------
                              Functions code
 -----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
/**
  @brief    Setup the recipe options    
  @param    parameters        the parameterlist to fill
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int
flames_obs_redchain_define_parameters(cpl_parameterlist *parameters)
{
    const char *recipe_id = make_str(FLAMES_OBS_REDCHAIN_ID);
    const char *subcontext = NULL;

    uves_par_new_value("scired",
                       CPL_TYPE_BOOL,
                       "Whether or not to do science reduction. "
                       "If false, only master calibration frames "
                       "are created. If false, either zero or all "
                       "necessary calibration frames must be provided "
                       "for each arm",
                       true);

    /*****************
     *    General    *
     *****************/
    if (uves_define_global_parameters(parameters) != CPL_ERROR_NONE)
        {
            return -1;
        }

    /******************
     *   Master bias  *
     ******************/
    if (uves_propagate_parameters(
            make_str(UVES_MBIAS_ID), parameters, make_str(FLAMES_OBS_REDCHAIN_ID), NULL) != 0)
        {
            return -1;
        }

    /******************
     *   Master dark  *
     ******************/
    if (uves_propagate_parameters(
            make_str(UVES_MDARK_ID), parameters, make_str(FLAMES_OBS_REDCHAIN_ID), NULL) != 0)
        {
            return -1;
        }

    /******************
     * Physical model *
     ******************/
    if (uves_prop_par(
            &flames_cal_predict_get_info, parameters, make_str(FLAMES_OBS_REDCHAIN_ID), NULL) != 0)
        {
            return -1;
        }

    /******************
     * Order position *
     ******************/
    if (uves_prop_par(
            &flames_cal_orderpos_get_info, parameters, make_str(FLAMES_OBS_REDCHAIN_ID), NULL) != 0)
        {
            return -1;
        }

    /******************
     *   Master flat  *
     ******************/
    if (uves_propagate_parameters(
            make_str(UVES_MFLAT_ID), parameters, make_str(FLAMES_OBS_REDCHAIN_ID), NULL) != 0)
        {
            return -1;
        }

    /******************
     *   Wave.cal.    *
     ******************/
    if (uves_prop_par(
            &flames_cal_wavecal_get_info, parameters, make_str(FLAMES_OBS_REDCHAIN_ID), NULL) != 0)
        {
            return -1;
        }

    /******************
     *     Prep       *
     ******************/
    if (uves_prop_par(
            &flames_cal_prep_sff_ofpos_get_info, parameters, make_str(FLAMES_OBS_REDCHAIN_ID), NULL) != 0)
        {
            return -1;
        }

    /******************
     *    Scired      *
     ******************/
    if (uves_prop_par(
            &flames_obs_scired_get_info, parameters, make_str(FLAMES_OBS_REDCHAIN_ID), NULL) != 0)
        {
            return -1;
        }

    return (cpl_error_get_code() != CPL_ERROR_NONE);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the command line options and execute the data reduction
  @param    parameters  the parameters list
  @param    frames      the frames list
  @return   CPL_ERROR_NONE if everything is ok


 */
/*----------------------------------------------------------------------------*/
static void
UVES_CONCAT2X(FLAMES_OBS_REDCHAIN_ID,exe)(cpl_frameset *frames,
                      const cpl_parameterlist *parameters, 
                      const char *starttime)
{
    const enum uves_chip chip1 = UVES_CHIP_REDL;
    const enum uves_chip chip2 = UVES_CHIP_REDU;

    bool do_science;

    bool run_mbias;
    bool run_mdark;
    bool run_physmod;
    bool run_orderpos;
    bool run_mflat;
    bool run_prep;
    bool run_wavecal;
    bool run_scired;

    cpl_frameset *fms = NULL;

    /* Exceptionally, this parameter is not used because this
       recipe does not create any products on its own. Suppress
       warning about unused variable */
    starttime = starttime;
    
    check( uves_get_parameter(parameters, NULL, make_str(FLAMES_OBS_REDCHAIN_ID), 
                              "scired", 
                              CPL_TYPE_BOOL, &do_science), "Could not read parameter");
    
    /* Check for at least one science frame */
    assure(!do_science ||
           cpl_frameset_find(frames, FLAMES_SCI_RED)    != NULL ||
           cpl_frameset_find(frames, FLAMES_SCI_SIM_RED) != NULL ||
           cpl_frameset_find(frames, FLAMES_SCI_COM_RED) != NULL,
           CPL_ERROR_DATA_NOT_FOUND, "No %s, %s or %s in frame set",
           FLAMES_SCI_RED,
           FLAMES_SCI_SIM_RED,
           FLAMES_SCI_COM_RED);

    fms = cpl_frameset_duplicate(frames);
    assure_mem( fms );

    /* Algorithm:
       (with purpose of failing early if we have to fail.)

       1) Find out which recipes to run
       2) Check for necessary input frames
       3) Execute
    */
    
    run_scired = do_science &&
        !(is_missing(fms, FLAMES_SCI_RED, NULL) &&
          is_missing(fms, FLAMES_SCI_SIM_RED, NULL) &&
          is_missing(fms, FLAMES_SCI_COM_RED, NULL));
    
    /* Require master bias */
    run_mbias = is_missing(fms, 
                           UVES_MASTER_BIAS(chip1),
                           UVES_MASTER_BIAS(chip2));
        
    /* Run master dark, only if master is missing and
       raw frames are available */ 
    run_mdark = 
        is_missing(fms,
                   UVES_MASTER_DARK(chip1),
                   UVES_MASTER_DARK(chip2)) 
        &&
        is_missing(fms,
                   UVES_MASTER_PDARK(chip1),
                   UVES_MASTER_PDARK(chip2))
        &&(!is_missing(fms, UVES_DARK(blue), NULL) ||
           !is_missing(fms, UVES_PDARK(blue), NULL));
        
    /* Run prep if no (final) order table */
    run_prep = is_missing(fms,
                          UVES_ORDER_TABLE(flames, chip1),
                          UVES_ORDER_TABLE(flames, chip2));
    
    /* Run orderpos if order table and guess order table is missing */
    run_orderpos = run_prep && (
        is_missing(fms,
                   UVES_GUESS_ORDER_TABLE(flames, chip1),
                   UVES_GUESS_ORDER_TABLE(flames, chip2))
        ||
        is_missing(fms,
                   FLAMES_ORDEF(flames, chip1),
                   FLAMES_ORDEF(flames, chip2))
        );
    
    /* Run master flat recipe if master flat frame is missing */
    run_mflat = run_prep &&    
        is_missing(fms,
                   UVES_MASTER_SFLAT(chip1),
                   UVES_MASTER_SFLAT(chip2));
    
    /* Run wavecal if no line table */
    run_wavecal = is_missing(fms,
                             UVES_LINE_TABLE(flames, chip1),
                             UVES_LINE_TABLE(flames, chip2));
        
    /* Run physical model if there's no guess line table */
    run_physmod = 
        (run_wavecal && is_missing(fms,
                                   UVES_GUESS_LINE_TABLE(flames, chip1),
                                   UVES_GUESS_LINE_TABLE(flames, chip2)))
        ||
        (run_orderpos && is_missing(fms,
                                    UVES_GUESS_ORDER_TABLE(flames, chip1),
                                    UVES_GUESS_ORDER_TABLE(flames, chip2)));

    
                    
    uves_msg("Reduction strategy for %s arm:", (blue) ? "BLUE" : "RED");
    uves_msg("Run %-13s: %s", make_str(UVES_MBIAS_ID)   , (run_mbias   ) ? "Yes" : "No");
    uves_msg("Run %-13s: %s", make_str(UVES_MDARK_ID)   , (run_mdark   ) ? "Yes" : "No");
    uves_msg("Run %-13s: %s", make_str(FLAMES_CAL_PREDICT_ID) , (run_physmod ) ? "Yes" : "No");
    uves_msg("Run %-13s: %s", make_str(FLAMES_CAL_ORDERPOS_ID), (run_orderpos) ? "Yes" : "No");
    uves_msg("Run %-13s: %s", make_str(UVES_MFLAT_ID)   , (run_mflat   ) ? "Yes" : "No");
    uves_msg("Run %-13s: %s", make_str(FLAMES_CAL_PREP_SFF_OFPOS_ID) , (run_prep) ? "Yes" : "No");
    uves_msg("Run %-13s: %s", make_str(FLAMES_CAL_WAVECAL_ID) , (run_wavecal ) ? "Yes" : "No");
    uves_msg("Run %-13s: %s", make_str(FLAMES_OBS_SCIRED_ID)  , (run_scired  ) ? "Yes" : "No");
 
    /* As a service to the user, assure that required
       raw frames and catalogue calibration frames
       exist *before* doing the reduction */
    
    assure( !run_mbias || !is_missing(fms, UVES_BIAS(blue), NULL), 
            CPL_ERROR_DATA_NOT_FOUND,
            "One or more '%s' frames needed for recipe '%s'",
            UVES_BIAS(blue), make_str(UVES_MBIAS_ID));
    
    assure( !run_mdark || 
            !is_missing(fms, UVES_DARK(blue), NULL) ||
            !is_missing(fms, UVES_PDARK(blue), NULL),
            CPL_ERROR_DATA_NOT_FOUND, 
            "One or more '%s' or '%s' frames needed for recipe '%s'",
            UVES_DARK(blue), UVES_PDARK(blue), make_str(UVES_MDARK_ID));
    
    assure( !run_physmod || !is_missing(fms, UVES_FORMATCHECK(flames, blue), NULL),
            CPL_ERROR_DATA_NOT_FOUND, "Frame '%s' needed for recipe '%s'",
            UVES_FORMATCHECK(flames, blue), make_str(FLAMES_CAL_PREDICT_ID));
    
    assure( !run_orderpos || !is_missing(fms, UVES_ORDER_FLAT(flames, blue), NULL),
            CPL_ERROR_DATA_NOT_FOUND, "Frame '%s' needed for recipe '%s'",
            UVES_ORDER_FLAT(flames, blue), make_str(FLAMES_CAL_ORDERPOS_ID));
    
    assure( !run_mflat || !is_missing(fms, UVES_SFLAT(blue), NULL),
            CPL_ERROR_DATA_NOT_FOUND, "Frame '%s' needed for recipe '%s'",
            UVES_SFLAT(blue),
            make_str(UVES_MFLAT_ID));
    
    assure( !run_wavecal || 
            !is_missing(fms, UVES_ARC_LAMP(flames, blue), NULL) ||
            !is_missing(fms, FLAMES_FIB_SCI_SIM, NULL),
            CPL_ERROR_DATA_NOT_FOUND, "Frame '%s' or '%s' needed for recipe '%s'",
            UVES_ARC_LAMP(flames, blue), FLAMES_FIB_SCI_SIM,
            make_str(FLAMES_CAL_WAVECAL_ID));

    assure( !run_wavecal || !is_missing(fms, UVES_LINE_REFER_TABLE, NULL),
            CPL_ERROR_DATA_NOT_FOUND, "Frame '%s' needed for recipe '%s'",
            UVES_LINE_REFER_TABLE, make_str(FLAMES_CAL_WAVECAL_ID));
    
    assure( !run_prep || !is_missing(fms, FLAMES_FIB_FF_ODD, NULL),
            CPL_ERROR_DATA_NOT_FOUND, "Frame '%s' needed for recipe '%s'",
            FLAMES_FIB_FF_ODD,
            make_str(FLAMES_CAL_PREP_SFF_OFPOS_ID));

    assure( !run_prep || !is_missing(fms, FLAMES_FIB_FF_EVEN, NULL),
            CPL_ERROR_DATA_NOT_FOUND, "Frame '%s' needed for recipe '%s'",
            FLAMES_FIB_FF_EVEN,
            make_str(FLAMES_CAL_PREP_SFF_OFPOS_ID));

    assure( !run_prep || !is_missing(fms, FLAMES_FIB_FF_ALL, NULL),
            CPL_ERROR_DATA_NOT_FOUND, "Frame '%s' needed for recipe '%s'",
            FLAMES_FIB_FF_ALL,
            make_str(FLAMES_CAL_PREP_SFF_OFPOS_ID));
       
    /* We now know which recipes to run and
     * that required input frames exist. Execute
     * chain; re-classify PRODUCT->CALIB under way
     */
    
    if (run_mbias)
        {
            const char *products[2];
            
            int nprod = sizeof(products) / sizeof (char *);
            
            products[0] = UVES_MASTER_BIAS(chip1);
            products[1] = UVES_MASTER_BIAS(chip2);
            
            check( execute_recipe(&uves_mbias_get_info,
                                  UVES_MBIAS_DOM, fms, parameters, products, nprod, true),
                   "Recipe execution failed");
        }
    
    check( remove_input_frame(fms, UVES_BIAS(blue)), "Error removing input frames");
    
    if (run_mdark)
        {
            const char *products[4];
            
            int nprod = sizeof(products) / sizeof (char *);
            
            products[0] = UVES_MASTER_DARK(chip1);
            products[1] = UVES_MASTER_PDARK(chip1);
            products[2] = UVES_MASTER_DARK(chip2);
            products[3] = UVES_MASTER_PDARK(chip2);
            
            check( execute_recipe(&uves_mdark_get_info,
                                  UVES_MDARK_DOM, fms, parameters, products, nprod, true), 
                   "Recipe execution failed");
        }
    
    check( remove_input_frame(fms, UVES_DARK(blue)), "Error removing input frames");
    check( remove_input_frame(fms, UVES_PDARK(blue)), "Error removing input frames");
    
    if (run_physmod)
        {
            const char *products[4];
            int nprod = sizeof(products) / sizeof (char *);
            
            products[0] = UVES_GUESS_LINE_TABLE (flames, chip1);
            products[1] = UVES_GUESS_ORDER_TABLE(flames, chip1);
            products[2] = UVES_GUESS_LINE_TABLE (flames, chip2);
            products[3] = UVES_GUESS_ORDER_TABLE(flames, chip2);
            
            check( execute_recipe(&flames_cal_predict_get_info,
                                  FLAMES_CAL_PREDICT_DOM,
                                  fms, parameters, products, nprod, true), 
                   "Recipe execution failed");
        }
    
    check( remove_input_frame(fms, UVES_FORMATCHECK(flames, blue)), 
           "Error removing input frames");
    
    if (run_orderpos)
        {
            const char *products[4];
            int nprod = sizeof(products) / sizeof (char *);
            
            products[0] = UVES_ORD_TAB(flames, chip1);
            products[1] = UVES_ORD_TAB(flames, chip2);
            products[2] = FLAMES_ORDEF(flames, chip1);
            products[3] = FLAMES_ORDEF(flames, chip2);
            
            check( execute_recipe(&flames_cal_orderpos_get_info,
                       FLAMES_CAL_ORDERPOS_DOM,
                       fms, parameters, products, nprod, true), 
                   "Recipe execution failed");
        }
    
    check( remove_input_frame(fms, UVES_ORDER_FLAT(flames, blue)),
           "Error removing input frames");
    
    if (run_mflat)
        {
            const char *products[2];
            
            int nprod = sizeof(products) / sizeof (char *);
            
            products[0] = UVES_MASTER_SFLAT(chip1);
            products[1] = UVES_MASTER_SFLAT(chip2);
            
            check( execute_recipe(&uves_mflat_get_info,
                                  UVES_MFLAT_DOM,
                                  fms, parameters, products, nprod, true), 
                   "Recipe execution failed");
        }
    
    check( remove_input_frame(fms, UVES_SFLAT(blue)), "Error removing input frames");
    
    if (run_prep)
        {
            const char *products[29];
            int nprod = sizeof(products) / sizeof (char *);

            products[0] = FIB_FF_ODD_INFO_TAB;
            products[1] = FIB_FF_EVEN_INFO_TAB;
            products[2] = FIB_FF_ALL_INFO_TAB;
            products[3] = UVES_ORDER_TABLE(flames, chip1);
            products[4] = UVES_ORDER_TABLE(flames, chip2);
            products[5] = FLAMES_SLIT_FF_COM(chip1);
            products[6] = FLAMES_SLIT_FF_COM(chip2);
            products[7] = FLAMES_SLIT_FF_NOR(chip1);
            products[8] = FLAMES_SLIT_FF_NOR(chip2);
            products[9] = FLAMES_SLIT_FF_DTC(chip1);
            products[10] = FLAMES_SLIT_FF_DTC(chip2);
            products[11] = FLAMES_SLIT_FF_BPC(chip1);
            products[12] = FLAMES_SLIT_FF_BPC(chip2);
            products[13] = FLAMES_SLIT_FF_SGC(chip1);
            products[14] = FLAMES_SLIT_FF_SGC(chip2);
            products[15] = FLAMES_SLIT_FF_BNC(chip1);
            products[16] = FLAMES_SLIT_FF_BNC(chip2);
            products[17] = FLAMES_FIB_FF_COM(chip1);
            products[18] = FLAMES_FIB_FF_COM(chip2);
            products[19] = FLAMES_FIB_FF_NOR(chip1);
            products[20] = FLAMES_FIB_FF_NOR(chip2);
            products[21] = FLAMES_FIB_FF_NSG(chip1);
            products[22] = FLAMES_FIB_FF_NSG(chip2);
            products[23] = FLAMES_FIB_FF_DTC(chip1);
            products[24] = FLAMES_FIB_FF_DTC(chip2);
            products[25] = FLAMES_FIB_FF_BPC(chip1);
            products[26] = FLAMES_FIB_FF_BPC(chip2);
            products[27] = FLAMES_FIB_FF_SGC(chip1);
            products[28] = FLAMES_FIB_FF_SGC(chip2);
            /* And many others too numerous to mention */
            
            check( execute_recipe(&flames_cal_prep_sff_ofpos_get_info,
                                  FLAMES_CAL_PREP_SFF_OFPOS_DOM,
                                  fms, parameters, products, nprod, true),
                   "Recipe execution failed");
        }
    
    check( remove_input_frame(fms, FLAMES_FIB_FF_ODD), "Error removing input frames");
    check( remove_input_frame(fms, FLAMES_FIB_FF_EVEN), "Error removing input frames");
    check( remove_input_frame(fms, FLAMES_FIB_FF_ALL), "Error removing input frames");
    
  
    if (run_wavecal)
        {
            const char *products[2];
            int nprod = sizeof(products) / sizeof (char *);
            
            products[0] = UVES_LINE_TABLE(flames, chip1);
            products[1] = UVES_LINE_TABLE(flames, chip2);
            
            check( execute_recipe(&flames_cal_wavecal_get_info,
                                  FLAMES_CAL_WAVECAL_DOM,
                                  fms, parameters, products, nprod, true),
                   "Recipe execution failed");
        }
    
    check( remove_input_frame(fms, UVES_ARC_LAMP(flames, blue)), 
           "Error removing input frames");
    check( remove_input_frame(fms, FLAMES_FIB_SCI_SIM),
           "Error removing input frames");
    check( remove_input_frame(fms, UVES_LINE_REFER_TABLE), 
           "Error removing input frames");
  
    if (run_scired)
        {
            const char *products[2];
            int nprod = sizeof(products) / sizeof (char *);
            
            /* not used */
            products[0] = "DUMMY";
            products[1] = "DUMMY";
            
            check( execute_recipe(&flames_obs_scired_get_info,
                                  FLAMES_OBS_SCIRED_DOM,
                                  fms, parameters, products, nprod, false),
                   "Recipe execution failed");
        }
    
    check( remove_input_frame(fms, FLAMES_SCI_RED), "Error removing input frames");
    check( remove_input_frame(fms, FLAMES_SCI_COM_RED), "Error removing input frames");
                  
    /* Insert all product frames into recipe frame set */
    {
        cpl_frame *f = NULL;
        
        for (f = cpl_frameset_get_first(fms);
             f != NULL;
             f = cpl_frameset_get_next(fms))
            {
                if (cpl_frame_get_group(f) == CPL_FRAME_GROUP_PRODUCT)
                    {
                        check( cpl_frameset_insert(frames, cpl_frame_duplicate(f)),
                               "Error inserting product '%s' into frame set",
                               cpl_frame_get_tag(f));
                    }
            }
    }
    
  cleanup:
    uves_free_frameset(&fms);
    return;
}

/* Execute a recipe and re-classify its products as calibration frames */
static cpl_error_code
execute_recipe(int (*get_info)(cpl_pluginlist *), 
               const char *recipe_domain,
               cpl_frameset *frames, const cpl_parameterlist *parameters,
               const char *products[],
               int n_products,
               bool reclassify)              /* Re-classify products? */
{
  int i;
  cpl_frame *f = NULL;

  /* Remove (from frame set) any product
     frames already present */
  for (i = 0; i < n_products; i++)
      {
          if ((f = cpl_frameset_find(frames, products[i])) != NULL)
              {
                  if (cpl_frame_get_group(f) == CPL_FRAME_GROUP_PRODUCT)
                      {
                          cpl_msg_warning(__func__, "Ignoring %s frame in '%s'. "
                                          "A new %s frame will now be calculated",
                                          products[i], cpl_frame_get_filename(f),
                                          products[i]);
                          
                          cpl_frameset_erase_frame(frames, f);
                      }
              }
      }
  
  /* Execute */
  check_nomsg( uves_exec_recipe(get_info, recipe_domain, parameters, frames, make_str(FLAMES_OBS_REDCHAIN_ID), NULL));
  if (reclassify)
      {
          /* Now re-classify PRODUCT->CALIB to be used in the remaining
             reduction chain. Before doing that, we have to remove any
             calibration frame with same tag as a product (such as line tables),
             in order not to confuse the re-classified products with the
             previous calibration frames */
          
          for (i = 0; i < n_products; i++)
              {
                  if ((f = cpl_frameset_find(frames, products[i])) != NULL &&
                      cpl_frame_get_group(f) != CPL_FRAME_GROUP_PRODUCT)
                      {
                          uves_msg("Removing %s frame in '%s' from frameset. "
                                   "It is not tagged as a product",
                                   products[i], cpl_frame_get_filename(f));
                          
                          cpl_frameset_erase_frame(frames, f);
                      }
              }
          
          /*
           * Re-classify products
           */
          for (i = 0; i < n_products; i++) {
              for (f = cpl_frameset_get_first(frames);
                   f != NULL;
                   f = cpl_frameset_get_next(frames)) {
                  
                  if (cpl_frame_get_group(f) == CPL_FRAME_GROUP_PRODUCT) {
                      if (strcmp(cpl_frame_get_tag(f), products[i]) == 0)
                          {
                              /* Re-classify the product as calibration frames */
                              uves_msg("Re-classifying %s product in '%s' PRODUCT->CALIB",
                                       products[i], cpl_frame_get_filename(f));
                              
                              cpl_frame_set_group(f, CPL_FRAME_GROUP_CALIB);
                          }
                  }
              }
          }
          
          /*
           * Remove other products that 
           * are not used later  (e.g. BKG_FLAT_xxxx)
           */
          for (f = cpl_frameset_get_first(frames);
               f != NULL;
               f = cpl_frameset_get_next(frames))
              {
                  if (cpl_frame_get_group(f) == CPL_FRAME_GROUP_PRODUCT)
                      {
                          /* Remove this product */
                          uves_msg("Removing product %s in '%s' from frameset. "
                                   "Not needed later",
                                   cpl_frame_get_tag(f), cpl_frame_get_filename(f));
                          
                          cpl_frameset_erase_frame(frames, f);
                          
                      }
              }  
      } /* if re-classify... */  

  cleanup:
  return cpl_error_get_code();
}
  

/* Retruns true if either frame 1 or frame 2 is not in the
   provided frame set 

   fixme: reverse the logic of this function, i.e. rename to 'contains'
*/
static bool
is_missing(const cpl_frameset *frames, const char *frame1, const char *frame2)
{
    bool result = false;
    if (cpl_frameset_find_const(frames, frame1) == NULL)
        {
            uves_msg("checking for %s... no", frame1);
            result = true;
        }
    else
        {
            uves_msg("checking for %s... yes", frame1);
        }
    
    if (frame2 != NULL && strcmp(frame1, frame2) != 0)
        {
            if (cpl_frameset_find_const(frames, frame2) == NULL)
                {
                    uves_msg("checking for %s... no", frame2);
                    result = true;
                }
            else
                {
                    uves_msg("checking for %s... yes", frame2);
                }
        }
    
  return result;
}


/* Remove input frames (e.g. bias frames) along the way */
static void
remove_input_frame(cpl_frameset *frames, const char *tag)
{
    int removed = cpl_frameset_erase(frames, tag);

    if (removed > 0)
        {
            uves_msg("Removing %d %s frame%s from frame set", removed, tag, removed > 1 ? "s" : "");
        }

    return;
}
/**@}*/
