/*
 * esolibTransferfunction.c
 *
 *  Created on: Oct 5, 2009
 *      Author: agabasch
 */

#include "esolibTransferfunction.h"
#include "cpl.h"
#include <math.h>
#include <string.h>
#include "amdrs.h"
#include "amber_qc.h"
#include "amber_dfs.h"
#include <libgen.h>
/*Function prototypes*/

static cpl_error_code amber_get_calibparam(double * Average,
		cpl_propertylist * plist, const char * startkey, const char * endkey);

static double amber_computeBesselK1o1 (double xArg, double xArgErr,
		double *besselK1o1Err);
static cpl_error_code amber_getCalibratorDiameter (const char * filename,
		CalibratorParam *calibrator, cpl_frameset *frameset, int *error);


static cpl_error_code amber_TransferFunctionCompute(
		CalibratorParam *calibrator12, CalibratorParam *calibrator13,
		CalibratorParam *calibrator23, const char * recipename,
		const char * outname_selector, cpl_parameterlist *parlist,
		cpl_frameset * framelist);

static cpl_error_code amber_identifyCalibrator (const char * filename,
		CalibratorParam *calibrator, const char * baseline,
		cpl_frameset * framelist, int *error);
static cpl_error_code amber_computeExpectedVis (CalibratorParam    *calibrator);

static cpl_error_code  amber_getInvWavelength (const char * filename,
		CalibratorParam *calibrator);

static cpl_error_code midi_amber_computeExpectedVis (CalibratorParam
		*calibrator);
static cpl_error_code amber_init_calibrator (CalibratorParam *calibrator);



/*-------------------------------------*/
/*Function implementation*/


static cpl_error_code amber_identifyCalibrator (const char * filename,
		CalibratorParam *calibrator, const char * baseline,
		cpl_frameset * framelist, int *error)
{
	cpl_propertylist * plist=NULL;
	const char * headerkey_start=NULL;
	const char * headerkey_end  = NULL;
	/*Set error to OK*/
	*error=0;

	/* Extract RA and DEC for the calibrator */
	plist = cpl_propertylist_load(filename, 0);
	if (plist==NULL)
	{
		cpl_msg_warning(cpl_func,"Error loading the header of %s",filename);
		*error=1;
		/* Propagate error, if any */
		return cpl_error_set(cpl_func, cpl_error_get_code());
	}
	if (cpl_propertylist_has(plist, "RA") == 1)
	{
		calibrator->calibRA=(cpl_propertylist_get_double(plist, "RA"));
		calibrator->calibRA *= DEG_TO_RAD;
	}
	else
	{
		*error=1;
		cpl_propertylist_delete(plist);
		return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
				"Can not fined RA from the header!");
	}
	if (cpl_propertylist_has(plist, "DEC") == 1)
	{
		calibrator->calibDEC=(cpl_propertylist_get_double(plist, "DEC"));
		calibrator->calibDEC *= DEG_TO_RAD;
	}
	else
	{
		*error=1;
		cpl_propertylist_delete(plist);
		return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
				"Can not fined DEC from the header!");
	}

	/* Get target name */
	if (cpl_propertylist_has(plist, "ESO OBS TARG NAME") == 1)
	{
		calibrator->calibNameObserved=cpl_sprintf("%s",
				cpl_propertylist_get_string(plist, "ESO OBS TARG NAME"));
	}
	else
	{
		cpl_msg_warning(cpl_func,"ESO OBS TARG NAME not found!"
				" Using default value NO_NAME");
		calibrator->calibNameObserved=cpl_sprintf("NO_NAME");
	}

	/* Get PBL */
	headerkey_start=cpl_sprintf("ESO ISS PBL%s START",baseline);
	headerkey_end=cpl_sprintf("ESO ISS PBL%s END",baseline);

	if(amber_get_calibparam(&(calibrator->calibPblAverage), plist,
			headerkey_start	, headerkey_end))
	{
		*error=1;
		cpl_free((void *)headerkey_start);
		cpl_free((void *)headerkey_end);
		cpl_propertylist_delete(plist);
		cpl_msg_warning (cpl_func, "Error when calculating calibPblAverage: "
				"%s",cpl_error_get_message());
		return cpl_error_set(cpl_func, cpl_error_get_code());

	}
	cpl_free((void *)headerkey_start);
	cpl_free((void *)headerkey_end);
	/* Get PARANG */

	if(amber_get_calibparam(&(calibrator->calibParangAverage), plist,
			"ESO ISS PARANG START", "ESO ISS PARANG END"))
	{
		*error=1;
		cpl_propertylist_delete(plist);
		cpl_msg_warning (cpl_func, "Error when calculating calibParangAverage: "
				"%s",cpl_error_get_message());
		return cpl_error_set(cpl_func, cpl_error_get_code());

	}

	cpl_msg_info(cpl_func,"Observed Calibrator                = %s         ",
			calibrator->calibNameObserved);
	cpl_msg_info(cpl_func,"Observed Calibrator RA             = %f radians ",
			calibrator->calibRA);
	cpl_msg_info(cpl_func,"Observed Calibrator DEC            = %f radians ",
			calibrator->calibDEC);
	cpl_msg_info(cpl_func,"Observed Calibrator Average PBL    = %f metres  ",
			calibrator->calibPblAverage);
	cpl_msg_info(cpl_func,"Observed Calibrator Average PARANG = %f degrees ",
			calibrator->calibParangAverage);

	/*Get diameter of the calibrator from the Calibrator database*/
	if(amber_getCalibratorDiameter (filename, calibrator, framelist, error))
	{
		cpl_propertylist_delete(plist);
		cpl_msg_warning (cpl_func, "%s",cpl_error_get_message());
		return cpl_error_set(cpl_func, cpl_error_get_code());
	}

	if (plist!=NULL)
	{
		cpl_propertylist_delete(plist);
	}

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


