/*
 * This file is part of the VISIR Pipeline
 * Copyright (C) 2012,2013,2014,2015 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
 */


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

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

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600 /* setenv */
#endif

#ifndef ESO_COVERAGE_BUILD
#define ESO_COVERAGE_BUILD 0
#endif

#include "visir_recipe.h"

#include "visir_utils.h"
#include "irplib_utils.h"
#include <cxlist.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

int visir_util_repack_get_info(cpl_pluginlist *);
int visir_util_undistort_get_info(cpl_pluginlist *);
int visir_old_util_destripe_get_info(cpl_pluginlist *);
int visir_old_spc_obs_get_info(cpl_pluginlist *);

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

#define RECIPE_STRING   "visir_spc_reduce"

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

/* FIXME: this code is littered with many unimportant memory leaks */

static cpl_error_code visir_spc_reduce_fill_parameterlist(cpl_parameterlist *);
static int visir_spc_reduce(cpl_frameset *, const cpl_parameterlist *);

static int visir_spc_reduce_create(cpl_plugin * plugin)
{
    cpl_recipe   * recipe   = (cpl_recipe *)plugin; /* Needed for the fill */
    cpl_errorstate prestate = cpl_errorstate_get(); /* To check the fill */

    /* Propagate error, if any */
    /* - Need two function calls to ensure plugin validity before the fill */
    return cpl_recipedefine_create(plugin)
        || cpl_recipedefine_create_is_ok(prestate,
             visir_spc_reduce_fill_parameterlist(recipe->parameters))
        ? (int)cpl_error_set_where(cpl_func) : 0;
}


/*----------------------------------------------------------------------------*/
/**
  @internal
  @brief    execute recipe
  @param    plugin  plugin structure
  @return   0 iff everything is ok

  Reliable removal of temporary data is not possible in signal/exit handlers
  due to the use of openmp in child recipes, so to guarante cleanup the recipe
  execution is forked.
  This also makes it more robust against crashes and memory leaks (which is
  relevant for gasgano)
*/
/*----------------------------------------------------------------------------*/
static int visir_spc_reduce_exec(cpl_plugin * plugin)
{
    char * progname = getenv("_");
    char * classpath = getenv("CLASSPATH");
    cpl_msg_debug(cpl_func, "Program name: %s", progname);
    cpl_msg_debug(cpl_func, "CLASSPATH: %s", classpath);
    if ((progname && strstr(progname, "gasgano")) ||
        (classpath && strstr(classpath, "gasgano.jar"))) {
        cpl_msg_info(cpl_func, "Running under gasgano, disabling OpenMP");
        setenv("OMP_NUM_THREADS", "0", 1);
        return visir_tmpdir_exec(RECIPE_STRING, plugin, visir_spc_reduce);
    }
    else if (ESO_COVERAGE_BUILD || getenv("VISIR_NO_FORK") != NULL) {
        /* debugging and coverage is better without fork */
        return cpl_recipedefine_exec(plugin, visir_spc_reduce)
            ? (int)cpl_error_set_where(cpl_func) : 0;
    }
    else
        return visir_forking_exec(RECIPE_STRING, plugin, visir_spc_reduce);
}

/* The definition of the recipe destroy function */
static int visir_spc_reduce_destroy(cpl_plugin * plugin)
{
    /* Propagate error, if any */
    return cpl_recipedefine_destroy(plugin)
        ? (int)cpl_error_set_where(cpl_func) : 0;
}

int cpl_plugin_get_info(cpl_pluginlist * list)
{
    /* Propagate error, if any. Return 1 on failure */
    return cpl_recipedefine_init(list, CPL_VERSION_CODE,
                                VISIR_BINARY_VERSION,
                                "visir_spc_reduce",
                    "Spectroscopic Observation recipe",
                    "This recipe performs a wavelength calibration "
                    "followed by spectrum extraction from a combined image. "
                    "It can also compute sensitivities for standard star "
                    "observations.\n"
                    "It works for low and high resolution including echelle "
                    "mode.\n"

                    "The files listed in the Set Of Frames (sof-file) "
                    "must be tagged:\n"
                    "VISIR-Long-Slit-Spectroscopy-file.fits "
                    VISIR_SPC_OBS_RAW "\n"
                    "VISIR-Quantum-Efficiency-Calibration-file.fits "
                    VISIR_CALIB_QEFF_SPC "\n"
                    "VISIR-Atmospheric-Emission-Lines-Calibration-file.fits "
                    VISIR_CALIB_LINES_SPC "\n"
                    "VISIR-Standard-Star-Flux-Catalog.fits (optional)"
                    VISIR_CALIB_STDSTAR_SPC"\n"
                    "VISIR-linearty-table.fits "VISIR_CALIB_LIN" (optional)"
                    MAN_VISIR_CALIB_BPM_IMG,
                                "Julian Taylor",
                                "jtaylor@partner.eso.org",
                                cpl_get_license(PACKAGE_NAME, "2015"),
                                visir_spc_reduce_create,
                                visir_spc_reduce_exec,
                                visir_spc_reduce_destroy)
        ? ((void)cpl_error_set_where(cpl_func), 1) : 0;
}

