/*
 * amber_spectral_calibration.c
 *
 *  Created on: Nov 5, 2012
 *      Author: agabasch
 *
 * 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

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

#include <string.h>
#include <cpl.h>

#include "amber_dfs.h"
#include "amdlib.h"
#include "amdlibProtected.h"

/*-----------------------------------------------------------------------------
                            Private function prototypes
 -----------------------------------------------------------------------------*/

static int amber_spectral_calibration_create(cpl_plugin *);
static int amber_spectral_calibration_exec(cpl_plugin *);
static int amber_spectral_calibration_destroy(cpl_plugin *);
static int amber_spectral_calibration(cpl_frameset *, const cpl_parameterlist *);

/*-----------------------------------------------------------------------------
                            Static variables
 -----------------------------------------------------------------------------*/

static char amber_spectral_calibration_description[] =
		"The recipe calculates the real Y offset between the different\n"
		"photometric and the interferometric channels in the 3-telescope\n"
		"mode. It uses the same algorithm as the amber_p2vm recipe.\n"
		"Input files:\n\n"
		"  DO category:               Type:      Explanation:      Required: \n"
		"  AMBER_BADPIX               Products   Bad pixel map              Y\n"
		"  AMBER_FLATFIELD            Products   Flatfield                  Y\n"
		"  AMBER_3WAVE                Raw        amber_wave data            Y\n"
		"\n"
		"Output files:\n\n"
		"  DO category:               Data type: Explanation:                \n"
		"  AMBER_SPECTRAL_CALIBRATION Products   Header with QC parameter  \n\n"
		"\n";

/*-----------------------------------------------------------------------------
                                Function code
 -----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/**
  @brief    Build the list of available plugins, for this module.
  @param    list    the plugin list
  @return   0 if everything is ok, 1 otherwise
  @note     Only this function is exported

  Create the recipe instance and make it available to the application using the
  interface.
 */