static cpl_error_code amber_getCalibratorDiameter (const char * filename,
		CalibratorParam    *calibrator,
		cpl_frameset      *frameset,
		int                *error)
{

	/*    Local Declarations
         --------------------*/
	int                 i=0;
	double              searchRA=0.;
	double              searchDEC=0.;
	double              diffAngle=0.;
	/*	cpl_errorstate      prestate = cpl_errorstate_get();*/
	cpl_frame         * cur_frame;
	cpl_table         * table;
	cpl_propertylist  * plist=NULL;
	const char       ** Name=NULL;
	int               * hourRA=NULL;
	int               * minuteRA=NULL;
	double            * secondRA=NULL;
	int               * signDEC=NULL;
	int               * degreeDEC=NULL;
	int               * minuteDEC=NULL;
	double            * secondDEC=NULL;
	double            * magJ=NULL;
	double            * magH=NULL;
	double            * magK=NULL;
	int               * flag=NULL;
	double            * diameter=NULL;
	double            * diameterErr=NULL;
	int                 ext_calibrator_data=0;
	int                 dimen_calibrator_data=0;
	char              * tag=NULL;
	char           *    tag_needed=NULL;
	int                 calibNameDatabase_index=0;

	cur_frame = cpl_frameset_get_first(frameset);
	if (cur_frame == NULL) {
		cpl_msg_error(cpl_func, "No frame found in the SOF");
		*error = 1;
		/* Propagate error, if any */
		return cpl_error_set(cpl_func, cpl_error_get_code());
	}

	/* Extract the FILTER J,H,K */
	plist = cpl_propertylist_load(filename, 0);
	if (plist==NULL)
	{
		cpl_msg_warning(cpl_func,"Error loading the header of %s",filename);
		*error=1;
		/* Propagate error, if any */
		return cpl_error_set(cpl_func, cpl_error_get_code());
	}
	if (cpl_propertylist_has(plist, "ESO QC BAND") == 1)
	{
		tag_needed=cpl_sprintf("CALIB_DATABASE_%s",
				cpl_propertylist_get_string(plist, "ESO QC BAND"));
		cpl_msg_info(cpl_func, "Searching for the calibrator database tagged %s"
				, tag_needed);
	}
	else
	{
		*error=1;
		cpl_propertylist_delete(plist);
		return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
				"Can not determine the Band from the header! ");
	}
	cpl_propertylist_delete(plist);

	/*  Find the calibrator database file in the SOF */
	while(cur_frame)
	{
		/*      Check the right tags */
		tag = (char*)cpl_frame_get_tag(cur_frame);
		if (strcmp(tag, tag_needed)) {
			cur_frame = cpl_frameset_get_next( frameset );
			continue;
		}
		else{
			cpl_msg_info(cpl_func, "Calibrator database found in the SOF: %s",
					cpl_frame_get_filename(cur_frame));
			break;
		}
	}

	if (strcmp(tag, tag_needed)) {
		cpl_free(tag_needed);
		*error = 1;
		return cpl_error_set_message(cpl_func, CPL_ERROR_FILE_NOT_FOUND,
				"No calibrator database file in the SOF! "
				"Transfer function can not be calculated!");
	}
	cpl_free(tag_needed);

	/* Load extension CALIBRATOR_DATA */

	/*	prestate = cpl_errorstate_get();*/

	cpl_msg_info(cpl_func, "Reading %s",cpl_frame_get_filename(cur_frame));

	ext_calibrator_data=cpl_fits_find_extension(
			cpl_frame_get_filename(cur_frame),"CALIBRATOR_DATA");

	/* Load extension  */
	if((table = cpl_table_load(cpl_frame_get_filename(cur_frame),
			ext_calibrator_data, 1))==NULL)
	{
		cpl_msg_info(cpl_func, "No siutable table found in the file: %s",
				cpl_frame_get_filename(cur_frame));
		cpl_msg_warning(cpl_func, "Transfer function can not be calculated!");
		/*		cpl_errorstate_set(prestate);*/
		*error = 1;
		/* Propagate error, if any */
		return cpl_error_set(cpl_func, cpl_error_get_code());
	}

	if (cpl_table_has_column(table, "Name"       ))
	{
		Name       =cpl_table_get_data_string_const(table, "Name"       );
	}
	else
	{
		cpl_msg_warning(cpl_func, "Entry not found:  Name"       );
		*error = 1;
	}
	if (cpl_table_has_column(table, "hourRA"     ))
	{
		hourRA     =cpl_table_get_data_int   (table, "hourRA"     );
	}
	else
	{
		cpl_msg_warning(cpl_func, "Entry not found:  hourRA"     );
		*error = 1;
	}
	if (cpl_table_has_column(table, "minuteRA"   ))
	{
		minuteRA   =cpl_table_get_data_int   (table, "minuteRA"   );
	}
	else
	{
		cpl_msg_warning(cpl_func, "Entry not found:  minuteRA"   );
		*error = 1;
	}
	if (cpl_table_has_column(table, "secondRA"   ))
	{
		secondRA   =cpl_table_get_data_double (table, "secondRA"   );
	}
	else
	{
		cpl_msg_warning(cpl_func, "Entry not found:  secondRA"   );
		*error = 1;
	}
	if (cpl_table_has_column(table, "signDEC"    ))
	{
		signDEC    =cpl_table_get_data_int   (table, "signDEC"    );
	}
	else
	{
		cpl_msg_warning(cpl_func, "Entry not found:  signDEC"    );
		*error = 1;
	}
	if (cpl_table_has_column(table, "degreeDEC"  ))
	{
		degreeDEC  =cpl_table_get_data_int   (table, "degreeDEC"  );
	}
	else
	{
		cpl_msg_warning(cpl_func, "Entry not found:  degreeDEC"  );
		*error = 1;
	}
	if (cpl_table_has_column(table, "minuteDEC"  ))
	{
		minuteDEC  =cpl_table_get_data_int   (table, "minuteDEC"  );
	}
	else
	{
		cpl_msg_warning(cpl_func, "Entry not found:  minuteDEC"  );
		*error = 1;
	}
	if (cpl_table_has_column(table, "secondDEC"  ))
	{
		secondDEC  =cpl_table_get_data_double (table, "secondDEC"  );
	}
	else
	{
		cpl_msg_warning(cpl_func, "Entry not found:  secondDEC"  );
		*error = 1;
	}
	if (cpl_table_has_column(table, "J_band") &&
			cpl_table_has_column(table, "H_band") &&
			cpl_table_has_column(table, "K_band"))
	{
		magJ       =cpl_table_get_data_double(table, "J_band"     );
		magH       =cpl_table_get_data_double(table, "H_band"     );
		magK       =cpl_table_get_data_double(table, "K_band"     );
	}
	else
	{
		cpl_msg_warning(cpl_func, "Entry not found:  J or H or K-band");
		*error = 1;
	}
	if (cpl_table_has_column(table, "flag"       ))
	{
		flag       =cpl_table_get_data_int   (table, "flag"       );
	}
	else
	{
		cpl_msg_warning(cpl_func, "Entry not found:  flag"       );
		*error = 1;
	}
	if (cpl_table_has_column(table, "diameter"   ))
	{
		diameter   =cpl_table_get_data_double (table, "diameter"   );
	}
	else
	{
		cpl_msg_warning(cpl_func, "Entry not found:  diameter"   );
		*error = 1;
	}
	if (cpl_table_has_column(table, "diameterErr"))
	{
		diameterErr=cpl_table_get_data_double (table, "diameterErr");
	}
	else
	{
		cpl_msg_warning(cpl_func, "Entry not found:  diameterErr");
		*error = 1;
	}

	dimen_calibrator_data=cpl_table_get_nrow(table);

	cpl_msg_info(cpl_func, "Total number of Calibrators in the table: %d",
			dimen_calibrator_data);

	calibrator->calibDistance =FLT_MAX;

	/* Search for the minimum distance and get the diameter */
	for (i=0; i<dimen_calibrator_data;i++)
	{

		searchRA= HOUR_TO_RAD * ((double) hourRA[i] + minuteRA[i]/60.0 +
				secondRA[i]/3600.0);
		searchDEC= DEG_TO_RAD * (((double) degreeDEC[i] + minuteDEC[i]/60.0 +
				secondDEC[i]/3600.0) * signDEC[i]);
		diffAngle = acos ( sin(searchDEC) * sin(calibrator->calibDEC) +
				cos(searchDEC) * cos(calibrator->calibDEC) *
				cos(searchRA - calibrator->calibRA) );


		if (diffAngle < calibrator->calibDistance)
		{
			calibrator->calibDistance    = diffAngle;
			calibrator->calibDiameter    = diameter[i];
			calibrator->calibDiameterErr = diameterErr[i];
			calibNameDatabase_index=i;
			calibrator->calibMagJ        = magJ[i];
			calibrator->calibMagH        = magH[i];
			calibrator->calibMagK        = magK[i];
			calibrator->calibFlag        = flag[i]; // Quality Parameter/Flag
		}

	}

	calibrator->calibNameDatabase=
			cpl_sprintf("%s",Name[calibNameDatabase_index]);

	/* Check if minimum angle is within the threshold */
	if (calibrator->calibDistance <= CALIBRATOR_MIN_THRESHOLD)
	{
		cpl_msg_info(cpl_func,"Closest Calibrator in the database       = "
				"%s         ", calibrator->calibNameDatabase);
		cpl_msg_info(cpl_func,"Calibrator Diameter                      = "
				"%f marcsec ", calibrator->calibDiameter);
		cpl_msg_info(cpl_func,"Calibrator Diameter error                = "
				"%f marcsec ", calibrator->calibDiameterErr);
		cpl_msg_info(cpl_func,"Computed Distance to Observed Calibrator = "
				"%f arcsec  ", RAD_TO_ARCSEC * calibrator->calibDistance);
		cpl_msg_info(cpl_func,"Calibrator Magnitude in the J-Band            = "
				"%g mag     ", calibrator->calibMagJ);
		cpl_msg_info(cpl_func,"Calibrator Magnitude in the H-Band            = "
				"%g mag     ", calibrator->calibMagH);
		cpl_msg_info(cpl_func,"Calibrator Magnitude in the K-Band            = "
				"%g mag     ", calibrator->calibMagK);
		cpl_msg_info(cpl_func,"Calibrator quality flag                  = "
				"%d         ", calibrator->calibFlag);

	}
	else
	{
		cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
				"Cannot find a close match in the database!");
		cpl_msg_warning(cpl_func,"Cannot find a close match in the database");
		*error = 1;
	}

	/* Release memory */
	if(table !=NULL) cpl_table_delete(table);

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