/*
cpl_recipe_define(visir_spc_reduce, VISIR_BINARY_VERSION,
                  "Julian Taylor", PACKAGE_BUGREPORT, "2012",
                  "Attempt to remove stripes in spectral data",
                  "The files listed in the Set Of Frames (sof-file) "
                  "must be tagged:\n"
                  "VISIR-chopnod-corrected-file.fits "
                  "\nThe product(s) will have a FITS card\n"
                  "'HIERARCH ESO PRO CATG' with a value of:\n");
                  */

/*----------------------------------------------------------------------------*/
/**
 * @defgroup visir_spc_reduce   remove stripes
 */
/*----------------------------------------------------------------------------*/

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

/*----------------------------------------------------------------------------*/
/**
  @internal
  @brief    Fill the recipe parameterlist
  @param    self  The parameterlist
  @return   0 iff everything is ok

*/
/*----------------------------------------------------------------------------*/
static cpl_error_code
visir_spc_reduce_fill_parameterlist(cpl_parameterlist * self)
{
    cpl_pluginlist * plugins = cpl_pluginlist_new();
    const char * context = PACKAGE ".visir_spc_reduce";

    cpl_recipe * repack = visir_init_recipe("visir_util_repack",
                                            &visir_util_repack_get_info,
                                            plugins);

    cpl_recipe * undist = visir_init_recipe("visir_util_undistort",
                                            &visir_util_undistort_get_info,
                                            plugins);

    cpl_recipe * destripe = visir_init_recipe("visir_old_util_destripe",
                                              &visir_old_util_destripe_get_info,
                                              plugins);

    cpl_recipe * spcobs = visir_init_recipe("visir_old_spc_obs",
                                            &visir_old_spc_obs_get_info,
                                            plugins);

    cpl_plugin_get_init(&repack->interface)(&repack->interface);
    cpl_plugin_get_init(&undist->interface)(&undist->interface);
    cpl_plugin_get_init(&destripe->interface)(&destripe->interface);
    cpl_plugin_get_init(&spcobs->interface)(&spcobs->interface);
    cpl_parameterlist * repack_par = repack->parameters;
    cpl_parameterlist * undist_par = undist->parameters;
    cpl_parameterlist * destripe_par = destripe->parameters;
    cpl_parameterlist * spcobs_par = spcobs->parameters;
    skip_if(0);

    for (cpl_parameter * p = cpl_parameterlist_get_first(repack_par);
         p != NULL; p = cpl_parameterlist_get_next(repack_par)) {
        const char * name = cpl_parameter_get_name(p);
        if (strstr(name, "bkgcorrect"))
            continue;
        if (strstr(name, "normalize"))
            continue;
        if (strstr(name, "compress"))
            continue;
        if (strstr(name, "ncycles"))
            continue;
        cpl_parameterlist_append(self, visir_parameter_duplicate(p));
    }

    skip_if(0);

    for (cpl_parameter * p = cpl_parameterlist_get_first(undist_par);
         p != NULL; p = cpl_parameterlist_get_next(undist_par)) {
        const char * name = cpl_parameter_get_name(p);
        if (strstr(name, "bkgcorrect"))
            continue;
        cpl_parameterlist_append(self, visir_parameter_duplicate(p));
    }

    skip_if(0);

    for (cpl_parameter * p = cpl_parameterlist_get_first(destripe_par);
         p != NULL; p = cpl_parameterlist_get_next(destripe_par)) {
        cpl_parameterlist_append(self, visir_parameter_duplicate(p));
    }

    skip_if(0);

    for (cpl_parameter * p = cpl_parameterlist_get_first(spcobs_par);
         p != NULL; p = cpl_parameterlist_get_next(spcobs_par)) {
        const char * name = cpl_parameter_get_name(p);
        if (strstr(name, "auto_bpm") ||
            strstr(name, "rem_glitch") ||
            strstr(name, "purge_bad") ||
            strstr(name, "union") ||
            strstr(name, "refine") ||
            strstr(name, "xcorr") ||
            strstr(name, "objects") ||
            strstr(name, "nodding") ||
            strstr(name, "offsets")) {
            continue;
        }
        cpl_parameterlist_append(self, visir_parameter_duplicate(p));
    }

    skip_if(0);


    skip_if(irplib_parameterlist_set_bool(self, PACKAGE, RECIPE_STRING,
                                          "delete-temp", CPL_TRUE, NULL,
                                          context, "Delete temporary files "
                                          "created during processing"));

    skip_if(irplib_parameterlist_set_bool(self, PACKAGE, RECIPE_STRING,
                                          "destripe", CPL_FALSE, NULL, context,
                                          "Attempt to remove stripes"));

    end_skip;

    cpl_parameterlist_delete(repack->parameters);
    cpl_parameterlist_delete(undist->parameters);
    cpl_parameterlist_delete(destripe->parameters);
    cpl_parameterlist_delete(spcobs->parameters);
    cpl_plugin_delete(&repack->interface);
    cpl_plugin_delete(&undist->interface);
    cpl_plugin_delete(&destripe->interface);
    cpl_plugin_delete(&spcobs->interface);
    cpl_pluginlist_delete(plugins);

    return cpl_error_get_code();
}

