/* This file is part of the AMBER Pipeline
 * Copyright (C) 2002,2003 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
 */


/*
 * amber_oimerge.c
 *
 *  Created on: June 28, 2010
 *      Author: agabasch
 */

/*-----------------------------------------------------------------------------
                                Includes
 -----------------------------------------------------------------------------*/
#include <config.h>
#include <cpl.h>
#include "amber_dfs.h"
#include <string.h>
#include "fitsio.h"
/*-----------------------------------------------------------------------------
                            Private function prototypes
 -----------------------------------------------------------------------------*/
static int amber_oimerge_create(cpl_plugin *);
static int amber_oimerge_exec(cpl_plugin *);
static int amber_oimerge_destroy(cpl_plugin *);
static int amber_oimerge(cpl_frameset *, cpl_parameterlist *);

static int amber_oimerge_frameset(cpl_frameset * frameset,
		cpl_parameterlist * parlist, cpl_frameset * frameset_global,
		const char * outfile);

static cpl_error_code  amber_merge_extension(cpl_table ** targettable,
		const cpl_frame * cur_frame, const char * extension);
static cpl_error_code amber_check_framesetconsistency(cpl_frameset * frameset);
static int	amber_compare_OI_ARRAY(const cpl_table * ref_table,
		const cpl_table * table);
static int	amber_compare_OI_WAVELENGTH(const cpl_table * ref_table,
		const cpl_table * table);

static cpl_error_code amber_merge_VISfiles(cpl_frameset * frameset,
		const char * outfile);
static int cfitsio_main_tabmerge(const char *argv1, const char *argv2);

static int amber_copy_extension(const char * infile , const char * outfile, 
		const char * extension_name);
static int amber_check_tag_oimerge(const cpl_frame * cur_frame);
static int amber_check_oiconsistency(const cpl_frame * cur_frame);


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

