/* $Id: hawki_util_extinction.c,v 1.4 2013/03/11 11:03:00 cgarcia Exp $
 *
 * This file is part of the HAWKI 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
 */

/*
 * $Author: cgarcia $
 * $Date: 2013/03/11 11:03:00 $
 * $Revision: 1.4 $
 * $Name: hawki-1_8_12 $
 */

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

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

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

#include "irplib_utils.h"
#include "irplib_strehl.h"
#include "irplib_stdstar.h"
#include "irplib_cat.h"

#include "hawki_utils.h"
#include "hawki_calib.h"
#include "hawki_load.h"
#include "hawki_save.h"
#include "hawki_pfits.h"
#include "hawki_dfs.h"
#include "hawki_alloc.h"

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

#ifdef __cplusplus
extern "C"
#endif
int cpl_plugin_get_info(cpl_pluginlist * list);

static int hawki_util_extinction_create(cpl_plugin *) ;
static int hawki_util_extinction_exec(cpl_plugin *) ;
static int hawki_util_extinction_destroy(cpl_plugin *) ;
static int hawki_util_extinction(cpl_parameterlist *, cpl_frameset *) ;

static cpl_table ** hawki_util_extinction_reduce
(cpl_frameset    *   set,
 const char      *   stdstars,
 const char      *   bpm,
 const char      *   flat,
 cpl_table       **  raw_zpoint_stats,
 int             *   star_labels,
 int             *   det_labels,
 cpl_imagelist   **  images);
static int hawki_util_extinction_save_photsol
(cpl_table           **  photsol_table,
 cpl_frameset        *   zpoint_frames,
 cpl_parameterlist   *   parlist,
 cpl_frameset        *   set);
static cpl_table ** hawki_util_extinction_photom
(cpl_frameset *   std_stars_photom);
static cpl_table * hawki_util_extinction_get_photomsol
(cpl_table * std_stars_photom);
static int hawki_util_extinction_compute_keywords
(cpl_frameset * set, 
 int          * det_labels);

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


static char hawki_util_extinction_description[] =
"hawki_util_extinction -- Zero point recipe with extinction\n"
"The input of the recipe files listed in the Set Of Frames (sof-file)\n"
"must be tagged as:\n"
"hawki_util_extinction.fits ("HAWKI_CALPRO_ZPOINT_TAB"): Zero point solution table\n"
"The recipe creates as an output:\n"
"hawki_cal_photom.fits ("HAWKI_CALPRO_PHOT_TAB"): Photometric solution\n"
"Return code:\n"
"esorex exits with an error code of 0 if the recipe completes successfully\n"
"or 1 otherwise";

/*-----------------------------------------------------------------------------
                                Functions code
 -----------------------------------------------------------------------------*/

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

  This function is exported.
 */
/*----------------------------------------------------------------------------*/
int cpl_plugin_get_info(cpl_pluginlist * list)
{
    cpl_recipe  *   recipe = cpl_calloc(1, sizeof(*recipe)) ;
    cpl_plugin  *   plugin = &recipe->interface ;

    cpl_plugin_init(plugin,
                    CPL_PLUGIN_API,
                    HAWKI_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "hawki_util_extinction",
                    "Zero point with extinction computation recipe",
                    hawki_util_extinction_description,
                    "Cesar Enrique Garcia Dabo",
                    "cgarcia@eso.org",
                    hawki_get_license(),
                    hawki_util_extinction_create,
                    hawki_util_extinction_exec,
                    hawki_util_extinction_destroy) ;

    cpl_pluginlist_append(list, plugin) ;

    return 0;
}

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

  Create the recipe instance and make it available to the application using the
  interface.
 */