/*----------------------------------------------------------------------------*/
/**
  @brief    Computes Bessel function of first kind of order 1 and the associated
  errors.
  @param    xArg      Bessel argument
  @param    xArgErr   Error of argument
  @param    xArgErr   Error of computed Bessel function
  @return   Bessel function of first kind
 */
/*----------------------------------------------------------------------------*/
static double amber_computeBesselK1o1 (double xArg, double xArgErr,
		double *besselK1o1Err)
{

	/*    Algorithm
    -----------*/

	*besselK1o1Err = (
			0.5 * xArgErr +
			0.062500000000 * xArgErr * fabs (3.0 * xArg * xArg) +
			0.002604166666 * xArgErr * fabs (5.0 * xArg * xArg * xArg * xArg) +
			0.000054253472 * xArgErr * fabs (7.0 * xArg * xArg * xArg * xArg *
					xArg * xArg));

	return (
			0.5 * xArg -
			0.062500000000 * xArg * xArg * xArg +
			0.002604166666 * xArg * xArg * xArg * xArg * xArg -
			0.000054253472 * xArg * xArg * xArg * xArg * xArg * xArg * xArg);
}


static cpl_error_code amber_get_calibparam(double * Average,
		cpl_propertylist * plist, const char * startkey, const char * endkey)
{
	double startvalue=0.;
	double endvalue=0.;
	int local_error=0;
	*Average=0.;


	if (cpl_propertylist_has(plist, startkey) == 1)
	{
		startvalue=(cpl_propertylist_get_double(plist, startkey));
	}
	else
	{
		local_error=1;
	}

	if (cpl_propertylist_has(plist, endkey) == 1)
	{
		endvalue=(cpl_propertylist_get_double(plist, endkey));
	}
	else
	{
		local_error=1;
	}

	if (!local_error)
	{
		*Average=(endvalue+startvalue)/2.;

		return 0;
	}
	else
	{
		return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
				"Can not compute the average!");
	}
}


