/*
 * This file is part of the GIRAFFE Pipeline
 * Copyright (C) 2002-2019 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

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

#include <math.h>

#include <cxmessages.h>
#include <cxmemory.h>
#include <cxlist.h>

#include <cpl_type.h>
#include <cpl_recipe.h>
#include <cpl_plugininfo.h>
#include <cpl_parameterlist.h>
#include <cpl_frameset.h>
#include <cpl_propertylist.h>
#include <cpl_msg.h>

#include "gialias.h"
#include "giframe.h"
#include "giimage.h"
#include "gifibers.h"
#include "gibias.h"
#include "gimath.h"
#include "gistacking.h"
#include "giqclog.h"
#include "gierror.h"
#include "giutils.h"


#define GIMASTERBIAS_BIAS_EXTENSION_IMG 0
#define GIMASTERBIAS_BIAS_EXTENSION_PL 0
#define GIMASTERBIAS_BAD_PIXEL_EXTENSION 0


static cxint giframestack(cpl_parameterlist*, cpl_frameset*);


/*
 * Create the recipe instance, i.e. setup the parameter list for this
 * recipe and make it availble to the application using the interface.
 */

static cxint
giframestack_create(cpl_plugin* plugin)
{

    cpl_recipe* recipe = (cpl_recipe*)plugin;

    giraffe_error_init();


    /*
     * We have to provide the option we accept to the application. We
     * need to setup our parameter list and hook it into the recipe
     * interface.
     */

    recipe->parameters = cpl_parameterlist_new();
    cx_assert(recipe->parameters != NULL);

    /*
     * Fill the parameter list.
     */

    giraffe_stacking_config_add(recipe->parameters);

    return 0;

}


/*
 * Execute the plugin instance given by the interface.
 */

static cxint
giframestack_exec(cpl_plugin* plugin)
{

    cpl_recipe* recipe = (cpl_recipe*)plugin;

    cxint status = 0;


    if (recipe->parameters == NULL || recipe->frames == NULL) {
        return 1;
    }

    status = giframestack(recipe->parameters, recipe->frames);

    if (status != 0) {
        return 1;
    }

    return 0;

}


static cxint
giframestack_destroy(cpl_plugin* plugin)
{

    cpl_recipe* recipe = (cpl_recipe*)plugin;


    /*
     * We just destroy what was created during the plugin initialization
     * phase, i.e. the parameter list. The frame set is managed by the
     * application which called us, so we must not touch it,
     */

    cpl_parameterlist_delete(recipe->parameters);

    giraffe_error_clear();

    return 0;

}

/*
 * The actual recipe starts here.
 */