static cpl_error_code
util_repack_set_parameters(cpl_parameterlist * rec_pars,
                           const cpl_parameterlist * chain_pars)
{
    cpl_parameter * par;

    skip_if(visir_copy_parameters(rec_pars, chain_pars));

    par = cpl_parameterlist_find(rec_pars, PACKAGE".visir_util_repack.bkgcorrect");

    if (par)
        cpl_parameter_set_string(par, "none");

    par = cpl_parameterlist_find(rec_pars, PACKAGE".visir_util_repack.normalize");

    if (par)
        cpl_parameter_set_bool(par, CPL_TRUE);

    par = cpl_parameterlist_find(rec_pars, PACKAGE".visir_util_repack.compress");

    if (par)
        cpl_parameter_set_bool(par, CPL_TRUE);

    end_skip;

    return cpl_error_get_code();
}

static cpl_error_code
util_undistort_set_parameters(cpl_parameterlist * rec_pars,
                              const cpl_parameterlist * chain_pars)
{
    cpl_parameter * par;

    skip_if(visir_copy_parameters(rec_pars, chain_pars));

    par = cpl_parameterlist_find(rec_pars, PACKAGE".visir_util_repack.bkgcorrect");

    if (par)
        cpl_parameter_set_bool(par, CPL_TRUE);

    end_skip;

    return cpl_error_get_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 visir_spc_reduce(cpl_frameset            * framelist,
                           const cpl_parameterlist * parlist)
{
    cpl_pluginlist * plugins = cpl_pluginlist_new();
    cpl_frameset * usedframes = cpl_frameset_new();

    cpl_recipe * repack = visir_init_recipe("visir_util_repack",
                                            &visir_util_repack_get_info,
                                            plugins);
    cpl_recipe * undist = visir_init_recipe("visir_util_undistort",
                                            &visir_util_undistort_get_info,
                                            plugins);
    cpl_recipe * destripe = visir_init_recipe("visir_old_util_destripe",
                                              &visir_old_util_destripe_get_info,
                                              plugins);
    cpl_recipe * spcobs = visir_init_recipe("visir_old_spc_obs",
                                            &visir_old_spc_obs_get_info,
                                            plugins);
    const cpl_boolean do_destripe =
        irplib_parameterlist_get_bool(parlist, PACKAGE,
                                      RECIPE_STRING, "destripe");

    cpl_frameset * repackset = cpl_frameset_new();
    cpl_frameset * obsset = cpl_frameset_new();
    cpl_size nrep;
    const char * input_tag;

    visir_dfs_set_groups(framelist);
    cpl_frame * linframe = NULL;
    FOR_EACH_FRAMESET(frame_, framelist) {
        cpl_frame * frame = cpl_frame_duplicate(frame_);
        cpl_frameset_insert(usedframes, cpl_frame_duplicate(frame));
        if (!strcmp(cpl_frame_get_tag(frame), VISIR_SPEC_STD_CAT_PROCATG) ||
            !strcmp(cpl_frame_get_tag(frame), VISIR_SPEC_CAL_LINES_PROCATG) ||
            !strcmp(cpl_frame_get_tag(frame), VISIR_SPEC_CAL_QEFF_PROCATG))
            cpl_frameset_insert(obsset, frame);
        else {
            /* linearity correction also passed to undistort */
            if (!strcmp(cpl_frame_get_tag(frame), VISIR_CALIB_LIN)) {
                linframe = cpl_frame_duplicate(frame);
            }
            cpl_frameset_insert(repackset, frame);
        }
    }
    nrep = cpl_frameset_get_size(repackset);
    if (cpl_frameset_find(repackset, VISIR_CALIB_LIN)) {
        nrep--;
    }
    if (cpl_frameset_find(repackset, VISIR_CALIB_STATIC_MASK)) {
        nrep--;
    }
    if (cpl_frameset_count_tags(repackset, VISIR_SPC_OBS_ECH_RAW) == nrep) {
        input_tag = VISIR_SPC_OBS_ECH_PP;
    }
    else if (cpl_frameset_count_tags(repackset,
                                     VISIR_SPC_PHOT_ECH_RAW) == nrep) {
        input_tag = VISIR_SPC_PHOT_ECH_PP;
    }
    else if (cpl_frameset_count_tags(repackset, VISIR_SPC_OBS_RAW) == nrep) {
        input_tag = VISIR_SPC_OBS_PP;
    }
    else if (cpl_frameset_count_tags(repackset, VISIR_SPC_PHOT_RAW) == nrep) {
        input_tag = VISIR_SPC_PHOT_PP;
    }
    else {
        error_if(1, CPL_ERROR_ILLEGAL_INPUT, "Invalid input tags");
    }
    cpl_msg_info(cpl_func, "Working on %s", input_tag);
    skip_if(visir_run_recipe(repack, repackset, parlist,
                             &util_repack_set_parameters));

    cpl_frameset * undistset = visir_prepare_frameset(repackset, NULL, 0,
                                                      CPL_TRUE);
    /* jitter correction modifies input calib file and adds a new one
     * to the recipe output, so drop the original one from the frameset */
    undistset = visir_remove_modified_calib(undistset);
    /* insert linearity table for skyframe*/
    if (linframe) {
        cpl_frameset_insert(undistset, linframe);
    }
    cpl_frameset_delete(repackset);
    repackset = NULL;
    skip_if(visir_run_recipe(undist, undistset, parlist,
                             &util_undistort_set_parameters));

    cpl_frameset * destripeset = undistset;
    if (do_destripe) {
        const char * tagmap[] = {"UNDISTORTED", VISIR_UTIL_CORRECTED};
        destripeset =
            visir_prepare_frameset(undistset, tagmap, ARRAY_LEN(tagmap),
                                   CPL_FALSE);
        skip_if(visir_run_recipe(destripe, destripeset, parlist,
                                 &visir_copy_parameters));
    }

    const char * obstagmap[] = {do_destripe ? "DESTRIPED" : "UNDISTORTED",
                                input_tag};
    cpl_frameset * tmp_obs =
        visir_prepare_frameset(destripeset, obstagmap, ARRAY_LEN(obstagmap),
                               CPL_FALSE);

    FOR_EACH_FRAMESET_C(frm, tmp_obs)
        cpl_frameset_insert(obsset, cpl_frame_duplicate(frm));
    cpl_frameset_delete(tmp_obs);

    cpl_frame * sky = cpl_frameset_find(undistset, "SPEC_OBS_LMR_SKYFRAME");
    skip_if(sky == NULL);
    sky = cpl_frame_duplicate(sky);
    cpl_frame_set_group(sky, CPL_FRAME_GROUP_CALIB);
    cpl_frameset_insert(obsset, sky);

    cpl_frameset_delete(undistset);

    skip_if(visir_run_recipe(spcobs, obsset, parlist,
                             &visir_copy_parameters));

    FOR_EACH_FRAMESET(frm, obsset)
        if (cpl_frame_get_group(frm) == CPL_FRAME_GROUP_PRODUCT)
            cpl_frameset_insert(framelist, cpl_frame_duplicate(frm));
    skip_if(0);

    end_skip;
    {
        cpl_recipe * recipes[] = {repack, undist, destripe, spcobs};
        for (size_t i = 0; i < ARRAY_LEN(recipes); i++)
            if (recipes[i])
                cpl_plugin_delete(&(recipes[i]->interface));
    }
    cpl_pluginlist_delete(plugins);

    cpl_frameset_delete(obsset);
    cpl_frameset_delete(usedframes);

    return cpl_error_get_code();
}
