/* $Id: visir_util_join.c,v 1.2 2011/07/28 15:02:10 llundin Exp $
 *
 * This file is part of the VISIR Pipeline
 * Copyright (C) 2011 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/07/28 15:02:10 $
 * $Revision: 1.2 $
 * $Name: visir-3_5_1 $
 */

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

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

#include "visir_recipe.h"

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

#define RECIPE_STRING   "visir_util_join"

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

static
cpl_error_code visir_util_join_one(cpl_frameset *,
                                   const irplib_framelist *,
                                   const irplib_framelist *,
                                   const irplib_framelist *,
                                   const irplib_framelist *,
                                   const irplib_framelist *,
                                   int, cpl_boolean, cpl_boolean, 
                                   cpl_boolean, cpl_boolean,
                                   const cpl_parameterlist *);

VISIR_RECIPE_DEFINE(visir_util_join, 0,
                    "Extend the final product with extensions containing its "
                    "error map, bad pixel map and weight or contribution map",
                    "The files listed in the Set Of Frames (SOF-file) "
                    "must be tagged:\n"
                    "VISIR-data.fits "          VISIR_UTIL_DATA "\n"
                    "\nOptionally, the SOF may also contain one or more of "
                    "the following:\n"
                    "VISIR-bad-pixel-map.fits " VISIR_CALIB_BPM "\n"
                    "VISIR-error.fits "         VISIR_UTIL_WEIGHT2ERROR_PROCATG
                      "\n"
                    "VISIR-contribution-map.fits " VISIR_IMG_CLIPPED_MAP_PROCATG
                      "\n"
                    "VISIR-weight-map.fits "    VISIR_UTIL_WEIGHT2ERROR
                      "\n"
                    "\nThe product(s) will have a FITS card\n"
                    "'HIERARCH " CPL_DFS_PRO_CATG "' with a value of:\n"
                    VISIR_UTIL_JOIN_PROCATG);

/*----------------------------------------------------------------------------*/
/**
 * @defgroup visir_util_join   Join a product with its meta information
 */
/*----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
                                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 visir_util_join(cpl_frameset            * framelist,
                           const cpl_parameterlist * parlist)
{
#ifdef _OPENMP
    cpl_errorstate     cleanstate = cpl_errorstate_get();
#endif
    cpl_error_code     didfail   = CPL_ERROR_NONE;
    irplib_framelist * allframes = NULL;
    irplib_framelist * rawframes = NULL;
    irplib_framelist * bpmframes = NULL;
    irplib_framelist * errframes = NULL;
    irplib_framelist * conframes = NULL;
    irplib_framelist * wgtframes = NULL;
    int                i, n;
    int                nbad = 0, nerr = 0, ncon = 0, nwgt = 0;
    

    /* Identify the RAW and TAB frames in the input frameset */
    skip_if (visir_dfs_set_groups(framelist));

    /* Objects observation */
    allframes = irplib_framelist_cast(framelist);
    skip_if(allframes == NULL);

    rawframes = irplib_framelist_extract(allframes, VISIR_UTIL_DATA);
    skip_if(rawframes == NULL);

    n = irplib_framelist_get_size(rawframes);

    if (cpl_frameset_find_const(framelist, VISIR_CALIB_BPM)) {
        bpmframes = irplib_framelist_extract(allframes, VISIR_CALIB_BPM);
        skip_if (bpmframes == NULL);

        nbad = irplib_framelist_get_size(bpmframes);
        error_if(nbad != n && nbad != 1, CPL_ERROR_INCOMPATIBLE_INPUT,
                 "%d raw-frames <=> %d bpm frames", n, nbad);
    }

    if (cpl_frameset_find_const(framelist, VISIR_UTIL_WEIGHT2ERROR_PROCATG)) {
        errframes = irplib_framelist_extract(allframes,
                                             VISIR_UTIL_WEIGHT2ERROR_PROCATG);
        skip_if (errframes == NULL);

        nerr = irplib_framelist_get_size(errframes);
        error_if(nerr != n && nerr != 1, CPL_ERROR_INCOMPATIBLE_INPUT,
                 "%d raw-frames <=> %d error frames", n, nerr);

    }

    if (cpl_frameset_find_const(framelist, VISIR_IMG_CLIPPED_MAP_PROCATG)) {
        conframes = irplib_framelist_extract(allframes,
                                             VISIR_IMG_CLIPPED_MAP_PROCATG);
        skip_if (conframes == NULL);

        ncon = irplib_framelist_get_size(conframes);
        error_if(ncon % n != 0 && ncon != 1, CPL_ERROR_INCOMPATIBLE_INPUT,
                 "%d raw-frames <=> %d contribution frames", n, ncon);
    }

    if (cpl_frameset_find_const(framelist, VISIR_UTIL_WEIGHT2ERROR)) {
        wgtframes = irplib_framelist_extract(allframes,
                                             VISIR_UTIL_WEIGHT2ERROR);
        skip_if (wgtframes == NULL);

        nwgt = irplib_framelist_get_size(wgtframes);
        error_if(nwgt != n && nwgt != 1, CPL_ERROR_INCOMPATIBLE_INPUT,
                 "%d raw-frames <=> %d weight frames", n, nwgt);
    }