static cpl_error_code amber_computeExpectedVis(CalibratorParam    *calibrator)
{
	int          dimen=0;
	int          i=0;
	double       besselK1o1=0.;
	double       besselK1o1Err=0.;
	double       xArg=0.;
	double       xArgErr=0.;
	cpl_vector * vec_xArg=NULL;
	cpl_vector * vec_xArgErr=NULL;
	cpl_vector * vec_ArgTmp=NULL;
	cpl_vector * vec_besselK1o1=NULL;
	cpl_vector * vec_besselK1o1Err=NULL;
	cpl_vector * vec_xArg_fabs=NULL;
	cpl_vector * vec_besselK1o1_fabs=NULL;
	cpl_vector * vec_calibVis_fabs=NULL;

	dimen=cpl_vector_get_size(calibrator->calibInvWavelength);

	/*Local vectors*/
	vec_ArgTmp=cpl_vector_duplicate(calibrator->calibInvWavelength);
	vec_besselK1o1=cpl_vector_new(dimen);
	vec_besselK1o1Err=cpl_vector_new(dimen);

	cpl_vector_multiply_scalar(vec_ArgTmp,(AMBER_PI * DIAMETER_FACTOR *
			calibrator->calibPblAverage ));

	/* Compute Bessel function of first kind of order 1
    For faster processing most divisions are stated by multiplying factors */

	vec_xArg   =cpl_vector_duplicate(vec_ArgTmp);
	vec_xArgErr=cpl_vector_duplicate(vec_ArgTmp);
	cpl_vector_delete(vec_ArgTmp);
	cpl_vector_multiply_scalar(vec_xArg,calibrator->calibDiameter);
	cpl_vector_multiply_scalar(vec_xArgErr,calibrator->calibDiameterErr);

	for(i=0;i<dimen;i++)
	{
		xArg=cpl_vector_get(vec_xArg,i);
		xArgErr=cpl_vector_get(vec_xArgErr,i);
		besselK1o1=0.;
		besselK1o1Err=0.;
		besselK1o1 = amber_computeBesselK1o1 (xArg, xArgErr, &besselK1o1Err);
		cpl_vector_set(vec_besselK1o1,i,besselK1o1);
		cpl_vector_set(vec_besselK1o1Err,i,besselK1o1Err);
	}

	/* Compute expected visibilities */

	calibrator->calibVis = cpl_vector_duplicate(vec_besselK1o1);
	cpl_vector_divide(calibrator->calibVis, vec_xArg);
	cpl_vector_multiply_scalar(calibrator->calibVis, 2.);

	calibrator->calibVisSqrd = cpl_vector_duplicate(calibrator->calibVis);
	cpl_vector_multiply(calibrator->calibVisSqrd, calibrator->calibVis);

	/* 	cpl_vector_dump(calibrator->calibVis,NULL); */
	/* 	cpl_vector_dump(calibrator->calibVisSqrd,NULL); */


	/* Compute error for visibility */

	vec_xArg_fabs=cpl_vector_new(dimen);
	vec_besselK1o1_fabs=cpl_vector_new(dimen);
	vec_calibVis_fabs=cpl_vector_new(dimen);

	for(i=0;i<dimen;i++)
	{
		cpl_vector_set(vec_xArg_fabs      ,i,
				fabs(cpl_vector_get(vec_xArg,i)));
		cpl_vector_set(vec_besselK1o1_fabs,i,
				fabs(cpl_vector_get(vec_besselK1o1,i)));
		cpl_vector_set(vec_calibVis_fabs  ,i,
				fabs(cpl_vector_get(calibrator->calibVis,i)));
	}

	cpl_vector_divide(vec_besselK1o1Err, vec_xArg_fabs);
	cpl_vector_divide(vec_besselK1o1_fabs, vec_xArg);
	cpl_vector_divide(vec_besselK1o1_fabs, vec_xArg);
	cpl_vector_multiply(vec_besselK1o1_fabs, vec_xArgErr);
	cpl_vector_add(vec_besselK1o1Err,vec_besselK1o1_fabs);
	cpl_vector_multiply_scalar(vec_besselK1o1Err,2.0);

	calibrator->calibVisErr=cpl_vector_duplicate(vec_besselK1o1Err);

	/* Compute error for squared visibility */

	cpl_vector_multiply(vec_calibVis_fabs,calibrator->calibVisErr);
	cpl_vector_multiply_scalar(vec_calibVis_fabs,2.0);

	calibrator->calibVisSqrdErr=cpl_vector_duplicate(vec_calibVis_fabs);

/*
	cpl_vector_dump(calibrator->calibVis,NULL);
	cpl_vector_dump(calibrator->calibVisSqrd,NULL);
	cpl_vector_dump(calibrator->calibVisErr,NULL);
	cpl_vector_dump(calibrator->calibVisSqrdErr,NULL);
*/
	/*exit(0);*/

	/*Free the memory*/
	cpl_vector_delete(vec_besselK1o1);
	cpl_vector_delete(vec_besselK1o1Err);

	cpl_vector_delete(vec_xArg_fabs);
	cpl_vector_delete(vec_besselK1o1_fabs);

	cpl_vector_delete(vec_xArg);
	cpl_vector_delete(vec_xArgErr);
	cpl_vector_delete(vec_calibVis_fabs);

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

}