static cxint
giframestack(cpl_parameterlist* config, cpl_frameset* set)
{

    const cxchar* const _id = "giframestack";


    cxint i = 0;
    cxint status = 0;

    cxdouble exptime = 0.;

    cx_string *tag = NULL;

    cx_list *frames = NULL;
    cx_list *images = NULL;

    cx_list_iterator position;

    cpl_size count = 0;

    cpl_propertylist *properties = NULL;

    cpl_frame* frame = NULL;

    cpl_frameset_iterator *it = NULL;

    GiImage *image  = NULL;
    GiImage *result = NULL;
    GiImage **stack = NULL;

    GiStackingConfig *setup = NULL;

    GiRecipeInfo info = {(cxchar*)_id, 1, NULL, config};



    setup = giraffe_stacking_config_create(config);

    if (setup == NULL) {
        cpl_msg_error(_id, "Invalid parameter list! Aborting ...");
        return 1;
    }


    /*
     * Create the list of frames to combine.
     */

    /*
     * Search for the first image in the input frameset. This is
     * used as reference in the selection of the other frames.
     */

    cpl_msg_info(_id, "Searching for frames to combine ...");

    it = cpl_frameset_iterator_new(set);

    frame = cpl_frameset_iterator_get(it);

    if (frame == NULL) {
        cpl_msg_error(_id, "Empty input frameset encountered!");

        cpl_frameset_iterator_delete(it);
        it = NULL;

        giraffe_stacking_config_destroy(setup);
        setup = NULL;

        return 1;
    }


    image = giraffe_image_new(CPL_TYPE_DOUBLE);

    status = giraffe_image_load(image, cpl_frame_get_filename(frame), 0);

    while (status != 0 && count < cpl_frameset_get_size(set)) {

        cpl_frameset_iterator_advance(it, 1);

        frame = cpl_frameset_iterator_get(it);

        status = giraffe_image_load(image, cpl_frame_get_filename(frame), 0);
        ++count;

    }

    cpl_frameset_iterator_delete(it);
    it = NULL;

    if (count == cpl_frameset_get_size(set)) {
        cpl_msg_error(_id, "The input frameset does not contain any "
                      "images.");

        giraffe_image_delete(image);
        image = NULL;

        giraffe_stacking_config_destroy(setup);
        setup = NULL;

        return 1;
    }

    tag = cx_string_create(cpl_frame_get_tag(frame));


    /*
     * Select frames for combination. Only frames with the same tag
     * and the same size as the first raw frame are combined. All
     * other frames are ignored.
     */

    cpl_msg_info(_id, "Selecting '%s' frames for combination.",
                 cx_string_get(tag));

    frames = cx_list_new();
    cx_list_push_back(frames, frame);

    images = cx_list_new();
    cx_list_push_back(images, image);


    frame = cpl_frameset_find(set, cx_string_get(tag));

    if (frame == cx_list_front(frames)) {
        frame = cpl_frameset_find(set, NULL);
    }

    while (frame != NULL) {

        if (frame != cx_list_front(frames)) {

            GiImage* _image = giraffe_image_new(CPL_TYPE_DOUBLE);


            status = giraffe_image_load(_image,
                                        cpl_frame_get_filename(frame), 0);

            if (status == 0) {

                cxint nx = cpl_image_get_size_x(giraffe_image_get(_image));
                cxint ny = cpl_image_get_size_y(giraffe_image_get(_image));

                if (nx == cpl_image_get_size_x(giraffe_image_get(image)) &&
                    ny == cpl_image_get_size_y(giraffe_image_get(image))) {

                    cx_list_push_back(frames, frame);
                    cx_list_push_back(images, _image);

                }
                else {

                    cpl_msg_warning(_id, "Ignoring frame '%s' because of "
                                    "different size!",
                                    cpl_frame_get_filename(frame));

                    giraffe_image_delete(_image);
                    _image = NULL;

                }

            }
            else {
                giraffe_image_delete(_image);
                _image = NULL;
            }

        }

        frame = cpl_frameset_find(set, NULL);

    }


    /*
     * Check whether there are sufficient raw frames present in the
     * set for the selected combination method.
     */

    count = cx_list_size(images);

    if (count < setup->min_nr_frames) {

        cpl_msg_error(_id, "Not enough frames (%" CPL_SIZE_FORMAT
                      "). Stacking method '%d' requires at least %d frames! "
                      "Aborting...", count, setup->stackmethod,
                      setup->min_nr_frames);

        cx_list_destroy(images, (cx_free_func)giraffe_image_delete);
        images = NULL;

        cx_list_delete(frames);
        frames = NULL;

        cx_string_delete(tag);
        tag = NULL;

        giraffe_stacking_config_destroy(setup);
        setup = NULL;

        return 1;

    }


    /*
     * Combine the selected frames
     */

    cpl_msg_info(_id, "Combining %" CPL_SIZE_FORMAT " frames (%s) ...", count,
                 cx_string_get(tag));

    stack = cx_calloc(count + 1, sizeof(GiImage*));

    i = 0;
    position = cx_list_begin(images);

    while (position != cx_list_end(images)) {
        stack[i] = cx_list_get(images, position);
        position = cx_list_next(images, position);
        ++i;
    }

    result = giraffe_stacking_stack_images(stack, setup);

    if (result == NULL) {

        cpl_msg_error(_id,"Frame combination failed! Aborting ...");

        cx_free(stack);
        stack = NULL;

        cx_list_destroy(images, (cx_free_func)giraffe_image_delete);
        images = NULL;

        cx_list_delete(frames);
        frames = NULL;

        cx_string_delete(tag);
        tag = NULL;

        giraffe_stacking_config_destroy(setup);
        setup = NULL;

        return 1;

    }

    cx_free(stack);
    stack = NULL;

    giraffe_stacking_config_destroy(setup);
    setup = NULL;


    /*
     * Update the properties of the combined result frame.
     */

    cpl_msg_info(_id, "Updating combined frame properties ...");

    properties = giraffe_image_get_properties(cx_list_front(images));
    cx_assert(properties != NULL);

    giraffe_image_set_properties(result, properties);
    properties = giraffe_image_get_properties(result);

    if (properties == NULL) {

        cpl_msg_error(_id, "Updating combined frame properties failed!");

        giraffe_image_delete(result);
        result = NULL;

        cx_list_destroy(images, (cx_free_func)giraffe_image_delete);
        images = NULL;

        cx_list_delete(frames);
        frames = NULL;

        cx_string_delete(tag);
        tag = NULL;

        return 1;

    }

    giraffe_error_push();

    cpl_propertylist_update_double(properties, GIALIAS_BZERO, 0.);
    cpl_propertylist_update_double(properties, GIALIAS_CRPIX1, 1.);

    exptime = 0.;
    position = cx_list_begin(images);

    while (position != cx_list_end(images)) {

        cpl_propertylist* p =
            giraffe_image_get_properties(cx_list_get(images, position));

        exptime += cpl_propertylist_get_double(p, GIALIAS_EXPTIME);
        position = cx_list_next(images, position);

    }

    cpl_propertylist_update_double(properties, GIALIAS_EXPTTOT, exptime);

    cpl_propertylist_update_int(properties, GIALIAS_DATANCOM, count);

    cpl_propertylist_erase(properties, GIALIAS_EXPTIME);
    cpl_propertylist_erase(properties, GIALIAS_TPLEXPNO);


    if (cpl_error_get_code() != CPL_ERROR_NONE) {

        cpl_msg_error(_id, "Updating combined frame properties failed!");

        giraffe_image_delete(result);
        result = NULL;

        cx_list_destroy(images, (cx_free_func)giraffe_image_delete);
        images = NULL;

        cx_list_delete(frames);
        frames = NULL;

        cx_string_delete(tag);
        tag = NULL;

        return 1;

    }

    giraffe_error_pop();


    /*
     * Save the combined result frame and register it as a product.
     */

    cpl_msg_info(_id, "Writing combined frame ...");

    giraffe_image_add_info(result, &info, set);

    cx_string_append(tag, "_COMBINED");

    frame = giraffe_frame_create_image(result, cx_string_get(tag),
                                       CPL_FRAME_LEVEL_FINAL, TRUE, TRUE);

    if (frame == NULL) {

        cpl_msg_error(_id, "Cannot create local file! Aborting ...");

        giraffe_image_delete(result);
        result = NULL;

        cx_list_destroy(images, (cx_free_func)giraffe_image_delete);
        images = NULL;

        cx_list_delete(frames);
        frames = NULL;

        cx_string_delete(tag);
        tag = NULL;

        return 1;

    }

    cpl_frameset_insert(set, frame);


    /*
     * Cleanup
     */

    giraffe_image_delete(result);
    result = NULL;

    cx_list_destroy(images, (cx_free_func)giraffe_image_delete);
    images = NULL;

    cx_list_delete(frames);
    frames = NULL;

    cx_string_delete(tag);
    tag = NULL;

    return 0;

}


/*
 * Build table of contents, i.e. the list of available plugins, for
 * this module. This function is exported.
 */

int
cpl_plugin_get_info(cpl_pluginlist* list)
{

    cpl_recipe* recipe = cx_calloc(1, sizeof *recipe);
    cpl_plugin* plugin = &recipe->interface;


    cpl_plugin_init(plugin,
                    CPL_PLUGIN_API,
                    GIRAFFE_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "giframestack",
                    "Creates a stacked image from a set of raw images.",
                    "TBD",
                    "Giraffe Pipeline",
                    PACKAGE_BUGREPORT,
                    giraffe_get_license(),
                    giframestack_create,
                    giframestack_exec,
                    giframestack_destroy);

    cpl_pluginlist_append(list, plugin);

    return 0;

}