/*----------------------------------------------------------------------------*/
int cpl_plugin_get_info(cpl_pluginlist * list)
{
    cpl_recipe  *   recipe = cpl_calloc(1, sizeof *recipe );
    cpl_plugin  *   plugin = &recipe->interface;

    if (cpl_plugin_init(plugin,
                    CPL_PLUGIN_API,
                    AMBER_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "amber_spectral_calibration",
                    "AMBER Offset between photo. and interf. channels",
                    amber_spectral_calibration_description,
                    "Armin Gabasch",
                    PACKAGE_BUGREPORT,
                    "GPL",
                    amber_spectral_calibration_create,
                    amber_spectral_calibration_exec,
                    amber_spectral_calibration_destroy)) {
        cpl_msg_error(cpl_func, "Plugin initialization failed");
        (void)cpl_error_set_where(cpl_func);
        return 1;
    }

    if (cpl_pluginlist_append(list, plugin)) {
        cpl_msg_error(cpl_func, "Error adding plugin to list");
        (void)cpl_error_set_where(cpl_func);
        return 1;
    }

    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Setup the recipe options
  @param    plugin  the plugin
  @return   0 if everything is ok

  Defining the command-line/configuration parameters for the recipe.
 */
/*----------------------------------------------------------------------------*/
static int amber_spectral_calibration_create(cpl_plugin * plugin)
{
    cpl_recipe    * recipe;
    //cpl_parameter * p;

    /* Do not create the recipe if an error code is already set */
    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        cpl_msg_error(cpl_func, "%s():%d: An error is already set: %s",
                      cpl_func, __LINE__, cpl_error_get_where());
        return (int)cpl_error_get_code();
    }

    if (plugin == NULL) {
        cpl_msg_error(cpl_func, "Null plugin");
        cpl_ensure_code(0, (int)CPL_ERROR_NULL_INPUT);
    }

    /* Verify plugin type */
    if (cpl_plugin_get_type(plugin) != CPL_PLUGIN_TYPE_RECIPE) {
        cpl_msg_error(cpl_func, "Plugin is not a recipe");
        cpl_ensure_code(0, (int)CPL_ERROR_TYPE_MISMATCH);
    }

    /* Get the recipe */
    recipe = (cpl_recipe *)plugin;

    /* Create the parameters list in the cpl_recipe object */
    recipe->parameters = cpl_parameterlist_new();
    if (recipe->parameters == NULL) {
        cpl_msg_error(cpl_func, "Parameter list allocation failed");
        cpl_ensure_code(0, (int)CPL_ERROR_ILLEGAL_OUTPUT);
    }

    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int amber_spectral_calibration_exec(cpl_plugin * plugin)
{

    cpl_recipe * recipe;
    int recipe_status;
    cpl_errorstate initial_errorstate = cpl_errorstate_get();

    /* Return immediately if an error code is already set */
    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        cpl_msg_error(cpl_func, "%s():%d: An error is already set: %s",
                      cpl_func, __LINE__, cpl_error_get_where());
        return (int)cpl_error_get_code();
    }

    if (plugin == NULL) {
        cpl_msg_error(cpl_func, "Null plugin");
        cpl_ensure_code(0, (int)CPL_ERROR_NULL_INPUT);
    }

    /* Verify plugin type */
    if (cpl_plugin_get_type(plugin) != CPL_PLUGIN_TYPE_RECIPE) {
        cpl_msg_error(cpl_func, "Plugin is not a recipe");
        cpl_ensure_code(0, (int)CPL_ERROR_TYPE_MISMATCH);
    }

    /* Get the recipe */
    recipe = (cpl_recipe *)plugin;

    /* Verify parameter and frame lists */
/*
    if (recipe->parameters == NULL) {
        cpl_msg_error(cpl_func, "Recipe invoked with NULL parameter list");
        cpl_ensure_code(0, (int)CPL_ERROR_NULL_INPUT);
    }
*/
    if (recipe->frames == NULL) {
        cpl_msg_error(cpl_func, "Recipe invoked with NULL frame set");
        cpl_ensure_code(0, (int)CPL_ERROR_NULL_INPUT);
    }

    /* Invoke the recipe */
    recipe_status = amber_spectral_calibration(recipe->frames, recipe->parameters);

    /* Ensure DFS-compliance of the products */
    if (cpl_dfs_update_product_header(recipe->frames)) {
        if (!recipe_status) recipe_status = (int)cpl_error_get_code();
    }

    if (!cpl_errorstate_is_equal(initial_errorstate)) {
        /* Dump the error history since recipe execution start.
           At this point the recipe cannot recover from the error */
        cpl_errorstate_dump(initial_errorstate, CPL_FALSE, NULL);
    }

    return recipe_status;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Destroy what has been created by the 'create' function
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int amber_spectral_calibration_destroy(cpl_plugin * plugin)
{
    cpl_recipe * recipe;

    if (plugin == NULL) {
        cpl_msg_error(cpl_func, "Null plugin");
        cpl_ensure_code(0, (int)CPL_ERROR_NULL_INPUT);
    }

    /* Verify plugin type */
    if (cpl_plugin_get_type(plugin) != CPL_PLUGIN_TYPE_RECIPE) {
        cpl_msg_error(cpl_func, "Plugin is not a recipe");
        cpl_ensure_code(0, (int)CPL_ERROR_TYPE_MISMATCH);
    }

    /* Get the recipe */
    recipe = (cpl_recipe *)plugin;

    cpl_parameterlist_delete(recipe->parameters);

    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Interpret the command line options and execute the data processing
  @param    frameset   the frames list
  @param    parlist    the parameters list
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int amber_spectral_calibration(cpl_frameset            * frameset,
		const cpl_parameterlist * parlist)
{
	const cpl_frame     *   flatfield = NULL ;
	const cpl_frame     *   badpixel  = NULL ;
	const cpl_frame     *   wave3tel1    = NULL ;
	const cpl_frame     *   wave3tel2    = NULL ;
	const cpl_frame     *   wave3tel3    = NULL ;
	const cpl_frame     *   wave3tel4    = NULL ;

	cpl_propertylist    *   applist    = NULL ;

	amdlibDOUBLE            spectralOffsets[3]= {amdlibOFFSETY_NOT_CALIBRATED,
			amdlibOFFSETY_NOT_CALIBRATED, amdlibOFFSETY_NOT_CALIBRATED};



	/* Identify the RAW and CALIB frames in the input frameset */
	cpl_ensure_code(amber_dfs_set_groups(frameset) == CPL_ERROR_NONE,
			cpl_error_get_code());

	cpl_msg_info (cpl_func,	"Extracting the required frames");

	cpl_msg_indent_more();

	/* GET THE REQUIRED FRAMES */
	flatfield = cpl_frameset_find_const( frameset, "AMBER_FLATFIELD" );
	if (flatfield == NULL) {
		return (int)cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
				"SOF does not have a flatfield tagged AMBER_FLATFIELD");
	}
	else {
		cpl_msg_info (cpl_func,	"AMBER_FLATFIELD found: %s",
				cpl_frame_get_filename(flatfield));
	}

	badpixel  = cpl_frameset_find_const( frameset, "AMBER_BADPIX" );
	if (badpixel == NULL) {
		return (int)cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
				"SOF does not have a badpixel mask tagged AMBER_BADPIX");
	}
	else {
		cpl_msg_info (cpl_func,	"AMBER_BADPIX found: %s",
				cpl_frame_get_filename(badpixel));
	}

	wave3tel1 = cpl_frameset_find_const( frameset, "AMBER_3WAVE" );
	if (wave3tel1 == NULL) {
		return (int)cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
				"SOF does not have all 4 images tagged AMBER_3WAVE");
	}
	else {
		cpl_msg_info (cpl_func,	"AMBER_3WAVE found: %s",
				cpl_frame_get_filename(wave3tel1));
	}

	wave3tel2 = cpl_frameset_find_const( frameset, NULL );
	if (wave3tel2 == NULL) {
		return (int)cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
				"SOF does not have all 4 images tagged AMBER_3WAVE");
	}
	else {
		cpl_msg_info (cpl_func,	"AMBER_3WAVE found: %s",
				cpl_frame_get_filename(wave3tel2));
	}

	wave3tel3 = cpl_frameset_find_const( frameset, NULL );
	if (wave3tel3 == NULL) {
		return (int)cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
				"SOF does not have all 4 images tagged AMBER_3WAVE");
	}
	else {
		cpl_msg_info (cpl_func,	"AMBER_3WAVE found: %s",
				cpl_frame_get_filename(wave3tel3));
	}

	wave3tel4 = cpl_frameset_find_const( frameset, NULL );
	if (wave3tel4 == NULL) {
		return (int)cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
				"SOF does not have all 4 images tagged AMBER_3WAVE");
	}
	else {
		cpl_msg_info (cpl_func,	"AMBER_3WAVE found: %s",
				cpl_frame_get_filename(wave3tel4));
	}

	cpl_msg_indent_less();

	cpl_msg_info (cpl_func, "Computing the spectral calibration for 3 Telecopes");

	cpl_msg_info(cpl_func," ");

	if (amdlibComputeSpectralCalibration3T(
			cpl_frame_get_filename(badpixel),
			cpl_frame_get_filename(flatfield),
			cpl_frame_get_filename(wave3tel1),
			cpl_frame_get_filename(wave3tel2),
			cpl_frame_get_filename(wave3tel3),
			cpl_frame_get_filename(wave3tel4),
			spectralOffsets)
			!= amdlibSUCCESS){
		return (int)cpl_error_set_message(cpl_func, CPL_ERROR_UNSPECIFIED,
				"Amdlib failed to compute the spectral calibration");
	}
	else
	{
		cpl_msg_info(cpl_func," ");
		cpl_msg_info(cpl_func,"Computed spectral offsets: ");
		cpl_msg_indent_more();
		cpl_msg_info(cpl_func,"Photometric 1 = %f, ", spectralOffsets[0]);
		cpl_msg_info(cpl_func,"Photometric 2 = %f, ", spectralOffsets[1]);
		cpl_msg_info(cpl_func,"Photometric 3 = %f, ", spectralOffsets[2]);
		cpl_msg_indent_less();
	}

	/* Add a QC parameter  */
	applist=cpl_propertylist_new();
	/* Add the product category  */
	cpl_propertylist_append_string(applist, CPL_DFS_PRO_CATG,
			"AMBER_SPECTRAL_CALIBRATION");



	cpl_propertylist_append_double(applist, "ESO QC P1 OFFSETY", spectralOffsets[0] );
	cpl_propertylist_append_double(applist, "ESO QC P2 OFFSETY", spectralOffsets[1] );
	cpl_propertylist_append_double(applist, "ESO QC P3 OFFSETY", spectralOffsets[2] );


	if(cpl_dfs_save_propertylist(frameset,
			NULL,
			parlist,
			frameset,
			NULL,
			"amber_spectral_calibration",
			applist,
			NULL,
			PACKAGE "/" PACKAGE_VERSION,
			"amber_spectral_calibration.fits")){
		/* Propagate the error */
		(void)cpl_error_set_where(cpl_func);
	}

	cpl_propertylist_delete(applist);

	return (int)cpl_error_get_code();
}