#ifdef _OPENMP
#pragma omp parallel for private(i)
#endif
    for (i = 0; i < n; i++) {
        if (!didfail) {

            /* The total number of iterations must be pre-determined for the
               parallelism to work. In case of an error we can therefore not
               break, so instead we skip immediately to the next iteration.
               FIXME: This check on didfail does not guarantee that only one
               iteration can cause an error to be dumped, but it is not
               worse than checking on a thread-local state, e.g. errori. */

            cpl_msg_info(cpl_func, "Joining frame %d/%d", 1+i, n);

            if (visir_util_join_one(framelist, rawframes, bpmframes, errframes,
                                    conframes, wgtframes, i, nbad == 1,
                                    nerr == 1, ncon == 1, nwgt == 1,
                                    parlist)) {
            const cpl_error_code errori = cpl_error_set_where(cpl_func);
#ifdef _OPENMP
                /* Cannot access these errors after the join,
                   so dump them now. :-(((((((((((((((((((( */
                cpl_errorstate_dump(cleanstate, CPL_FALSE, NULL);
                cpl_errorstate_set(cleanstate);
#pragma omp critical(visir_util_join)
#endif
                didfail = errori;
            }
        }
    }

    error_if(didfail, didfail, "Failed to process %d frame(s)", n);

    end_skip;

    irplib_framelist_delete(allframes);
    irplib_framelist_delete(rawframes);
    irplib_framelist_delete(bpmframes);
    irplib_framelist_delete(errframes);
    irplib_framelist_delete(conframes);
    irplib_framelist_delete(wgtframes);

    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Process the i'th frame with its meta information
  @param    framelist   The frameset to append products to
  @param    rawframes   The object frames list
  @param    bpmframes   The bpm frames list
  @param    errframes   The error frames list
  @param    conframes   The contribution frames list
  @param    wgtframes   The weight frames list
  @param    i           The frame to process (0 for first)
  @param    bshared     CPL_TRUE iff the bpm is shared among all raw frames
  @param    eshared     CPL_TRUE iff the error is shared among all raw frames
  @param    cshared     CPL_TRUE iff the contrib is shared among all raw frames
  @param    wshared     CPL_TRUE iff the weight is shared among all raw frames
  @param    parlist     The parameters list
  @return   CPL_ERROR_NONE iff OK, otherwise the relevant CPL error code.
 */