/*----------------------------------------------------------------------------*/
static int hawki_util_extinction_create(cpl_plugin * plugin)
{
    cpl_recipe      * recipe ;
    cpl_parameter   * p ;

    /* Get the recipe out of the plugin */
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE)
        recipe = (cpl_recipe *)plugin ;
    else return -1 ;

    /* Create the parameters list in the cpl_recipe object */
    recipe->parameters = cpl_parameterlist_new() ;
    if (recipe->parameters == NULL)
        return 1;

    /* Fill the parameters list */

    /* Return */
    return 0;
}

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

    /* Get the recipe out of the plugin */
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE)
        recipe = (cpl_recipe *)plugin ;
    else return -1 ;

    /* Issue a banner */
    hawki_print_banner();

    return hawki_util_extinction(recipe->parameters, recipe->frames) ;
}

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

    /* Get the recipe out of the plugin */
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE)
        recipe = (cpl_recipe *)plugin ;
    else return -1 ;

    cpl_parameterlist_delete(recipe->parameters) ;
    return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    The recipe data reduction part is implemented here
  @param    parlist     the parameters list
  @param    framelist   the frames list
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int hawki_util_extinction(
        cpl_parameterlist   *   parlist,
        cpl_frameset        *   framelist)
{
    cpl_frameset    *   std_stars_photom ;
    cpl_table       **  zpoint_tables;

    /* Initialise Output */
    std_stars_photom = NULL ;

    /* Identify the RAW and CALIB frames in the input frameset */
    if (hawki_dfs_set_groups(framelist)) {
        cpl_msg_error(__func__, "Cannot identify RAW and CALIB frames") ;
        return -1 ;
    }

    /* Retrieve zpoint tables */
    if ((std_stars_photom = hawki_extract_frameset
            (framelist, HAWKI_CALPRO_ZPOINT_TAB)) == NULL)
    {
        cpl_msg_error(__func__, "Cannot find std stars info (%s)",
        		      HAWKI_CALPRO_ZPOINT_TAB);
        return -1 ;
    }

    /* Compute the zpoint values */
    cpl_msg_info(__func__, "Reduce the data") ;
    cpl_msg_indent_more() ;
    if ((zpoint_tables = hawki_util_extinction_photom(std_stars_photom))==NULL)
    {
        cpl_msg_error(__func__, "Cannot compute photometric solution") ;
        cpl_frameset_delete(std_stars_photom) ;
        cpl_msg_indent_less() ;
        return -1 ;
    }
    cpl_msg_indent_less() ;

    /* Save the products */
    cpl_msg_info(__func__, "Save the products");
    cpl_msg_indent_more() ;
    if (hawki_util_extinction_save_photsol
            (zpoint_tables, std_stars_photom,
             parlist, framelist) == -1)
    {
        cpl_msg_warning(__func__, "Data could not be saved. "
                        "Check permisions or disk space") ;
        hawki_table_delete(zpoint_tables) ;
        cpl_frameset_delete(std_stars_photom);
        cpl_msg_indent_less() ;
        return -1 ;
    }
    cpl_msg_indent_less() ;

    /* Free and return */
    cpl_frameset_delete(std_stars_photom);
    hawki_table_delete(zpoint_tables);

    /* Return */
    if (cpl_error_get_code()) return -1 ;
    else return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Reduce one image
  @param    ilist   the input image list
  @param    pos     the objects positions
  @param    labels  the chip numbers
  @return   the newly allocated table with the following columns:
            POSX POSY ZPOINT FLUX BGD PEAK FWHMX FWHMY
            or NULL in error case
 */
/*----------------------------------------------------------------------------*/
static cpl_table ** hawki_util_extinction_photom
(cpl_frameset *   std_stars_photom)

{
    cpl_table **        photsol;
    int                 nframes;
    double              extinction;
    cpl_bivector    *   iqe_res ;
    int                 iframe;
    int                 idet;
    int                 jdet;
    int                 iext;
    int                 ext_nb;
    cpl_frame       *   ref_frame;

    /* Test entries */
    if (std_stars_photom == NULL) return NULL ;

    /* Initialise */
    photsol = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_table*));
    nframes = cpl_frameset_get_size(std_stars_photom) ;

    /* Get reference */
    ref_frame = cpl_frameset_get_first(std_stars_photom);
    nframes = cpl_frameset_get_size(std_stars_photom);

    /* Loop on detectors */
    for (idet=0 ; idet<HAWKI_NB_DETECTORS; idet++)
    {
    	cpl_table * merged_std_stars_photom;

    	cpl_msg_info(cpl_func, "Working on detector %d", idet +1);
    	cpl_msg_indent_more();
        /* Get the extension */
        ext_nb=hawki_get_ext_from_detector(cpl_frame_get_filename(ref_frame), idet+1);

        /* Load all the tables and merge them */
        for( iframe = 0 ; iframe < nframes ; ++iframe)
        {
        	cpl_frame * this_frame;
        	cpl_table * this_table;
            /* Get the current image */
            this_frame = cpl_frameset_get_frame(std_stars_photom, iframe);
            if(iframe == 0)
            	merged_std_stars_photom = cpl_table_load
            	    (cpl_frame_get_filename(this_frame),ext_nb, 1);
            else
            {
            	cpl_table * this_table =
            			cpl_table_load(cpl_frame_get_filename(this_frame),ext_nb, 1);
            	cpl_table_insert(merged_std_stars_photom, this_table,
            			         cpl_table_get_nrow(merged_std_stars_photom));
                cpl_table_delete(this_table);
            }
        }

        if(cpl_table_get_nrow(merged_std_stars_photom) < 2)
        {
            cpl_msg_error(__func__, "%d stars found. At least 2 stars must be present",
            		cpl_table_get_nrow(merged_std_stars_photom));
            cpl_table_delete(merged_std_stars_photom);
            return NULL;
        }
        cpl_msg_info(__func__,"Number of star measurements %d",
        		     cpl_table_get_nrow(merged_std_stars_photom));

        if((photsol[idet] = hawki_util_extinction_get_photomsol
        		(merged_std_stars_photom)) == NULL)
        {
            cpl_table_delete(merged_std_stars_photom);
            for(jdet=0; jdet < idet; jdet++)
        	    cpl_table_delete(photsol[jdet]);
            cpl_free(photsol);
            return NULL;
        }

        cpl_table_delete(merged_std_stars_photom);
    	cpl_msg_indent_less();
    }
    return photsol;
}