static cpl_error_code amber_getInvWavelength (const char * filename,
		CalibratorParam *calibrator)
{
	int            ext_oi_wavelength=0;
	cpl_table    * table=NULL;

	/* Load extension  OI_WAVELENGTH  */
	ext_oi_wavelength=cpl_fits_find_extension(filename,"OI_WAVELENGTH");
	table = cpl_table_load(filename, ext_oi_wavelength, 0);
	if (table == NULL) {
		return cpl_error_set_message(cpl_func, cpl_error_get_code(),
				"Could not load the table");
	}

	/*Load column EFF_WAVE ...*/
	if (cpl_table_has_column(table,"EFF_WAVE")){
		const int dimen_table=cpl_table_get_nrow(table);
		int       i;
		/*Allocate memory*/
		calibrator->calibInvWavelength=cpl_vector_new(dimen_table);

		/*Write the inverse (!!!) values in the cpl_vector*/
		for(i=0;i<dimen_table;i++){
			cpl_vector_set(calibrator->calibInvWavelength,i,
					1./cpl_table_get_float(table,"EFF_WAVE",i,NULL));
		}
	}
	cpl_table_delete(table);

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

static cpl_error_code midi_amber_computeExpectedVis(CalibratorParam *calibrator)
{
	/*    Local Declarations
    --------------------*/

	double      besselK1o1, besselK1o1Err, xArg, xArgErr;

	/*    Algorithm
    -----------*/


	/* Compute Bessel function of first kind of order 1
    For faster processing most divisions are stated by multiplying factors */
	xArg=(double) (AMBER_PI * DIAMETER_FACTOR * calibrator->calibDiameter
			* calibrator->calibPblAverage * INVERSE_WAVELENGTH);
	xArgErr=(double) (AMBER_PI * DIAMETER_FACTOR * calibrator->calibDiameterErr
			* calibrator->calibPblAverage * INVERSE_WAVELENGTH);
	besselK1o1=amber_computeBesselK1o1 (xArg, xArgErr, &besselK1o1Err);

	/* Compute expected visibilities */
	calibrator->mcalibVis = (float) (2.0 * (besselK1o1 / xArg));
	calibrator->mcalibVisSqrd = calibrator->mcalibVis * calibrator->mcalibVis;

	/* Compute errors */
	calibrator->mcalibVisErr = (float) (2.0 * (besselK1o1Err/fabs(xArg)
			+ xArgErr * (fabs(besselK1o1)/(xArg * xArg))));
	calibrator->mcalibVisSqrdErr = 2.0 * (calibrator->mcalibVisErr
			* fabs (calibrator->mcalibVis));

	cpl_msg_info(cpl_func,"Expected Visibility                      = %f \n",
			calibrator->mcalibVis);
	cpl_msg_info(cpl_func,"Expected Visibility Error                = %f \n",
			calibrator->mcalibVisErr);
	cpl_msg_info(cpl_func,"Expected Squared Visibility              = %f \n",
			calibrator->mcalibVisSqrd);
	cpl_msg_info(cpl_func,"Expected Squared Visibility Error        = %f \n",
			calibrator->mcalibVisSqrdErr);

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

}
static cpl_error_code amber_init_calibrator (CalibratorParam *calibrator)
{
	calibrator->calibRA=0.;
	calibrator->calibDEC=0.;
	calibrator->calibDistance=0.;
	calibrator->calibNameDatabase=NULL;
	calibrator->calibNameObserved=NULL;
	calibrator->calibInvWavelength=NULL;
	calibrator->calibVis=NULL;
	calibrator->calibVisErr=NULL;
	calibrator->calibVisSqrd=NULL;
	calibrator->calibVisSqrdErr=NULL;
	calibrator->calibPblAverage=0.;
	calibrator->calibParangAverage=0.;
	calibrator->calibDiameter=0.;
	calibrator->calibDiameterErr=0.;
	calibrator->calibMagJ=-9.9e+100;
	calibrator->calibMagH=-9.9e+100;
	calibrator->calibMagK=-9.9e+100;
	calibrator->calibFlag=0;
	calibrator->calibEsoTrusted=TRUE;
	calibrator->mcalibVis=0.;         /*only for crosscheck with MIDI results*/
	calibrator->mcalibVisErr=0.;      /*only for crosscheck with MIDI results*/
	calibrator->mcalibVisSqrd=0.;     /*only for crosscheck with MIDI results*/
	calibrator->mcalibVisSqrdErr=0.;  /*only for crosscheck with MIDI results*/

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

}

cpl_error_code amber_TransferFunction(const char * recipename,
		const char * outname_selector, cpl_parameterlist *parlist,
		cpl_frameset * framelist)
{
	int error;
	int nTel=0;
	cpl_propertylist * plist=NULL;
	CalibratorParam *calibrator12=NULL;
	CalibratorParam *calibrator13=NULL;
	CalibratorParam *calibrator23=NULL;

	/* Extract number of telescopes */
	plist = cpl_propertylist_load(outname_selector, 0);
	if (plist==NULL)
	{
		cpl_msg_warning(cpl_func,"Error loading the primary header of %s",
				outname_selector);
		/* Propagate error, if any */
		return cpl_error_set(cpl_func, cpl_error_get_code());
	}
	if (cpl_propertylist_has(plist, "ESO ISS CONF NTEL") == 1)
	{
		nTel=cpl_propertylist_get_int(plist, "ESO ISS CONF NTEL");
	}
	else
	{
		cpl_propertylist_delete(plist);
		return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
				"Can not find number of telescopes from the header!");
	}


	calibrator12=cpl_calloc(1,sizeof(CalibratorParam));
	if (amber_init_calibrator(calibrator12))
	{
		cpl_msg_warning (cpl_func, "amber_init_calibrator: %s",
				cpl_error_get_message());
	}
	if (amber_identifyCalibrator (outname_selector, calibrator12,
			"12", framelist, &error))
	{
		cpl_propertylist_delete(plist);
		cpl_free((void *)calibrator12->calibNameObserved);
		cpl_free((void *)calibrator12->calibNameDatabase);
		cpl_free(calibrator12);
		return cpl_error_set(cpl_func, cpl_error_get_code());
	}

	if(amber_getInvWavelength (outname_selector, calibrator12))
	{
		cpl_msg_warning (cpl_func, "amber_getInvWavelength: %s",
				cpl_error_get_message());
	}

	if(cpl_error_get_code())
	{

		cpl_propertylist_delete(plist);
		cpl_free((void *)calibrator12->calibNameObserved);
		cpl_free((void *)calibrator12->calibNameDatabase);
		cpl_vector_delete(calibrator12->calibInvWavelength);
		cpl_free(calibrator12);
		return cpl_error_set(cpl_func, cpl_error_get_code());
	}

	/*fake the midi value for testing purpose*/
	/*
	cpl_vector_fill(calibrator12->calibInvWavelength,100000.);
	cpl_vector_dump(calibrator12->calibInvWavelength,NULL);
	 */
	if(amber_computeExpectedVis (calibrator12))
	{
		cpl_msg_warning (cpl_func, "amber_computeExpectedVis: %s",
				cpl_error_get_message());
	}


	if(nTel>2)
	{
		calibrator13=cpl_calloc(1,sizeof(CalibratorParam));
		calibrator23=cpl_calloc(1,sizeof(CalibratorParam));

		if (amber_init_calibrator(calibrator13))
		{
			cpl_msg_warning (cpl_func, "amber_init_calibrator: %s",
					cpl_error_get_message());
		}
		if (amber_init_calibrator(calibrator23))
		{
			cpl_msg_warning (cpl_func, "amber_init_calibrator: %s",
					cpl_error_get_message());
		}

		if (amber_identifyCalibrator (outname_selector, calibrator13,
				"13", framelist, &error))
		{
			cpl_msg_warning (cpl_func, "amber_identifyCalibrator: %s",
					cpl_error_get_message());
		}
		if (amber_identifyCalibrator (outname_selector, calibrator23,
				"23", framelist, &error))
		{
			cpl_msg_warning (cpl_func, "amber_identifyCalibrator: %s",
					cpl_error_get_message());
		}

		if(amber_getInvWavelength (outname_selector, calibrator13))
		{
			cpl_msg_warning (cpl_func, "amber_getInvWavelength: %s",
					cpl_error_get_message());
		}
		if(amber_getInvWavelength (outname_selector, calibrator23))
		{
			cpl_msg_warning (cpl_func, "amber_getInvWavelength: %s",
					cpl_error_get_message());
		}
		if(amber_computeExpectedVis (calibrator13))
		{
			cpl_msg_warning (cpl_func, "amber_computeExpectedVis: %s",
					cpl_error_get_message());
		}
		if(amber_computeExpectedVis (calibrator23))
		{
			cpl_msg_warning (cpl_func, "amber_computeExpectedVis: %s",
					cpl_error_get_message());
		}
	}


	if(amber_TransferFunctionCompute(calibrator12, calibrator13,
			calibrator23, recipename, outname_selector, parlist, framelist))
	{
		cpl_msg_warning (cpl_func, "amber_TransferFunctionCompute: %s",
				cpl_error_get_message());
	}
	/*
	midi_computeExpectedVis (calibrator12);
	cpl_msg_info(cpl_func,"AAAAAAAAAAAAAAAAAAAAAAAAAAAA");
	cpl_msg_info(cpl_func,"Expected Visibility               = %f \n",
	calibrator12->mcalibVis);
	cpl_msg_info(cpl_func,"Expected Visibility Error         = %f \n",
	calibrator12->mcalibVisErr);
	cpl_msg_info(cpl_func,"Expected Squared Visibility       = %f \n",
	calibrator12->mcalibVisSqrd);
	cpl_msg_info(cpl_func,"Expected Squared Visibility Error = %f \n",
	calibrator12->mcalibVisSqrdErr);
	 */


	/*Free the memory*/
	cpl_free((void *)calibrator12->calibNameDatabase);
	cpl_free((void *)calibrator12->calibNameObserved);
	cpl_vector_delete(calibrator12->calibInvWavelength);
	cpl_vector_delete(calibrator12->calibVis);
	cpl_vector_delete(calibrator12->calibVisErr);
	cpl_vector_delete(calibrator12->calibVisSqrd);
	cpl_vector_delete(calibrator12->calibVisSqrdErr);


	if(nTel>2)
	{
		cpl_free((void *)calibrator13->calibNameDatabase);
		cpl_free((void *)calibrator13->calibNameObserved);
		cpl_vector_delete(calibrator13->calibInvWavelength);
		cpl_vector_delete(calibrator13->calibVis);
		cpl_vector_delete(calibrator13->calibVisErr);
		cpl_vector_delete(calibrator13->calibVisSqrd);
		cpl_vector_delete(calibrator13->calibVisSqrdErr);

		cpl_free((void *)calibrator23->calibNameDatabase);
		cpl_free((void *)calibrator23->calibNameObserved);
		cpl_vector_delete(calibrator23->calibInvWavelength);
		cpl_vector_delete(calibrator23->calibVis);
		cpl_vector_delete(calibrator23->calibVisErr);
		cpl_vector_delete(calibrator23->calibVisSqrd);
		cpl_vector_delete(calibrator23->calibVisSqrdErr);
	}

	cpl_free(calibrator12);
	cpl_free(calibrator13);
	cpl_free(calibrator23);
	cpl_propertylist_delete(plist);


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

}
static cpl_error_code amber_TransferFunctionCompute(
		CalibratorParam *calibrator12, CalibratorParam *calibrator13,
		CalibratorParam *calibrator23,const char * recipename,
		const char * filename, cpl_parameterlist *parlist,
		cpl_frameset * framelist)
{
	int iFrame=0;
	int iWave=0;
	const char * outname_trf=NULL;
	const char * outname_trf_paf=NULL;
	char       * local_filename=NULL;
	const char * tag_trf=NULL;
	cpl_frame * final_local_frame=NULL;
	cpl_frame * local_frame=NULL;
	cpl_propertylist * pHeader=NULL;
	amdlibERROR_MSG  errMsg;
	amdlibPHOTOMETRY photometry = {NULL};
	amdlibWAVELENGTH wave = {NULL};
	amdlibPISTON     opd = {NULL};
	amdlibOI_TARGET  target = {NULL};
	amdlibOI_ARRAY   array = {NULL};
	amdlibVIS        vis = {NULL};
	amdlibVIS2       vis2 = {NULL};
	amdlibVIS3       vis3 = {NULL};
	amdlibINS_CFG    insCfg;
	amdlibSPECTRUM   spectrum={NULL};

	amdlibLogTrace("amdlibPerformSelection()");

	/* Load input OI-FITS file */
	amdlibLogInfo("Loading OI-FITS file...");
	/*	    amdlibLogInfoDetail(inputFile);*/

	amdlibClearInsCfg(&insCfg);

	if (amdlibLoadOiFile(filename, &insCfg, &array, &target, &photometry,
			&vis, &vis2, &vis3, &wave, &opd, &spectrum,
			errMsg) != amdlibSUCCESS)
	{
		amdlibLogError("Could not load OI-FITS file '%s'", filename);
		amdlibLogErrorDetail(errMsg);
		return amdlibFAILURE;
	}

	/*cpl_msg_info(cpl_func, "AA: %g",vis2.table[0].vis2[0]);*/

	/* Transfer function for Visibility2*/
	for(iFrame = 0; iFrame < (vis.nbFrames * vis.nbBases); iFrame++){
		for(iWave = 0; iWave <wave.nbWlen; iWave++){
			/*Vis2*/
			vis2.table[iFrame].vis2[iWave] =
					(vis2.table[iFrame].vis2[iWave])/
					cpl_vector_get(calibrator12->calibVisSqrd, iWave);
			/*
					cpl_vector_get(calibrator12->calibVisSqrd, iWave)
					/ (vis2.table[iFrame].vis2[iWave]);
			 */
			/*Vis2 Error propagation*/
			vis2.table[iFrame].vis2Error[iWave] = sqrt(
					(cpl_vector_get(calibrator12->calibVisSqrdErr, iWave) *
							cpl_vector_get(calibrator12->calibVisSqrdErr, iWave))
							+ ((vis2.table[iFrame].vis2Error[iWave]) *
									(vis2.table[iFrame].vis2Error[iWave])));

		}
		if(vis.nbBases>1){
			iFrame++;
			for(iWave = 0; iWave < wave.nbWlen; iWave++){
				/*Vis2*/
				vis2.table[iFrame].vis2[iWave] =
						(vis2.table[iFrame].vis2[iWave])/
						cpl_vector_get(calibrator13->calibVisSqrd, iWave);
				/*
						cpl_vector_get(calibrator13->calibVisSqrd, iWave)
						/ (vis2.table[iFrame].vis2[iWave]);
				 */
				/*Vis2 Error propagation*/
				vis2.table[iFrame].vis2Error[iWave] = sqrt(
						(cpl_vector_get(calibrator13->calibVisSqrdErr, iWave) *
								cpl_vector_get(calibrator13->calibVisSqrdErr, iWave))
								+ ((vis2.table[iFrame].vis2Error[iWave]) *
										(vis2.table[iFrame].vis2Error[iWave])));

			}
			iFrame++;
			for(iWave = 0; iWave < wave.nbWlen; iWave++){
				/*Vis2*/
				vis2.table[iFrame].vis2[iWave] =
						(vis2.table[iFrame].vis2[iWave])/
						cpl_vector_get(calibrator23->calibVisSqrd, iWave);
				/*
						cpl_vector_get(calibrator23->calibVisSqrd, iWave)
						/ (vis2.table[iFrame].vis2[iWave]);
				 */
				/*Vis2 Error propagation*/
				vis2.table[iFrame].vis2Error[iWave] = sqrt(
						(cpl_vector_get(calibrator23->calibVisSqrdErr, iWave) *
								cpl_vector_get(calibrator23->calibVisSqrdErr, iWave))
								+ ((vis2.table[iFrame].vis2Error[iWave]) *
										(vis2.table[iFrame].vis2Error[iWave])));

			}
		}
	}


	/*Workarround to inherit important parameters from the selected frame and
	 * not the first raw frame*/


	/*basename() may modify its argument, therefore I duplicate it*/

	local_filename=cpl_sprintf("%s", filename);
	local_frame = cpl_frameset_get_first(framelist);
	while(local_frame)
	{
		if(!strcmp(cpl_frame_get_filename(local_frame),filename))
		{
			break;
		}
		else{
			local_frame = cpl_frameset_get_next( framelist );
		}
	}

	cpl_msg_info(cpl_func,"Frame to inherit: %s",
			cpl_frame_get_filename(local_frame));

	final_local_frame=cpl_frame_duplicate(local_frame);
	cpl_frame_set_group(final_local_frame,CPL_FRAME_GROUP_PRODUCT);

	outname_trf = cpl_sprintf("trf_%s", basename(local_filename));
	cpl_msg_info(cpl_func,"Frame to write the corresponding transfer function:"
			" %s",outname_trf);
	pHeader = cpl_propertylist_load(filename, 0 );

	if( cpl_dfs_setup_product_header(  pHeader,
			final_local_frame,
			framelist,
			parlist,
			recipename, /* const char *  recid,  */
			PACKAGE "/" PACKAGE_VERSION, /* const char *  pipeline_id,  */
			PRODUCT_DID,  /* const char *  dictionary_id */
			local_frame
	)  != CPL_ERROR_NONE )
	{
		/* Error */
		cpl_msg_warning(cpl_func, "Error in setting up the product header." );
	}


	amber_qc(&wave, &vis, &vis2, &vis3, NULL, pHeader, "trf");

	if (cpl_propertylist_has(pHeader, "ESO QC BAND") == 1)
	{
		tag_trf=cpl_sprintf("AMBER_TRF_%s",
				cpl_propertylist_get_string(pHeader, "ESO QC BAND"));
	}
	else{

		tag_trf=cpl_sprintf("AMBER_TRF");
	}




	cpl_propertylist_update_string(pHeader, CPL_DFS_PRO_CATG, tag_trf);
	cpl_free((void *)tag_trf);

	cpl_propertylist_update_double(pHeader,  "ESO QC OBS CAL RA"      ,
			calibrator12->calibRA);
	cpl_propertylist_update_double(pHeader,  "ESO QC OBS CAL DEC"     ,
			calibrator12->calibDEC);
	cpl_propertylist_update_string(pHeader,  "ESO QC OBS NAME"         ,
			calibrator12->calibNameObserved);
	cpl_propertylist_update_string(pHeader,  "ESO QC DB NAME"         ,
			calibrator12->calibNameDatabase);
	cpl_propertylist_update_double(pHeader,  "ESO QC DB DIAM"         ,
			calibrator12->calibDiameter);
	cpl_propertylist_update_double(pHeader,  "ESO QC DB DIAM ERR "    ,
			calibrator12->calibDiameterErr);
	cpl_propertylist_update_double(pHeader,  "ESO QC DB DIST"         ,
			RAD_TO_ARCSEC * calibrator12->calibDistance);
	cpl_propertylist_update_double(pHeader,  "ESO QC DB MAG JBAND"    ,
			calibrator12->calibMagJ);
	cpl_propertylist_update_double(pHeader,  "ESO QC DB MAG HBAND"    ,
			calibrator12->calibMagH);
	cpl_propertylist_update_double(pHeader,  "ESO QC DB MAG KBAND"    ,
			calibrator12->calibMagK);
	cpl_propertylist_update_int   (pHeader,  "ESO QC DB FLAG"         ,
			calibrator12->calibFlag);
	cpl_propertylist_update_double(pHeader,  "ESO QC OBS AVR PARANG"  ,
			calibrator12->calibParangAverage);
	cpl_propertylist_update_bool(pHeader,  "ESO QC OBS ESOTRUSTED"  ,
			calibrator12->calibEsoTrusted);

	cpl_propertylist_update_double(pHeader,  "ESO QC OBS AVR PBL12 "    ,
			calibrator12->calibPblAverage);
	if(vis.nbBases>1){
		cpl_propertylist_update_double(pHeader,  "ESO QC OBS AVR PBL13 "    ,
				calibrator13->calibPblAverage);
		cpl_propertylist_update_double(pHeader,  "ESO QC OBS AVR PBL23 "    ,
				calibrator23->calibPblAverage);
	}

	cpl_propertylist_set_comment (pHeader, "ESO QC OBS CAL RA"      ,
			"Observed Calibrator RA in radians");
	cpl_propertylist_set_comment (pHeader, "ESO QC OBS CAL DEC"     ,
			"Observed Calibrator DEC in radians");
	cpl_propertylist_set_comment (pHeader, "ESO QC OBS NAME"        ,
			"Observed Calibrator name");
	cpl_propertylist_set_comment (pHeader, "ESO QC DB NAME"         ,
			"Closest Calibrator in the database");
	cpl_propertylist_set_comment (pHeader, "ESO QC DB DIAM"         ,
			"Calibrator Diameter in marcsec");
	cpl_propertylist_set_comment (pHeader, "ESO QC DB DIAM ERR "    ,
			"Calibrator Diameter error in marcsec");
	cpl_propertylist_set_comment (pHeader, "ESO QC DB DIST"         ,
			"Computed Distance in arcsec");
	cpl_propertylist_set_comment (pHeader, "ESO QC DB MAG JBAND"    ,
			"Calibrator Magnitude in the J-Band");
	cpl_propertylist_set_comment (pHeader, "ESO QC DB MAG HBAND"    ,
			"Calibrator Magnitude in the H-Band");
	cpl_propertylist_set_comment (pHeader, "ESO QC DB MAG KBAND"    ,
			"Calibrator Magnitude in the K-Band");
	cpl_propertylist_set_comment (pHeader, "ESO QC DB FLAG"         ,
			"Calibrator Quality Flag");
	cpl_propertylist_set_comment (pHeader, "ESO QC OBS AVR PARANG"  ,
			"Observed Calibrator Average PARANG in degrees");
	cpl_propertylist_set_comment (pHeader, "ESO QC OBS ESOTRUSTED"  ,
			"Observed Calibrator Trusted status");


	cpl_propertylist_set_comment (pHeader, "ESO QC OBS AVR PBL12 "    ,
			"Observed Calibrator Average PBL12 in meter");
	if(vis.nbBases>1){
		cpl_propertylist_set_comment (pHeader, "ESO QC OBS AVR PBL13 "    ,
				"Observed Calibrator Average PBL13 in meter");
		cpl_propertylist_set_comment (pHeader, "ESO QC OBS AVR PBL23 "    ,
				"Observed Calibrator Average PBL23 in meter");
	}

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

	cpl_image_save(NULL, outname_trf, CPL_BPP_16_SIGNED,pHeader,CPL_IO_DEFAULT);
	outname_trf_paf=cpl_sprintf("qc_trf_%s.paf", basename(local_filename));


	if (cpl_propertylist_has(pHeader, "ESO QC ARC") == 1)
	{
		cpl_propertylist_append_string(pHeader,"ARCFILE",
				(cpl_propertylist_get_string(pHeader, "ESO QC ARC")));
		//cpl_dfs_save_paf("AMBER", recipename, pHeader, outname_trf_paf);
		cpl_propertylist_erase(pHeader,"ARCFILE");
	}


	cpl_free((void *)outname_trf_paf);
	cpl_propertylist_delete(pHeader);


	amdlibSaveOiFile(outname_trf, &insCfg, &array, &target,
			&photometry, &vis, &vis2, &vis3, &wave, &opd, &spectrum, errMsg);

	/*	amdlibReleaseSelection(&selectedFrames);*/
	amdlibReleaseOiArray(&array);
	amdlibReleaseOiTarget(&target);
	amdlibReleasePhotometry(&photometry);
	amdlibReleaseVis(&vis);
	amdlibReleaseVis2(&vis2);
	amdlibReleaseVis3(&vis3);
	amdlibReleaseWavelength(&wave);
	amdlibReleaseSpectrum(&spectrum);
	amdlibReleasePiston(&opd);

	/*Append the saved image to the global framelist*/

	cpl_frame_set_filename(final_local_frame, outname_trf);

	cpl_free((void *)outname_trf);
	cpl_free((void *)local_filename);
	cpl_frameset_insert( framelist, final_local_frame);
	/*cpl_frameset_dump(framelist, NULL);*/


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

}
