/*
 * 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 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>
/* for strcasecmp */
#include <strings.h>

int visir_util_repack_get_info(cpl_pluginlist *);
int visir_util_detect_shift_get_info(cpl_pluginlist *);
int visir_util_clip_get_info(cpl_pluginlist *);
int visir_util_run_swarp_get_info(cpl_pluginlist *);
int visir_util_qc_get_info(cpl_pluginlist *);
int visir_old_img_phot_get_info(cpl_pluginlist *);
int visir_util_join_get_info(cpl_pluginlist *);
static cpl_error_code visir_img_reduce_fill_parameterlist(cpl_parameterlist *);
static int visir_img_reduce(cpl_frameset            *,
                           const cpl_parameterlist *);


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

#define RECIPE_STRING   "visir_img_reduce"
#define DEFAULT_CONFIG  VISIR_CONFIG_PATH "/visir_default.swarp"

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

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

static int visir_img_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_img_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_img_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_img_reduce);
    }
    else if (ESO_COVERAGE_BUILD || getenv("VISIR_NO_FORK") != NULL) {
        /* debugging and coverage is better without fork */
        return cpl_recipedefine_exec(plugin, visir_img_reduce)
            ? (int)cpl_error_set_where(cpl_func) : 0;
    }
    else
        return visir_forking_exec(RECIPE_STRING, plugin, visir_img_reduce);
}


/* The definition of the recipe destroy function */
static int visir_img_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_img_reduce",
                                "Combines a stack of chopped, jittered and/or "
                                "nodded exposures and can compute the "
                                "sensitivity for standard star observations",
                    "This recipe recombines the data observed in "
                    "chopping/nodding or\n"
                    "chopping or nodding modes into one combined image using "
                    "optionally\n"
                    "cross-correlation methods.\n"
                    "The files listed in the Set Of Frames (sof-file) "
                    "must be tagged:\n"
                    "VISIR-observation-file.fits " VISIR_IMG_COMBINE_CNJ " or\n"
                    "VISIR-observation-file.fits " VISIR_IMG_COMBINE_CJ " or\n"
                    "VISIR-observation-file.fits " VISIR_IMG_COMBINE_NJ " or\n"
                    "VISIR-observation-file.fits " VISIR_IMG_COMBINE_DJ " or\n"
                    "VISIR-observation-file.fits BURST\n"
                    "VISIR-Standard-star-catalog " VISIR_CALIB_STDSTAR_IMG " (optional)\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_img_reduce_create,
                                visir_img_reduce_exec,
                                visir_img_reduce_destroy)
        ? ((void)cpl_error_set_where(cpl_func), 1) : 0;
}