static cpl_table * hawki_util_extinction_get_photomsol
(cpl_table * std_stars_photom)
{
	const double    * instrmag;
	const double    * airmass;
	const double    * catmag;
	cpl_table       * photsol;
	double            zeropoint;
	double            extcoeff;
	cpl_matrix      * xpos;
	cpl_vector      * ypos;
	cpl_polynomial  * phot_coeff;
	int               nstars;
	int               istar;
    const cpl_boolean sampsym = CPL_TRUE;
    const cpl_size         mindeg1d = 0;
    const cpl_size         maxdeg1d = 1;
    cpl_size               pows;


	nstars = cpl_table_get_nrow(std_stars_photom);

	photsol = cpl_table_new(1);
	instrmag = cpl_table_get_data_double_const
			(std_stars_photom, HAWKI_COL_ZPOINT_INSTRMAG);
	airmass = cpl_table_get_data_double_const
			(std_stars_photom, HAWKI_COL_ZPOINT_AIRMASS);
	catmag = cpl_table_get_data_double_const
			(std_stars_photom, HAWKI_COL_ZPOINT_STARMAG);

	if(instrmag == NULL || airmass == NULL || catmag == NULL)
	{
		cpl_msg_error(cpl_func, "Some of the following columns not found "
				      "in table: %s, %s, %s", HAWKI_COL_ZPOINT_INSTRMAG,
				      HAWKI_COL_ZPOINT_AIRMASS, HAWKI_COL_ZPOINT_STARMAG);
		cpl_table_delete(photsol);
		return NULL;
	}

	cpl_table_new_column(photsol, HAWKI_COL_PHOT_FILTER, CPL_TYPE_STRING);
	cpl_table_new_column(photsol, HAWKI_COL_PHOT_ZEROPOINT, CPL_TYPE_DOUBLE);
	cpl_table_set_column_unit(photsol,HAWKI_COL_PHOT_ZEROPOINT,"mag");
	cpl_table_new_column(photsol, HAWKI_COL_PHOT_EXTCOEFF, CPL_TYPE_DOUBLE);
	cpl_table_set_column_unit(photsol,HAWKI_COL_PHOT_EXTCOEFF,"mag/airmass");
        
	/* Make the fit to get the coefficients */
	xpos = cpl_matrix_new(1, nstars);
	ypos = cpl_vector_new(nstars);
	for(istar = 0; istar < nstars; ++istar)
	{
		double y;
		cpl_matrix_set(xpos, 0, istar, airmass[istar]);
		y = catmag[istar] + instrmag[istar];
		cpl_vector_set(ypos, istar, y);
	}
	/* phot_coeff[0] --> ZP
	 * phot_coeff[1] --> -extcoeff
	 */
	phot_coeff = cpl_polynomial_new(1);
	if(cpl_polynomial_fit(phot_coeff, xpos, NULL, ypos, NULL,
			              CPL_FALSE, &mindeg1d, &maxdeg1d) != CPL_ERROR_NONE)
	{
		cpl_msg_error(cpl_func,"Cannot get the photometric solution");
		cpl_table_delete(photsol);
		return NULL;
	}
	cpl_matrix_delete(xpos);
	cpl_vector_delete(ypos);
	pows = 0;
	zeropoint = cpl_polynomial_get_coeff(phot_coeff, &pows);
	pows = 1;
	extcoeff  = -cpl_polynomial_get_coeff(phot_coeff, &pows);
	cpl_polynomial_delete(phot_coeff);

	cpl_table_set_double(photsol, HAWKI_COL_PHOT_ZEROPOINT, 0,
					     zeropoint);
	cpl_table_set_double(photsol, HAWKI_COL_PHOT_EXTCOEFF, 0,
			             extcoeff);

    /* Output results */
	cpl_msg_indent_more();
	cpl_msg_info(__func__, "Zero point: %f", zeropoint);
	cpl_msg_info(__func__, "Extinction coefficient: %f", extcoeff);
	cpl_msg_indent_less();

    return photsol;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Save the zpoint table on disk
  @param    tab         the table to save
  @param    images      the images where the photometry is computed
  @param    parlist     the parameter list
  @param    set         the input frame set
  @return   0 if everything is ok, -1 otherwise
 */
/*----------------------------------------------------------------------------*/
static int hawki_util_extinction_save_photsol
(cpl_table           **  photsol_table,
 cpl_frameset        *   zpoint_frames,
 cpl_parameterlist   *   parlist,
 cpl_frameset        *   set)
{
    cpl_propertylist    **  qclists ;
    const char          *   ref_filename;
    cpl_propertylist    *   inputlist ;
    int                     ext_nb, nframes ;
    const char          *   recipe_name = "hawki_util_extinction" ;
    int                     idet;
    int                     iframe;
    cpl_errorstate          error_prevstate = cpl_errorstate_get();


    /* Initialise */
    nframes = cpl_frameset_get_size(set) ;

    /* Get the reference frame */
    ref_filename = hawki_get_extref_file(set);

    /* Create the QC lists for the extensions */
    qclists = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_propertylist*)) ;
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
    {
        int            this_iframe = -1;

        qclists[idet] = cpl_propertylist_new() ;
        /* These QC are common to all extensions */


        /* Propagate some keywords from input raw frame extensions */
        ext_nb=hawki_get_ext_from_detector(ref_filename, idet+1);
        inputlist = cpl_propertylist_load_regexp(ref_filename, ext_nb,
                HAWKI_HEADER_EXT_FORWARD, 0) ;
        cpl_propertylist_append(qclists[idet], inputlist) ;
        cpl_propertylist_delete(inputlist) ;

    }
    
    /* Write the photometric table  */
    hawki_tables_save(set,
                      parlist,
                      zpoint_frames,
                      (const cpl_table **)photsol_table,
                      recipe_name,
                      HAWKI_CALPRO_PHOT_TAB,
                      HAWKI_PROTYPE_PHOT_TAB,
                      NULL,
                      (const cpl_propertylist **)qclists,
                      "hawki_util_extinction.fits");

    /* Free */
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) {
        cpl_propertylist_delete(qclists[idet]) ;
    }
    cpl_free(qclists) ;

    /* Free */
    if(!cpl_errorstate_is_equal(error_prevstate))
    {
        cpl_errorstate_set(CPL_ERROR_NONE);
        return -1;
    }
    return  0;
}