/*----------------------------------------------------------------------------*/
static
cpl_error_code visir_util_join_one(cpl_frameset * framelist,
                                   const irplib_framelist * rawframes,
                                   const irplib_framelist * bpmframes,
                                   const irplib_framelist * errframes,
                                   const irplib_framelist * conframes,
                                   const irplib_framelist * wgtframes,
                                   int i,
                                   cpl_boolean bshared, cpl_boolean eshared,
                                   cpl_boolean cshared, cpl_boolean wshared,
                                   const cpl_parameterlist * parlist)
{

    const int          n = irplib_framelist_get_size(rawframes);
    const cpl_frame  * frame;
    cpl_frameset     * products   = cpl_frameset_new();
    cpl_frameset     * usedframes = cpl_frameset_new();
    const char       * filename;
    cpl_propertylist * plist      = NULL;
    char             * proname    = cpl_sprintf(RECIPE_STRING "_%d"
                                                CPL_DFS_FITS, 1+i);
    cpl_image        * image      = NULL;
    cpl_image        * csum       = NULL;
    cpl_mask         * bpm        = NULL; 

    bug_if(cpl_frameset_insert(usedframes, cpl_frame_duplicate
                               (irplib_framelist_get_const(rawframes, i))));

    if (bpmframes != NULL) {
        frame = irplib_framelist_get_const(bpmframes, bshared ? 0 : i);
        bug_if(cpl_frameset_insert(usedframes, cpl_frame_duplicate(frame)));
    }

    if (errframes != NULL) {
        frame = irplib_framelist_get_const(errframes, eshared ? 0 : i);
        bug_if(cpl_frameset_insert(usedframes, cpl_frame_duplicate(frame)));
    }

    if (conframes != NULL && (irplib_framelist_get_size(conframes) == n ||
                              cshared)) {
        frame = irplib_framelist_get_const(conframes, cshared ? 0 : i);
        bug_if(cpl_frameset_insert(usedframes, cpl_frame_duplicate(frame)));
    }

    if (wgtframes != NULL) {
        frame = irplib_framelist_get_const(wgtframes, wshared ? 0 : i);
        bug_if(cpl_frameset_insert(usedframes, cpl_frame_duplicate(frame)));
    }

    /* 1) The data frame */
    frame = irplib_framelist_get_const(rawframes, i);
    filename = cpl_frame_get_filename(frame);

    plist = cpl_propertylist_load(filename, 0);
    skip_if(plist == NULL);

    /* FIXME: Change to CPL_TYPE_UNSPECIFIED when supported */
    image = cpl_image_load(filename, CPL_TYPE_FLOAT, 0, 0);
    skip_if(image == NULL);

    /* FIXME: Change to CPL_TYPE_UNSPECIFIED when supported */
    skip_if(irplib_dfs_save_image(products, parlist, usedframes,
                                  image, CPL_BPP_IEEE_FLOAT,
                                  RECIPE_STRING,
                                  VISIR_UTIL_JOIN_PROCATG, plist,
                                  NULL, visir_pipe_id, proname));

    if (bpmframes != NULL) {
        /* 2) The bpm frame */

        frame = irplib_framelist_get_const(bpmframes, bshared ? 0 : i);

        filename = cpl_frame_get_filename(frame);

        cpl_propertylist_delete(plist);
        plist = cpl_propertylist_load_regexp(filename, 0, " ESO QC | ESO PRO ",
                                             0);
        skip_if(plist == NULL);

        bug_if(cpl_propertylist_append_string(plist, "EXTNAME",
                                              "BAD PIXEL MAP"));

        /* FIXME: Change to CPL_TYPE_UNSPECIFIED when supported */
        cpl_image_delete(image);
        image = cpl_image_load(filename, CPL_TYPE_INT, 0, 0);
        skip_if(image == NULL);

        /* FIXME: Change to CPL_TYPE_UNSPECIFIED when supported */
        skip_if(cpl_image_save(image, proname, CPL_BPP_8_UNSIGNED,
                               plist, CPL_IO_EXTEND));
    }

    if (errframes != NULL) {
        /* 3) The error frame */

        frame = irplib_framelist_get_const(errframes, eshared ? 0 : i);

        filename = cpl_frame_get_filename(frame);

        cpl_propertylist_delete(plist);
        plist = cpl_propertylist_load_regexp(filename, 0, " ESO QC | ESO PRO ",
                                             0);
        skip_if(plist == NULL);

        bug_if(cpl_propertylist_append_string(plist, "EXTNAME", "ERROR MAP"));

        /* FIXME: Change to CPL_TYPE_UNSPECIFIED when supported */
        cpl_image_delete(image);
        image = cpl_image_load(filename, CPL_TYPE_FLOAT, 0, 0);
        skip_if(image == NULL);

        /* FIXME: Change to CPL_TYPE_UNSPECIFIED when supported */
        skip_if(cpl_image_save(image, proname, CPL_BPP_IEEE_FLOAT,
                               plist, CPL_IO_EXTEND));

        if (conframes == NULL && bpmframes == NULL) {

            cpl_propertylist_empty(plist);
            bug_if(cpl_propertylist_append_string(plist, "EXTNAME",
                                                  "BAD PIXEL MAP"));

            /* FIXME: Find a better way to create a image BPM */
            bpm = cpl_mask_threshold_image_create(image, -DBL_EPSILON, 1e8);
            cpl_mask_not(bpm);
            cpl_image_delete(image);
            image = cpl_image_new_from_mask(bpm);

            skip_if(cpl_image_save(image, proname, CPL_BPP_8_UNSIGNED,
                                   plist, CPL_IO_EXTEND));
        }

    }


    if (conframes != NULL) {
        /* 4) The contribution frame(s) */

        if (cshared || irplib_framelist_get_size(conframes) == n) {

            frame = irplib_framelist_get_const(conframes, cshared ? 0 : i);

            filename = cpl_frame_get_filename(frame);

            cpl_propertylist_delete(plist);
            plist = cpl_propertylist_load_regexp(filename, 0,
                                                 " ESO QC | ESO PRO ", 0);
            skip_if(plist == NULL);

            /* FIXME: Change to CPL_TYPE_UNSPECIFIED when supported */
            cpl_image_delete(image);
            image = cpl_image_load(filename, CPL_TYPE_INT, 0, 0);
            skip_if(image == NULL);

        } else {

            const int    ncon = irplib_framelist_get_size(conframes);
            const int    nz   = ncon / n;
            int          j;

            for (j = i * nz; j < i * nz + nz; j ++) {
                frame = irplib_framelist_get_const(conframes, j);
                bug_if(cpl_frameset_insert(usedframes,
                                           cpl_frame_duplicate(frame)));

                filename = cpl_frame_get_filename(frame);

                if (j == i * nz) {

                    cpl_image_delete(image);
                    image = cpl_image_load(filename, CPL_TYPE_INT, 0, 0);
                    skip_if(image == NULL);

                } else {
                    csum = cpl_image_load(filename, CPL_TYPE_INT, 0, 0);
                    skip_if(csum == NULL);
                    skip_if(cpl_image_add(image, csum));
                }
            }

            cpl_propertylist_empty(plist);
        }

        bug_if(cpl_propertylist_append_string(plist, "EXTNAME",
                                              "CONTRIBUTION MAP"));

        /* FIXME: Change to CPL_TYPE_UNSPECIFIED when supported */
        skip_if(cpl_image_save(image, proname, CPL_BPP_32_SIGNED,
                               plist, CPL_IO_EXTEND));

        if (bpmframes == NULL) {

            bug_if(cpl_propertylist_update_string(plist, "EXTNAME",
                                                  "BAD PIXEL MAP"));

            /* FIXME: Find a better way to create a image BPM */
            bug_if(cpl_image_threshold(image, -0.5, 1.0, 0.5, 1.0));
            bug_if(cpl_image_multiply_scalar(image, -1.0));
            bug_if(cpl_image_add_scalar(image, 1.0));
            skip_if(cpl_image_save(image, proname, CPL_BPP_8_UNSIGNED,
                                   plist, CPL_IO_EXTEND));
        }
    }

    if (wgtframes != NULL) {
        /* 5) The weight frame */

        frame = irplib_framelist_get_const(wgtframes, wshared ? 0 : i);

        filename = cpl_frame_get_filename(frame);

        cpl_propertylist_delete(plist);
        plist = cpl_propertylist_load_regexp(filename, 0, " ESO QC | ESO PRO ",
                                             0);
        skip_if(plist == NULL);

        bug_if(cpl_propertylist_append_string(plist, "EXTNAME", "WEIGHT MAP"));

        /* FIXME: Change to CPL_TYPE_UNSPECIFIED when supported */
        cpl_image_delete(image);
        image = cpl_image_load(filename, CPL_TYPE_FLOAT, 0, 0);
        skip_if(image == NULL);

        /* FIXME: Change to CPL_TYPE_UNSPECIFIED when supported */
        skip_if(cpl_image_save(image, proname, CPL_BPP_IEEE_FLOAT,
                               plist, CPL_IO_EXTEND));
    }

    for (frame = cpl_frameset_get_first_const(products);
         frame != NULL;
         frame = cpl_frameset_get_next_const(products)) {
        cpl_frame * copy = cpl_frame_duplicate(frame);
        cpl_error_code error;

#ifdef _OPENMP
#pragma omp critical(visir_util_join_one)
#endif
        error = cpl_frameset_insert(framelist, copy);

        if (error) break;
    }
    bug_if(frame != NULL);

    end_skip;

    cpl_image_delete(image);
    cpl_mask_delete(bpm);
    cpl_free(proname);
    cpl_propertylist_delete(plist);
    cpl_frameset_delete(usedframes);
    cpl_frameset_delete(products);

    return cpl_error_get_code();
}