static char amber_oimerge_man[] =
		"This recipe merges OI-Fits files\n"
		"\n"
		"Input files:\n\n"
		"  DO category:               Type:      Explanation:      Required: \n"
		"  SCIENCE_REDUCED            Products   OI-Fits file                \n"
		"  or                                                                \n"
		"  SCIENCE_REDUCED_FILTERED   Products   OI-Fits file                \n"
		"  or                                                                \n"
		"  CALIB_REDUCED              Products   OI-Fits file                \n"
		"  or                                                                \n"
		"  CALIB_REDUCED_FILTERED     Products   OI-Fits file                \n"
		"  or                                                               Y\n"
		"  AMBER_TRF_J                Products   OI-Fits file                \n"
		"  or                                                                \n"
		"  AMBER_TRF_H                Products   OI-Fits file                \n"
		"  or                                                                \n"
		"  AMBER_TRF_K                Products   OI-Fits file                \n"
		"  or                                                                \n"
		"  SCIENCE_CALIBRATED         Products   OI-Fits file              \n\n"
		"Output files:\n\n"
		"  DO category:               Data type: Explanation:                \n"
		"  SCIENCE_REDUCED            Products:  merged OI-fits file         \n"
		"  or                                                                \n"
		"  SCIENCE_REDUCED_FILTERED   Products:  merged OI-fits file         \n"
		"  or                                                                \n"
		"  CALIB_REDUCED              Products:  merged OI-fits file         \n"
		"  or                                                                \n"
		"  CALIB_REDUCED_FILTERED     Products:  merged OI-fits file         \n"
		"  or                                                               Y\n"
		"  AMBER_TRF_J                Products   merged OI-Fits file         \n"
		"  or                                                                \n"
		"  AMBER_TRF_H                Products   merged OI-Fits file         \n"
		"  or                                                                \n"
		"  AMBER_TRF_K                Products   merged OI-Fits file         \n"
		"  or                                                                \n"
		"  SCIENCE_CALIBRATED         Products:  merged OI-fits file      \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_oimerge",
			"AMBER Merge OI-Fits files",
			amber_oimerge_man,
			"Armin Gabasch",
			PACKAGE_BUGREPORT,
			"GPL",
			amber_oimerge_create,
			amber_oimerge_exec,
			amber_oimerge_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_oimerge_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);
	}

	/* Fill the parameters list */


	return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int amber_oimerge_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_oimerge(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_oimerge_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_oimerge(cpl_frameset * frameset, cpl_parameterlist * parlist)
{

	cpl_frameset     * frameset_J=NULL;
	cpl_frameset     * frameset_H=NULL;
	cpl_frameset     * frameset_K=NULL;
	cpl_propertylist * dummy_propertylist=NULL;
	const char       * dummy_string=NULL;
	cpl_frame        * cur_frame=NULL;
	int                jband=0;
	int                hband=0;
	int                kband=0;
	cpl_errorstate     prestate=0;
	cpl_frameset_iterator * it = NULL;
	/*
	cpl_errorstate     errorstateJ=0;
	cpl_errorstate     errorstateH=0;
	cpl_errorstate     errorstateK=0;
	cpl_errorstate     errorstateALL=0;
	 */

	amber_dfs_set_groups(frameset);

	if(cpl_frameset_find( frameset, "SCIENCE_REDUCED" )==NULL &&
			cpl_frameset_find( frameset, "SCIENCE_REDUCED_FILTERED")==NULL &&
			cpl_frameset_find( frameset, "CALIB_REDUCED" )==NULL &&
			cpl_frameset_find( frameset, "CALIB_REDUCED_FILTERED")==NULL &&
			cpl_frameset_find( frameset, "AMBER_TRF_J")==NULL &&
			cpl_frameset_find( frameset, "AMBER_TRF_H")==NULL &&
			cpl_frameset_find( frameset, "AMBER_TRF_K")==NULL &&
			cpl_frameset_find( frameset, "SCIENCE_CALIBRATED")==NULL)
	{
		cpl_msg_error(cpl_func,"No file tagged SCIENCE_REDUCED, CALIB_REDUCED, "
				"SCIENCE_REDUCED_FILTERED, CALIB_REDUCED_FILTERED. "
				"AMBER_TRF_J, AMBER_TRF_H, or AMBER_TRF_K  "
				"found in the SOF!!");
		return -1;
	}


	frameset_J=cpl_frameset_new();
	frameset_H=cpl_frameset_new();
	frameset_K=cpl_frameset_new();

	it = cpl_frameset_iterator_new(frameset);

	/*Looping over frames	*/
	while((cur_frame = cpl_frameset_iterator_get(it)))
	{
		dummy_string=NULL;
		dummy_propertylist=
				cpl_propertylist_load(cpl_frame_get_filename(cur_frame),0);
		dummy_string=cpl_propertylist_get_string(dummy_propertylist,
				"ESO QC BAND");

		if(dummy_string!=NULL && strcmp(dummy_string,"J")==0){
			jband++;
			cpl_frameset_insert(frameset_J, cpl_frame_duplicate(cur_frame));
		}
		if(dummy_string!=NULL && strcmp(dummy_string,"H")==0){
			hband++;
			cpl_frameset_insert(frameset_H, cpl_frame_duplicate(cur_frame));
		}
		if(dummy_string!=NULL && strcmp(dummy_string,"K")==0){
			kband++;
			cpl_frameset_insert(frameset_K, cpl_frame_duplicate(cur_frame));
		}
		cpl_frameset_iterator_advance(it, 1);
		cpl_propertylist_delete(dummy_propertylist);
	}
	cpl_frameset_iterator_delete(it);
	cpl_msg_info(cpl_func,"%d J-BAND frames found", jband);
	cpl_msg_info(cpl_func,"%d H-BAND frames found", hband);
	cpl_msg_info(cpl_func,"%d K-BAND frames found", kband);


	prestate = cpl_errorstate_get();


	if(!cpl_frameset_is_empty(frameset_J))
	{
		cpl_msg_info(cpl_func,"Merging J-Band data. Please wait ...");
		amber_oimerge_frameset(frameset_J, parlist, frameset,
				"merged_J-band.fits");
	}
	//errorstateJ = cpl_errorstate_get();
	cpl_errorstate_set(prestate);


	if(!cpl_frameset_is_empty(frameset_H))
	{
		cpl_msg_info(cpl_func,"Merging H-Band data. Please wait ...");
		amber_oimerge_frameset(frameset_H, parlist, frameset,
				"merged_H-band.fits");
	}
	//errorstateH = cpl_errorstate_get();
	cpl_errorstate_set(prestate);


	if(!cpl_frameset_is_empty(frameset_K))
	{
		cpl_msg_info(cpl_func,"Merging K-Band data. Please wait ...");
		amber_oimerge_frameset(frameset_K, parlist, frameset,
				"merged_K-band.fits");
	}
	//errorstateK = cpl_errorstate_get();
	cpl_errorstate_set(prestate);


	if(cpl_frameset_is_empty(frameset_J) && cpl_frameset_is_empty(frameset_H)
			&& cpl_frameset_is_empty(frameset_K) )
	{
		cpl_msg_info(cpl_func,"Trying to merge unknown data. Please wait ...");

		amber_oimerge_frameset(frameset, parlist, frameset,
				"merged_unknown.fits");
	}
	/*	errorstateALL = cpl_errorstate_get();*/

	cpl_errorstate_set(prestate);


	cpl_frameset_delete(frameset_J);
	cpl_frameset_delete(frameset_H);
	cpl_frameset_delete(frameset_K);

	//amber_oimerge_frameset(frameset,parlist);
	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_oimerge_frameset(cpl_frameset * frameset, cpl_parameterlist
		* parlist, cpl_frameset * frameset_global, const char * outfile)
{
	cpl_errorstate   prestate=0;
	const cpl_frame  * cur_frame=NULL;
	const cpl_frame  * FirstValidFrame=NULL;
	const char       * FirstValidTag=NULL;
	const char       * FirstValidFilename=NULL;
	cpl_propertylist * PrimaryHeader=NULL;
	cpl_propertylist * tablelist=NULL;
	cpl_propertylist * property_OI_ARRAY=NULL;
	cpl_propertylist * dummy_propertylist=NULL;
	int                counter=0;
	cpl_table        * table_OI_ARRAY      =NULL;
	cpl_table        * table_OI_TARGET     =NULL;
	cpl_table        * table_OI_WAVELENGTH =NULL;
	cpl_table        * table_OI_VIS        =NULL;
	cpl_table        * table_OI_VIS2       =NULL;
	cpl_table        * table_OI_T3         =NULL;
	cpl_table        * table_AMBER_DATA    =NULL;
	cpl_table        * table_AMBER_SPECTRUM=NULL;
	cpl_frameset_iterator * it = NULL;


	prestate = cpl_errorstate_get();




	amber_check_framesetconsistency(frameset);

	if (!cpl_errorstate_is_equal(prestate)){
		/*cpl_errorstate_dump(prestate, CPL_FALSE, NULL);*/
		return cpl_error_set_message(cpl_func, cpl_error_get_code(),
				"SOF is not consistent");
	}

	it = cpl_frameset_iterator_new(frameset);
	counter=0;
	/*Looping over science_reduced frames	*/

	while((cur_frame = cpl_frameset_iterator_get(it)))
	{
		/*Checking if the file has all required extension for merging.
		 * If not, a warning is issued*/
		if(amber_check_tag_oimerge(cur_frame)){
			if(!amber_check_oiconsistency(cur_frame)){
				cpl_msg_warning(cpl_func,"File %s has not all required "
						"extensions for a clean merging! This file is not "
						"taking into account during the merging process",
						cpl_frame_get_filename(cur_frame));
			}
		}

		if(amber_check_tag_oimerge(cur_frame) &&
				amber_check_oiconsistency(cur_frame))

		{
			cpl_msg_indent_more();
			cpl_msg_info(cpl_func,"Processing valid frame: %s",
					cpl_frame_get_filename(cur_frame));
			cpl_msg_indent_less();
			if(counter<1){
				/*execute only once*/
				FirstValidFrame   =cur_frame;
				FirstValidTag     =cpl_frame_get_tag(cur_frame);
				FirstValidFilename=cpl_frame_get_filename(FirstValidFrame);
				//cpl_msg_info(cpl_func,"First valid frame and tag: %s  %s",
				//		FirstValidFilename,FirstValidTag);
				amber_merge_extension(&table_OI_ARRAY, cur_frame, "OI_ARRAY");

				amber_merge_extension(&table_OI_WAVELENGTH, cur_frame,
						"OI_WAVELENGTH");
				counter=1;
			}
			amber_merge_extension(&table_OI_TARGET    , cur_frame, "OI_TARGET");
			amber_merge_extension(&table_OI_VIS       , cur_frame, "OI_VIS");
			amber_merge_extension(&table_OI_VIS2      , cur_frame, "OI_VIS2");
			amber_merge_extension(&table_OI_T3        , cur_frame, "OI_T3");
			amber_merge_extension(&table_AMBER_DATA   , cur_frame, "AMBER_DATA");
			amber_merge_extension(&table_AMBER_SPECTRUM,
					cur_frame, "AMBER_SPECTRUM");

			/*
			cpl_msg_info(cpl_func,"Processing frame: %s",
					cpl_frame_get_filename(cur_frame));
			cpl_msg_info(cpl_func,"nrow:  %i",
					cpl_table_get_nrow(table_AMBER_SPECTRUM));
			 */

		}
		cpl_frameset_iterator_advance(it, 1);
	}
	cpl_frameset_iterator_delete(it);

	/*According to OI-Fits the FLAG must be of type LOGICAL*/
	cpl_table_set_column_savetype(table_OI_VIS, "FLAG",CPL_TYPE_BOOL);
	cpl_table_set_column_savetype(table_OI_VIS2,"FLAG",CPL_TYPE_BOOL);
	cpl_table_set_column_savetype(table_OI_T3,  "FLAG",CPL_TYPE_BOOL);

	/* Propertylist to write to the extension */


	PrimaryHeader=cpl_propertylist_new();
	prestate = cpl_errorstate_get();

	/*Try to copy the following header parameters*/
	dummy_propertylist=cpl_propertylist_load(FirstValidFilename,0);
	cpl_propertylist_copy_property(PrimaryHeader,
			dummy_propertylist,"ESO PRO TECH");
	cpl_propertylist_copy_property(PrimaryHeader,
			dummy_propertylist,"ESO PRO SCIENCE");
	cpl_propertylist_copy_property(PrimaryHeader,
			dummy_propertylist,"ESO QC LAMBDA CHAN");
	cpl_propertylist_copy_property(PrimaryHeader,
			dummy_propertylist,"ESO QC LAMBDA MIN");
	cpl_propertylist_copy_property(PrimaryHeader,
			dummy_propertylist,"ESO QC LAMBDA MAX");
	cpl_propertylist_copy_property(PrimaryHeader,
			dummy_propertylist,"ESO QC BAND");
	cpl_propertylist_copy_property(PrimaryHeader,
			dummy_propertylist,"ESO QC STA1");
	cpl_propertylist_copy_property(PrimaryHeader,
			dummy_propertylist,"ESO QC STA2");
	cpl_propertylist_copy_property(PrimaryHeader,
			dummy_propertylist,"ESO QC STA3");
	cpl_propertylist_delete(dummy_propertylist);

	cpl_errorstate_set(prestate);

	cpl_propertylist_update_string(PrimaryHeader,CPL_DFS_PRO_CATG,FirstValidTag);

	/*Adding the JMMC acknowledgements*/
	amber_JMMC_acknowledgement(PrimaryHeader);


	tablelist= cpl_propertylist_load(FirstValidFilename,
			cpl_fits_find_extension(FirstValidFilename,"OI_ARRAY"));

	cpl_dfs_save_table(frameset_global, NULL, parlist, frameset, FirstValidFrame,
			table_OI_ARRAY,
			tablelist, "amber_oimerge",
			PrimaryHeader, NULL,
			PACKAGE "/" PACKAGE_VERSION,
			outfile);
	cpl_propertylist_delete(tablelist);


	tablelist= cpl_propertylist_load(FirstValidFilename,
			cpl_fits_find_extension(FirstValidFilename, "OI_TARGET"));
	cpl_table_save(table_OI_TARGET,NULL,tablelist,outfile,CPL_IO_EXTEND);
	cpl_propertylist_delete(tablelist);

	tablelist= cpl_propertylist_load(FirstValidFilename,
			cpl_fits_find_extension(FirstValidFilename, "OI_WAVELENGTH"));
	cpl_table_save(table_OI_WAVELENGTH,NULL,tablelist,outfile,
			CPL_IO_EXTEND);
	cpl_propertylist_delete(tablelist);


	/*At the moment cpl_table can not deal with complex types, therefore the
	 * following workarround based on cfitsio is used */

	/*
	Once cpl_table can handle complex types, comment this in again
	tablelist= cpl_propertylist_load(FirstValidFilename,
			cpl_fits_find_extension(FirstValidFilename, "OI_VIS"));
	cpl_table_save(table_OI_VIS,NULL,tablelist,outfile,CPL_IO_EXTEND);
	cpl_propertylist_delete(tablelist);
	 */

	amber_merge_VISfiles(frameset, outfile);

	tablelist= cpl_propertylist_load(FirstValidFilename,
			cpl_fits_find_extension(FirstValidFilename, "OI_VIS2"));
	cpl_table_save(table_OI_VIS2,NULL,tablelist,outfile,CPL_IO_EXTEND);
	cpl_propertylist_delete(tablelist);

	tablelist= cpl_propertylist_load(FirstValidFilename,
			cpl_fits_find_extension(FirstValidFilename, "OI_T3"));
	cpl_table_save(table_OI_T3,NULL,tablelist,outfile,CPL_IO_EXTEND);
	cpl_propertylist_delete(tablelist);

	tablelist= cpl_propertylist_load(FirstValidFilename,
			cpl_fits_find_extension(FirstValidFilename, "AMBER_DATA"));
	cpl_table_save(table_AMBER_DATA,NULL,tablelist,outfile,CPL_IO_EXTEND);
	cpl_propertylist_delete(tablelist);

	tablelist= cpl_propertylist_load(FirstValidFilename,
			cpl_fits_find_extension(FirstValidFilename, "AMBER_SPECTRUM"));
	cpl_table_save(table_AMBER_SPECTRUM,NULL,tablelist,outfile,
			CPL_IO_EXTEND);
	cpl_propertylist_delete(tablelist);


	/*

	cpl_dfs_save_table(frameset, PrimaryHeader, parlist, frameset, NULL,
	table_OI_T3,
			PrimaryHeader, "amber_oimerge",
			NULL, NULL,
			PACKAGE "/" PACKAGE_VERSION,
			"table_OI_T3.fits");
	 */

	cpl_propertylist_delete(property_OI_ARRAY);
	cpl_propertylist_delete(PrimaryHeader);
	cpl_table_delete(table_OI_ARRAY);
	cpl_table_delete(table_OI_TARGET);
	cpl_table_delete(table_OI_WAVELENGTH);
	cpl_table_delete(table_OI_VIS);
	cpl_table_delete(table_OI_VIS2);
	cpl_table_delete(table_OI_T3);
	cpl_table_delete(table_AMBER_DATA);
	cpl_table_delete(table_AMBER_SPECTRUM);





	//cpl_table_dump(table_AMBER_DATA,1,5000000,NULL);
	//cpl_table_save(table_AMBER_DATA,NULL,NULL,"table_AMBER_DATA.fits",0);

	if (!cpl_errorstate_is_equal(prestate)){
		/*cpl_errorstate_dump(prestate, CPL_FALSE, NULL);*/
		return cpl_error_set_message(cpl_func, cpl_error_get_code(),
				"Recipe failed");


	}
	return 0;

}

static cpl_error_code amber_merge_extension(cpl_table ** targettable,
		const cpl_frame * cur_frame, const char * extension)
{
	int               ext_number=0;
	cpl_table       * table=NULL;
	//cpl_errorstate    prestate=0;

	//cpl_msg_info(cpl_func,"AA: %p",targettable);
	//cpl_msg_info(cpl_func,"AA: %p",*targettable);


	ext_number=cpl_fits_find_extension(cpl_frame_get_filename(cur_frame),
			extension);
	table = cpl_table_load(cpl_frame_get_filename(cur_frame), ext_number, 1);
	if (table == NULL) {
		return cpl_error_set_message(cpl_func, cpl_error_get_code(),
				"Could not load the table");
		cpl_table_dump(table,1,20,NULL);

	}
	//cpl_ensure_code(cpl_errorstate_is_equal(prestate), cpl_error_get_code());
	//cpl_table_dump(table,1,20,NULL);

	if(*targettable==NULL)
	{
		*targettable=table;
	}
	else{
		cpl_table_insert(*targettable, table, cpl_table_get_nrow(*targettable));
		cpl_table_delete(table);
	}
	/*Free the memory*/
	//	cpl_msg_info(cpl_func,"frameAA: %s",cpl_frame_get_filename(cur_frame));
	//	cpl_msg_info(cpl_func,"nrowAA:  %i",cpl_table_get_nrow(targettable));
	//cpl_table_dump(targettable,1,20,NULL);

	/* Propagate error, if any */
	return cpl_error_set(cpl_func, cpl_error_get_code());


}

/*----------------------------------------------------------------------------*/
/**
 * @brief
 *   Checks if the frameset is compatible
 *
 * @param table  Pointer to the reference table.
 * @param name   Pointer to the table.
 * @return CPL_ERROR_NONE or the relevant CPL error code on error
 *
 */
/*----------------------------------------------------------------------------*/

static cpl_error_code amber_check_framesetconsistency(cpl_frameset * frameset)
{
	const cpl_frame * cur_frame=NULL;
	cpl_table       * table_ref_OI_ARRAY=NULL;
	cpl_table       * table_ref_OI_WAVELENGTH=NULL;
	cpl_table       * table_OI_ARRAY=NULL;
	cpl_table       * table_OI_WAVELENGTH=NULL;
	const cpl_frame * FirstValidFrame=NULL;
	const char      * FirstValidTag=NULL;
	const char      * FirstValidFilename=NULL;
	int               isConsistentARRAY=0;
	int               isConsistentWAVELENGTH=0;
	cpl_frameset_iterator * it = NULL;

	//cpl_errorstate    prestate=0;

	it = cpl_frameset_iterator_new(frameset);
	cur_frame = cpl_frameset_iterator_get(it);

	/*Looping over science_reduced frames	*/
	while(cur_frame)
	{

		if(amber_check_tag_oimerge(cur_frame) &&
				amber_check_oiconsistency(cur_frame))
		{
			FirstValidFrame   =cur_frame;
			FirstValidTag     =cpl_frame_get_tag(cur_frame);
			FirstValidFilename=cpl_frame_get_filename(FirstValidFrame);
			break;
		}
		else {
		    cpl_frameset_iterator_advance(it, 1);
		    cur_frame = cpl_frameset_iterator_get(it);
		}
	}
	/*Load reference tables*/


	table_ref_OI_ARRAY=cpl_table_load(FirstValidFilename,
			cpl_fits_find_extension(FirstValidFilename,"OI_ARRAY"), 1);
	if (table_ref_OI_ARRAY == NULL) {
	    cpl_frameset_iterator_delete(it);
		return cpl_error_set_message(cpl_func, cpl_error_get_code(),
				"Could not load the table OI_ARRAY");

	}


	table_ref_OI_WAVELENGTH=cpl_table_load(FirstValidFilename,
			cpl_fits_find_extension(FirstValidFilename,"OI_WAVELENGTH"), 1);
	if ( table_ref_OI_WAVELENGTH== NULL) {
		cpl_table_delete(table_ref_OI_ARRAY);
	    cpl_frameset_iterator_delete(it);
		return cpl_error_set_message(cpl_func, cpl_error_get_code(),
				"Could not load the table OI_WAVELENGTH");

	}


	//cpl_ensure_code(cpl_errorstate_is_equal(prestate), cpl_error_get_code());

	cpl_frameset_iterator_reset(it);
	cur_frame = cpl_frameset_iterator_get(it);

	/*Looping over reduced frames and compare the tables*/

	while(cur_frame)
	{
		if(amber_check_tag_oimerge(cur_frame))

		{
			table_OI_ARRAY=cpl_table_load(cpl_frame_get_filename(cur_frame),
					cpl_fits_find_extension(cpl_frame_get_filename(cur_frame),
							"OI_ARRAY"), 1);
			table_OI_WAVELENGTH=cpl_table_load(cpl_frame_get_filename(cur_frame)
					,cpl_fits_find_extension(cpl_frame_get_filename(cur_frame),
							"OI_WAVELENGTH"), 1);

			isConsistentARRAY=
					amber_compare_OI_ARRAY(table_ref_OI_ARRAY,table_OI_ARRAY);

			isConsistentWAVELENGTH=
					amber_compare_OI_WAVELENGTH(table_ref_OI_WAVELENGTH,
							table_OI_WAVELENGTH);

			if(isConsistentARRAY>0 || isConsistentWAVELENGTH >0)
			{
				cpl_table_delete(table_ref_OI_ARRAY);
				cpl_table_delete(table_ref_OI_WAVELENGTH);
				cpl_table_delete(table_OI_ARRAY);
				cpl_table_delete(table_OI_WAVELENGTH);
			    cpl_frameset_iterator_delete(it);
				return cpl_error_set_message(cpl_func,
						CPL_ERROR_INCOMPATIBLE_INPUT,
						"SOF contains inconsistent OI-Fits files");
			}
			cpl_table_delete(table_OI_ARRAY);
			cpl_table_delete(table_OI_WAVELENGTH);

		}
		  cpl_frameset_iterator_advance(it, 1);
		  cur_frame = cpl_frameset_iterator_get(it);

	}
	cpl_frameset_iterator_delete(it);

	cpl_table_delete(table_ref_OI_ARRAY);
	cpl_table_delete(table_ref_OI_WAVELENGTH);
	return cpl_error_set(cpl_func, cpl_error_get_code());

}

/*----------------------------------------------------------------------------*/
/**
 * @brief
 *   Checks if two OI_ARRAY tables are compatible
 *
 * @param table  Pointer to the reference table.
 * @param name   Pointer to the table.
 *
 */
/*----------------------------------------------------------------------------*/
static int	amber_compare_OI_ARRAY(const cpl_table * ref_table,
		const cpl_table * table)
{

	int          nrow=0;
	int          i=0;

	const char    ** pointer_ref_TEL_NAME=NULL;
	const char    ** pointer_TEL_NAME=NULL;
	const char    ** pointer_ref_STA_NAME=NULL;
	const char    ** pointer_STA_NAME=NULL;
	const int      * pointer_ref_STA_INDEX=NULL;
	const int      * pointer_STA_INDEX=NULL;



	/*Check if the table structure and length is identical*/
	if (cpl_table_compare_structure(ref_table,table)!=0 ||
			cpl_table_get_nrow(ref_table)!=cpl_table_get_nrow(table))
	{
		return 1;
		cpl_msg_error(cpl_func,"OI_ARRAY table structure differ");

	}

	nrow=cpl_table_get_nrow(ref_table);

	if(!cpl_table_has_column(ref_table,"TEL_NAME")       ||
			!cpl_table_has_column(table,"TEL_NAME")      ||
			!cpl_table_has_column(ref_table,"STA_NAME")  ||
			!cpl_table_has_column(table,"STA_NAME")      ||
			!cpl_table_has_column(ref_table,"STA_INDEX") ||
			!cpl_table_has_column(table,"STA_INDEX"))
	{
		cpl_msg_error(cpl_func,"OI_ARRAY table not complete");
		return 1;
	}
	pointer_ref_TEL_NAME=cpl_table_get_data_string_const(ref_table,"TEL_NAME");
	pointer_TEL_NAME    =cpl_table_get_data_string_const(table,"TEL_NAME");

	pointer_ref_STA_NAME=cpl_table_get_data_string_const(ref_table,"STA_NAME");
	pointer_STA_NAME    =cpl_table_get_data_string_const(table,"STA_NAME");

	pointer_ref_STA_INDEX=cpl_table_get_data_int_const(ref_table,"STA_INDEX");
	pointer_STA_INDEX    =cpl_table_get_data_int_const(table,"STA_INDEX");



	for (i=0; i<nrow;i++)
	{
		/*cpl_msg_error(cpl_func,"STA_INDEX=%d",pointer_ref_STA_INDEX[i]);*/

		if(strcmp(pointer_ref_TEL_NAME[i],pointer_TEL_NAME[i])!=0      ||
				strcmp(pointer_ref_STA_NAME[i],pointer_STA_NAME[i])!=0 ||
				pointer_ref_STA_INDEX[i]!=pointer_STA_INDEX[i])
		{
			cpl_msg_error(cpl_func,"OI_ARRAY table differ");
			return 1;
		}
	}


	return 0;

}

/*----------------------------------------------------------------------------*/
/**
 * @brief
 *   Checks if two OI_WAVELENGTH tables are compatible
 *
 * @param table  Pointer to the reference table.
 * @param name   Pointer to the table.
 *
 */
/*----------------------------------------------------------------------------*/
static int	amber_compare_OI_WAVELENGTH(const cpl_table * ref_table,
		const cpl_table * table)
{
	int                 nrow=0;
	int                 i=0;
	const float      * pointer_ref_EFF_WAVE=NULL;
	const float      * pointer_EFF_WAVE=NULL;
	const float      * pointer_ref_EFF_BAND=NULL;
	const float      * pointer_EFF_BAND=NULL;


	/*Check if the table structure and length is identical*/
	if (cpl_table_compare_structure(ref_table,table)!=0 ||
			cpl_table_get_nrow(ref_table)!=cpl_table_get_nrow(table))
	{
		cpl_msg_error(cpl_func,"OI_WAVELENGTH table structure differ");
		return 1;
	}

	nrow=cpl_table_get_nrow(ref_table);

	if(!cpl_table_has_column(ref_table,"EFF_BAND")       ||
			!cpl_table_has_column(table,"EFF_BAND")      ||
			!cpl_table_has_column(ref_table,"EFF_WAVE") ||
			!cpl_table_has_column(table,"EFF_WAVE"))
	{
		cpl_msg_error(cpl_func,"OI_WAVELENGTH table not complete");
		return 1;
	}

	pointer_ref_EFF_WAVE=cpl_table_get_data_float_const(ref_table,"EFF_WAVE");
	pointer_EFF_WAVE    =cpl_table_get_data_float_const(table,"EFF_WAVE");
	pointer_ref_EFF_BAND=cpl_table_get_data_float_const(ref_table,"EFF_BAND");
	pointer_EFF_BAND    =cpl_table_get_data_float_const(table,"EFF_BAND");

	for (i=0; i<nrow;i++)
	{
		/*cpl_msg_error(cpl_func,"EFF_BAND=%d",pointer_ref_EFF_BAND[i]);*/

		if(pointer_ref_EFF_BAND[i]!=pointer_EFF_BAND[i] ||
				pointer_ref_EFF_WAVE[i]!=pointer_EFF_WAVE[i])
		{
			cpl_msg_error(cpl_func,"OI_WAVELENGTH table differ");
			return 1;
		}
	}


	return 0;

}

static cpl_error_code amber_merge_VISfiles(cpl_frameset * frameset,
		const char * outfile)
{

	const char * argv1=NULL;
	const char * argv2=NULL;
	int          extension_OI_VIS=0;
	int          counter=0;
	const cpl_frame  * cur_frame=NULL;
	cpl_frameset_iterator * it = NULL;

	it = cpl_frameset_iterator_new(frameset);
	/*Looping over reduced frames and compare the tables*/

	while((cur_frame = cpl_frameset_iterator_get(it)))
	{
		if(amber_check_tag_oimerge(cur_frame) &&
				amber_check_oiconsistency(cur_frame))

		{

			extension_OI_VIS=
					cpl_fits_find_extension(cpl_frame_get_filename(cur_frame),
							"OI_VIS");
			//extension_OI_VIS-=1;



			if(counter<1){
				amber_copy_extension(cpl_frame_get_filename(cur_frame),
						outfile, "OI_VIS");
				counter=1;
			}
			else {

				argv1=cpl_sprintf("%s+%d", cpl_frame_get_filename(cur_frame),
						extension_OI_VIS);
				argv2=cpl_sprintf("%s+%d", outfile,
						extension_OI_VIS);
				/*
				cpl_msg_info(cpl_func, "argv1: %s",argv1);
				cpl_msg_info(cpl_func, "argv2: %s",argv2);
				 */

				cfitsio_main_tabmerge(argv1, argv2);
				cpl_free((void *)argv1);
				cpl_free((void *)argv2);
			}
		}
		cpl_frameset_iterator_advance(it, 1);
	}
	cpl_frameset_iterator_delete(it);

	return 0;
}


int cfitsio_main_tabmerge(const char *argv1, const char *argv2)
{
	fitsfile *infptr, *outfptr;  /* FITS file pointers */
	int status = 0;   /* CFITSIO status value MUST be initialized to zero! */
	int icol, incols, outcols, intype, outtype, check = 1;
	long inrep, outrep, width, inrows, outrows, ii, jj;
	unsigned char *buffer = 0;

	if (0) {
		cpl_msg_info(cpl_func,"Usage:  tabmerge infile1[ext][filter] outfile[ext]\n");
		cpl_msg_info(cpl_func,"\n");
		cpl_msg_info(cpl_func,"Merge 2 tables by copying all the rows from the 1st table\n");
		cpl_msg_info(cpl_func,"into the 2nd table.  The  2 tables must have identical\n");
		cpl_msg_info(cpl_func,"structure, with the same number of columns with the same\n");
		cpl_msg_info(cpl_func,"datatypes.  This program modifies the output file in place,\n");
		cpl_msg_info(cpl_func,"rather than creating a whole new output file.\n");
		cpl_msg_info(cpl_func,"\n");
		cpl_msg_info(cpl_func,"Examples: \n");
		cpl_msg_info(cpl_func,"\n");
		cpl_msg_info(cpl_func,"1. tabmerge intab.fit+1 outtab.fit+2\n");
		cpl_msg_info(cpl_func,"\n");
		cpl_msg_info(cpl_func,"    merge the table in the 1st extension of intab.fit with\n");
		cpl_msg_info(cpl_func,"    the table in the 2nd extension of outtab.fit.\n");
		cpl_msg_info(cpl_func,"\n");
		cpl_msg_info(cpl_func,"2. tabmerge 'intab.fit+1[PI > 45]' outab.fits+2\n");
		cpl_msg_info(cpl_func,"\n");
		cpl_msg_info(cpl_func,"    Same as the 1st example, except only rows that have a PI\n");
		cpl_msg_info(cpl_func,"    column value > 45 will be merged into the output table.\n");
		cpl_msg_info(cpl_func,"\n");
		return(0);
	}

	/* open both input and output files and perform validity checks */
	if ( fits_open_file(&infptr,  argv1, READONLY,  &status) ||
			fits_open_file(&outfptr, argv2, READWRITE, &status) )
		cpl_msg_info(cpl_func," Couldn't open both files\n");

	else if ( fits_get_hdu_type(infptr,  &intype,  &status) ||
			fits_get_hdu_type(outfptr, &outtype, &status) )
		cpl_msg_info(cpl_func,"couldn't get the type of HDU for the files\n");

	else if (intype == IMAGE_HDU)
		cpl_msg_info(cpl_func,"The input HDU is an image, not a table\n");

	else if (outtype == IMAGE_HDU)
		cpl_msg_info(cpl_func,"The output HDU is an image, not a table\n");

	else if (outtype != intype)
		cpl_msg_info(cpl_func,"Input and output HDUs are not the same type of table.\n");

	else if ( fits_get_num_cols(infptr,  &incols,  &status) ||
			fits_get_num_cols(outfptr, &outcols, &status) )
		cpl_msg_info(cpl_func,"Couldn't get number of columns in the tables\n");

	else if ( incols != outcols )
		cpl_msg_info(cpl_func,"Input and output HDUs don't have same # of columns.\n");

	else if ( fits_read_key(infptr, TLONG, "NAXIS1", &width, NULL, &status) )
		cpl_msg_info(cpl_func,"Couldn't get width of input table\n");

	else if (!(buffer = (unsigned char *) malloc(width)) )
		cpl_msg_info(cpl_func,"memory allocation error\n");

	else if ( fits_get_num_rows(infptr,  &inrows,  &status) ||
			fits_get_num_rows(outfptr, &outrows, &status) )
		cpl_msg_info(cpl_func,"Couldn't get the number of rows in the tables\n");

	else  {
		/* check that the corresponding columns have the same datatypes */
		for (icol = 1; icol <= incols; icol++) {
			fits_get_coltype(infptr,  icol, &intype,  &inrep,  NULL, &status);
			fits_get_coltype(outfptr, icol, &outtype, &outrep, NULL, &status);
			if (intype != outtype || inrep != outrep) {
				cpl_msg_info(cpl_func,"Column %d is not the same in both tables\n", icol);
				check = 0;
			}
		}

		if (check && !status)
		{
			/* insert 'inrows' empty rows at the end of the output table */
			fits_insert_rows(outfptr, outrows, inrows, &status);

			for (ii = 1, jj = outrows +1; ii <= inrows; ii++, jj++)
			{
				/* read row from input and write it to the output table */
				fits_read_tblbytes( infptr,  ii, 1, width, buffer, &status);
				fits_write_tblbytes(outfptr, jj, 1, width, buffer, &status);
				if (status)break;  /* jump out of loop if error occurred */
			}

			/* all done; now free memory and close files */
			fits_close_file(outfptr, &status);
			fits_close_file(infptr,  &status);
		}
	}

	if (buffer)
		free(buffer);

	if (status) fits_report_error(stderr, status); /* print any error message */
	return(status);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    copy/appends an extension of infile to outfile
  @param    infile         the filename with the extension
  @param    outfile        the filename to which the extension should be copied
  @param    extension_name the name of the extension
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/

static int amber_copy_extension(const char * infile , const char * outfile,
		const char * extension_name)
{
	fitsfile * fptrin=NULL;
	fitsfile * fptrout=NULL;
	int status=0;

	fits_open_diskfile(&fptrin, infile, READONLY, &status);
	fits_open_diskfile(&fptrout, outfile, READWRITE, &status);
	fits_movnam_hdu(fptrin, ANY_HDU, (char *)extension_name, 0, &status);
	fits_copy_hdu(fptrin, fptrout, 0, &status);
	fits_close_file(fptrin, &status);
	fits_close_file(fptrout, &status);

	if  (status != 0){
		cpl_msg_error(cpl_func,
				"A problem occured while copying the EXTENSION: %s",
				extension_name);
		return 1;
	}
	else{
		return 0;
	}
}

static int amber_check_tag_oimerge(const cpl_frame * cur_frame)
{
	if(strcmp(cpl_frame_get_tag(cur_frame),
			"SCIENCE_REDUCED")==0 ||
			strcmp(cpl_frame_get_tag(cur_frame),
					"CALIB_REDUCED")==0 ||
					strcmp(cpl_frame_get_tag(cur_frame),
							"SCIENCE_REDUCED_FILTERED")==0 ||
							strcmp(cpl_frame_get_tag(cur_frame),
									"CALIB_REDUCED_FILTERED")==0 ||
									strcmp(cpl_frame_get_tag(cur_frame),
											"AMBER_TRF_J")==0 ||
											strcmp(cpl_frame_get_tag(cur_frame),
													"AMBER_TRF_H")==0 ||
													strcmp(cpl_frame_get_tag(cur_frame),
															"AMBER_TRF_K")==0 ||
															strcmp(cpl_frame_get_tag(cur_frame),
																	"SCIENCE_CALIBRATED")==0)
	{

		return 1;
	}
	else {
		return 0;
	}

}
/*----------------------------------------------------------------------------*/
/**
  @brief    checks if all extensions are present in the oifits file
  @param    cur_frame      frame to be checked for consistency
  @return   1 if everything is ok else 0
 */
/*----------------------------------------------------------------------------*/

static int amber_check_oiconsistency(const cpl_frame * cur_frame)
{

	if(cpl_fits_find_extension(cpl_frame_get_filename(cur_frame),
			"OI_ARRAY")>0 &&
			cpl_fits_find_extension(cpl_frame_get_filename(cur_frame),
					"OI_WAVELENGTH")>0 &&
			cpl_fits_find_extension(cpl_frame_get_filename(cur_frame),
					"OI_TARGET")>0 &&
			cpl_fits_find_extension(cpl_frame_get_filename(cur_frame),
					"OI_VIS")>0 &&
			cpl_fits_find_extension(cpl_frame_get_filename(cur_frame),
					"OI_VIS2")>0 &&
			/*cpl_fits_find_extension(cpl_frame_get_filename(cur_frame),
			 * "OI_T3")>0 &&*/
/*
 * USD asked to remove this check in order to be able to run it on oifitsfiles
 * averaged with the amdlib functions - in this case this extencion is missing
			cpl_fits_find_extension(cpl_frame_get_filename(cur_frame),
					"AMBER_DATA")>0 &&
*/
			cpl_fits_find_extension(cpl_frame_get_filename(cur_frame),
					"AMBER_SPECTRUM")>0)
	{

		return 1;
	}
	else {
		return 0;
	}

}