/* FIXME:
cpl_recipe_define(visir_img_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_img_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_img_reduce_fill_parameterlist(cpl_parameterlist * self)
{
    cpl_pluginlist * plugins = cpl_pluginlist_new();
    const char * context = PACKAGE ".visir_img_reduce";

    cpl_recipe * repack = visir_init_recipe("visir_util_repack",
                                            &visir_util_repack_get_info,
                                            plugins);
    cpl_recipe * shift = visir_init_recipe("visir_util_detect_shift",
                                           &visir_util_detect_shift_get_info,
                                           plugins);
    cpl_recipe * clip = visir_init_recipe("visir_util_clip",
                                          &visir_util_clip_get_info,
                                          plugins);
    cpl_recipe * swarp = visir_init_recipe("visir_util_run_swarp",
                                          &visir_util_run_swarp_get_info,
                                          plugins);
    cpl_recipe * phot = visir_init_recipe("visir_old_img_phot",
                                          &visir_old_img_phot_get_info,
                                          plugins);

    cpl_plugin_get_init(&repack->interface)(&repack->interface);
    cpl_plugin_get_init(&shift->interface)(&shift->interface);
    cpl_plugin_get_init(&clip->interface)(&clip->interface);
    cpl_plugin_get_init(&swarp->interface)(&swarp->interface);
    cpl_plugin_get_init(&phot->interface)(&phot->interface);
    cpl_parameterlist * repack_par = repack->parameters;
    cpl_parameterlist * clip_par = clip->parameters;
    cpl_parameterlist * shift_par = shift->parameters;
    cpl_parameterlist * swarp_par = swarp->parameters;
    cpl_parameterlist * phot_par = phot->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(shift_par);
         p != NULL; p = cpl_parameterlist_get_next(shift_par)) {
        cpl_parameterlist_append(self, visir_parameter_duplicate(p));
    }

    skip_if(0);

    for (cpl_parameter * p = cpl_parameterlist_get_first(clip_par);
         p != NULL; p = cpl_parameterlist_get_next(clip_par)) {
        const char * name = cpl_parameter_get_name(p);
        if (strstr(name, "error-out-type"))
            continue;
        cpl_parameterlist_append(self, visir_parameter_duplicate(p));
    }

    skip_if(0);

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

    skip_if(0);

    for (cpl_parameter * p = cpl_parameterlist_get_first(phot_par);
         p != NULL; p = cpl_parameterlist_get_next(phot_par)) {
        const char * name = cpl_parameter_get_name(p);
        if (strstr(name, "jy_val") || strstr(name, "radii"))
            cpl_parameterlist_append(self, visir_parameter_duplicate(p));
    }

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

    cpl_parameterlist_delete(repack->parameters);
    cpl_parameterlist_delete(shift->parameters);
    cpl_parameterlist_delete(clip->parameters);
    cpl_parameterlist_delete(swarp->parameters);
    cpl_parameterlist_delete(phot->parameters);
    cpl_plugin_delete(&repack->interface);
    cpl_plugin_delete(&shift->interface);
    cpl_plugin_delete(&clip->interface);
    cpl_plugin_delete(&swarp->interface);
    cpl_plugin_delete(&phot->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, "chopnod");

    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
check_swarp(void)
{
    int ret = system(VISIR_SWARP_BIN " -v > /dev/null 2>/dev/null");
    if (ret != 0) {
        const char * path = getenv("PATH");
        return cpl_error_set_message(cpl_func, CPL_ERROR_UNSUPPORTED_MODE,
                                     VISIR_SWARP_BIN " not found in PATH: %s",
                                     path ? path : "<NULL>");
    }
    return CPL_ERROR_NONE;
}


static cpl_frame *
run_phot(cpl_recipe * phot, cpl_frameset *photset,
         const cpl_parameterlist * parlist,
         cpl_frame * imgf_, cpl_frame * wgtf_,
         cpl_frame * qcf_,
         const int idx)
{
    cpl_frame * imgf = cpl_frame_duplicate(imgf_);
    cpl_frame * wgtf = cpl_frame_duplicate(wgtf_);
    cpl_frame * qcf = cpl_frame_duplicate(qcf_);
    cpl_frame * res = NULL;
    char buffer[128];

    skip_if(0);

    cpl_frame_set_tag(imgf, VISIR_IMG_CAL_PHOT_PP);
    cpl_frameset_insert(photset, imgf);
    cpl_frame_set_tag(wgtf, VISIR_UTIL_WEIGHT_MAP_PROCATG);
    cpl_frameset_insert(photset, wgtf);
    cpl_frame_set_group(qcf, CPL_FRAME_GROUP_RAW);
    cpl_frame_set_tag(qcf, VISIR_UTIL_QC_PROCATG);
    cpl_frameset_insert(photset, qcf);

    skip_if(visir_run_recipe(phot, photset, parlist, &visir_copy_parameters));
    res = cpl_frameset_find(photset, VISIR_IMG_PHOT_COMBINED_PROCATG);
    if (res == NULL)
        res = cpl_frameset_find(photset, VISIR_IMG_PHOT_ONEBEAM_PROCATG);

    sprintf(buffer, "visir_img_phot_%03d.fits", idx);
    skip_if(rename(cpl_frame_get_filename(res), buffer) == -1);
    cpl_frame_set_filename(res, buffer);

    end_skip;

    return res;
}

/*----------------------------------------------------------------------------*/
/**
  @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_img_reduce(cpl_frameset            * framelist,
                           const cpl_parameterlist * parlist)
{
    cpl_errorstate cleanstate = cpl_errorstate_get();
    cpl_frameset * repackset = cpl_frameset_new();
    cpl_frameset * photset = cpl_frameset_new();
    cpl_recipe * repack = NULL;
    cpl_recipe * shift = NULL;
    cpl_recipe * clip = NULL;
    cpl_recipe * qc = NULL;
    cpl_recipe * swarp = NULL;
    cpl_recipe * join = NULL;
    cpl_recipe * phot = NULL;
    cpl_pluginlist * plugins = cpl_pluginlist_new();
    cpl_frameset * usedframes = cpl_frameset_new();
    cpl_frame * meanfrm = NULL;

    skip_if(check_swarp());


    repack = visir_init_recipe("visir_util_repack",
                               &visir_util_repack_get_info, plugins);
    shift = visir_init_recipe("visir_util_detect_shift",
                              &visir_util_detect_shift_get_info, plugins);
    clip = visir_init_recipe("visir_util_clip",
                             &visir_util_clip_get_info, plugins);
    qc = visir_init_recipe("visir_util_qc",
                           &visir_util_qc_get_info, plugins);
    swarp = visir_init_recipe("visir_util_run_swarp",
                              &visir_util_run_swarp_get_info, plugins);
    join = visir_init_recipe("visir_util_join",
                             &visir_util_join_get_info, plugins);

    visir_dfs_set_groups(framelist);

    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_IMA_STD_CAT_PROCATG) == 0)
            cpl_frameset_insert(photset, frame);
        else
            cpl_frameset_insert(repackset, frame);
    }
    skip_if(visir_run_recipe(repack, repackset, parlist,
                             &util_repack_set_parameters));
    meanfrm = cpl_frame_duplicate(cpl_frameset_find(repackset,
                                            VISIR_UTIL_REPACK_MEAN_PROCATG));


    cpl_frameset * shiftset = 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 */
    shiftset = visir_remove_modified_calib(shiftset);
    cpl_frameset_delete(repackset);
    repackset = NULL;
    skip_if(visir_run_recipe(shift, shiftset, parlist, &visir_copy_parameters));
    shiftset = visir_remove_modified_calib(shiftset);


    const char * tagmap[] = {VISIR_UTIL_DETECT_SHIFT_PROCATG, VISIR_UTIL_INPUTS_RAW};
    cpl_frameset * clipset =
        visir_prepare_frameset(shiftset, tagmap, ARRAY_LEN(tagmap), CPL_FALSE);
    skip_if(visir_run_recipe(clip, clipset, parlist, &visir_copy_parameters));


    const char * qctagmap[] = {VISIR_UTIL_DETECT_SHIFT_PROCATG, VISIR_UTIL_CORRECTED};
    cpl_frameset * qcset = visir_prepare_frameset(shiftset, qctagmap,
                                                  ARRAY_LEN(qctagmap),
                                                  CPL_FALSE);
    skip_if(visir_run_recipe(qc, qcset, parlist, &visir_copy_parameters));


    const char * swarptagmap[] = {VISIR_UTIL_DETECT_SHIFT_PROCATG, VISIR_UTIL_CORRECTED};
    cpl_frameset * swarpset = visir_prepare_frameset(shiftset, swarptagmap,
                                                     ARRAY_LEN(swarptagmap),
                                                     CPL_FALSE);
    cpl_frameset_delete(shiftset);
    shiftset = NULL;
    FOR_EACH_FRAMESET(frm, clipset) {
        if (strcmp(cpl_frame_get_tag(frm), VISIR_UTIL_ERROR_MAP) != 0)
            continue;
        cpl_frame * frame = cpl_frame_duplicate(frm);
        cpl_frame_set_group(frame, CPL_FRAME_GROUP_RAW);
        cpl_frameset_insert(swarpset, frame);
    }
    cpl_frameset_delete(clipset);
    clipset = NULL;
    skip_if(visir_run_recipe(swarp, swarpset, parlist, &visir_copy_parameters));
    irplib_framelist * swarpfl = irplib_framelist_cast(swarpset);
    irplib_framelist * qcfl = irplib_framelist_cast(qcset);

    irplib_framelist * imgs = irplib_framelist_extract(swarpfl, "COADDED_IMAGE");
    irplib_framelist * wgts = irplib_framelist_extract(swarpfl, "COADDED_WEIGHT");
    irplib_framelist * qcs = irplib_framelist_extract(qcfl, VISIR_UTIL_QC_PROCATG);
    if (cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND) {
        imgs = irplib_framelist_new();
        wgts = irplib_framelist_new();
        qcs = irplib_framelist_new();
        cpl_errorstate_set(cleanstate);
    }
    cpl_frame * coadd_imgf = cpl_frameset_find(swarpset, VISIR_IMG_COADDED_IMG);
    cpl_frame * coadd_wgtf = cpl_frameset_find(swarpset, VISIR_IMG_COADDED_WGT);
    skip_if(coadd_imgf == NULL || coadd_wgtf == NULL);


    if (cpl_frameset_get_size(photset) != 0) {
        phot = visir_init_recipe("visir_old_img_phot",
                                 &visir_old_img_phot_get_info, plugins);
        cpl_frameset *orig_phot = cpl_frameset_duplicate(photset);
        run_phot(phot, photset, parlist, coadd_imgf, coadd_wgtf,
                 cpl_frameset_find(qcset, VISIR_UTIL_QC_PROCATG"_COMBINED"), 0);
        skip_if(0);

        for (int i = 0; i < irplib_framelist_get_size(imgs); i++) {
            cpl_frame * imgf = irplib_framelist_get(imgs, i);
            cpl_frame * wgtf = irplib_framelist_get(wgts, i);
            cpl_frame * qcf = irplib_framelist_get(qcs, i);
            cpl_frameset *copy = cpl_frameset_duplicate(orig_phot);
            cpl_frame * res = run_phot(phot, copy, parlist, imgf, wgtf, qcf, i + 1);
            cpl_frameset_insert(photset, res);
            skip_if(0);
        }
    }

    {
        cpl_frame * wgtf = cpl_frame_duplicate(coadd_wgtf);
        cpl_frameset * joinset = cpl_frameset_new();
        if (cpl_frameset_get_size(photset) != 0) {
            FOR_EACH_FRAMESET(frm, photset) {
                if (strcmp(cpl_frame_get_tag(frm),
                           VISIR_IMG_PHOT_COMBINED_PROCATG) == 0 ||
                    strcmp(cpl_frame_get_tag(frm),
                           VISIR_IMG_PHOT_ONEBEAM_PROCATG) == 0) {
                    cpl_frame * dfrm = cpl_frame_duplicate(frm);
                    cpl_frameset_insert(joinset, dfrm);
                }
            }

        }
        else {
            cpl_frame * imgf = cpl_frame_duplicate(coadd_imgf);
            cpl_frame * qcf =
                cpl_frameset_find(qcset, VISIR_UTIL_QC_PROCATG"_COMBINED");
            qcf = cpl_frame_duplicate(qcf);
            cpl_frameset_insert(joinset, imgf);
            for (int i = 0; i < irplib_framelist_get_size(imgs); i++) {
                imgf = cpl_frame_duplicate(irplib_framelist_get(imgs, i));
                cpl_frameset_insert(joinset, imgf);
            }
            cpl_frame_set_tag(qcf, VISIR_UTIL_QC_PROCATG);
            cpl_frame_set_group(qcf, CPL_FRAME_GROUP_RAW);
            cpl_frameset_insert(joinset, qcf);
            for (int i = 0; i < irplib_framelist_get_size(qcs); i++) {
                qcf = cpl_frame_duplicate(irplib_framelist_get(qcs, i));
                cpl_frame_set_group(qcf, CPL_FRAME_GROUP_RAW);
                cpl_frameset_insert(joinset, qcf);
            }
        }

        cpl_frame_set_tag(wgtf, VISIR_UTIL_WEIGHT_MAP_PROCATG);
        cpl_frameset_insert(joinset, wgtf);
        for (int i = 0; i < irplib_framelist_get_size(wgts); i++) {
            wgtf = cpl_frame_duplicate(irplib_framelist_get(wgts, i));
            cpl_frame_set_tag(wgtf, VISIR_UTIL_WEIGHT_MAP_PROCATG);
            cpl_frameset_insert(joinset, wgtf);
        }

        skip_if(visir_run_recipe(join, joinset, parlist,
                                 &visir_copy_parameters));

        FOR_EACH_FRAMESET(frm, joinset)
            if (cpl_frame_get_group(frm) == CPL_FRAME_GROUP_PRODUCT)
                cpl_frameset_insert(framelist, cpl_frame_duplicate(frm));
        cpl_frameset_delete(joinset);
        cpl_frameset_insert(framelist, meanfrm);
        meanfrm = NULL;
        skip_if(0);
    }

    end_skip;

    {
        cpl_recipe * recipes[] = {repack, shift, clip, qc, swarp, phot, join};
        for (size_t i = 0; i < ARRAY_LEN(recipes); i++)
            if (recipes[i])
                cpl_plugin_delete(&(recipes[i]->interface));
    }
    cpl_pluginlist_delete(plugins);

    cpl_frame_delete(meanfrm);
    cpl_frameset_delete(photset);
    cpl_frameset_delete(usedframes);

    return cpl_error_get_code();
}
