/* $Id: hawki_util_gendist.c,v 1.25 2013-09-02 14:34:14 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/*
 * $Author: cgarcia $
 * $Date: 2013-09-02 14:34:14 $
 * $Revision: 1.25 $
 * $Name: not supported by cvs2svn $
 */

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

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

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

#include "irplib_utils.h"

#include "hawki_utils.h"
#include "hawki_load.h"
#include "hawki_pfits.h"
#include "hawki_dfs.h"
#include "hawki_distortion.h"
#include "hawki_save.h"

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

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

static int hawki_util_gendist_create(cpl_plugin *) ;
static int hawki_util_gendist_exec(cpl_plugin *) ;
static int hawki_util_gendist_destroy(cpl_plugin *) ;
static int hawki_util_gendist(cpl_parameterlist *, cpl_frameset *) ;
static cpl_table * hawki_util_gendist_convert(const char *) ; 
static hawki_distortion * hawki_util_gendist_convert_to_images
(const cpl_table * dist_tab,
 int               detector_nx,
 int               detector_ny); 

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

static char hawki_util_gendist_description[] = 
"hawki_util_gendist -- HAWK-I distortion calibration file creation.\n"
"The 4 files (chip 1 2 3 4) listed in the Set Of Frames (sof-file) \n"
"must be tagged:\n"
"raw-file_chip1.txt "HAWKI_UTIL_DISTMAP_RAW"\n"
"raw-file_chip2.txt "HAWKI_UTIL_DISTMAP_RAW"\n"
"raw-file_chip3.txt "HAWKI_UTIL_DISTMAP_RAW"\n"
"raw-file_chip4.txt "HAWKI_UTIL_DISTMAP_RAW"\n" ;

/*-----------------------------------------------------------------------------
                                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_gendist",
                    "Distortion map creation",
                    hawki_util_gendist_description,
                    "Yves Jung",
                    PACKAGE_BUGREPORT,  
                    hawki_get_license(),
                    hawki_util_gendist_create,
                    hawki_util_gendist_exec,
                    hawki_util_gendist_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_gendist_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 */
    /* --dim_x */
    p = cpl_parameter_new_value("hawki.hawki_util_gendist.dim_x",
            CPL_TYPE_INT, "Dimension of distortion image in X",
            "hawki.hawki_util_gendist", HAWKI_DET_NPIX_X);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "dim_x");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --dim_y */
    p = cpl_parameter_new_value("hawki.hawki_util_gendist.dim_y",
            CPL_TYPE_INT, "Dimension of distortion image in Y",
            "hawki.hawki_util_gendist", HAWKI_DET_NPIX_Y);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "dim_y");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);


    /* 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_gendist_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_gendist(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_gendist_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    framelist   the frames list
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int hawki_util_gendist
(cpl_parameterlist   *   parlist, 
 cpl_frameset        *   framelist)
{
    cpl_frameset         * rawframes;
    const cpl_frame      * cur_frame;
    const char           * dist_name;
    cpl_table           ** distortion_table;
    hawki_distortion    ** distortion_im;
    cpl_propertylist     * plist;
    cpl_propertylist     * plist_ext;
    char                   sval[64];
    const char           * recipe_name = "hawki_util_gendist";
    int                    iext;
    int                    ichip;
    cpl_parameter        * par;
    int                    dim_x;
    int                    dim_y;
    
    /* 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 raw frames */
    if ((rawframes = hawki_extract_frameset(framelist,
                    HAWKI_UTIL_DISTMAP_RAW)) == NULL) {
        cpl_msg_error(__func__, "Cannot find raw frames in the input list") ;
        return -1 ;
    }
    
    /* There should be HAWKI_NB_DETECTORS input frames ordered  */
    if (cpl_frameset_get_size(rawframes) != HAWKI_NB_DETECTORS) {
        cpl_msg_error(__func__, "%d frames expected", HAWKI_NB_DETECTORS) ;
        cpl_frameset_delete(rawframes) ;
        return -1 ;
    }

    /* Retrieve parameters */
    par = cpl_parameterlist_find(parlist, "hawki.hawki_util_gendist.dim_x") ;
    dim_x = cpl_parameter_get_int(par);
    par = cpl_parameterlist_find(parlist, "hawki.hawki_util_gendist.dim_y") ;
    dim_y = cpl_parameter_get_int(par);

    /* Allocate holder for the tables */
    distortion_table = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_table*)) ;

    /* Loop on the chips */
    for (ichip=0 ; ichip<HAWKI_NB_DETECTORS ; ichip++) {

        /* Get the file name */
        cur_frame = cpl_frameset_get_position_const(rawframes, ichip) ;
        dist_name = cpl_frame_get_filename(cur_frame) ;

        /* Create the output table */
        cpl_msg_info(__func__, "Create the output table for chip %d", ichip+1) ;
        if ((distortion_table[ichip] = hawki_util_gendist_convert(dist_name)) == NULL) {
            int j;
            cpl_msg_error(__func__, "Cannot create the output table") ;
            cpl_frameset_delete(rawframes) ;
            for (j=0 ; j<ichip ; j++) cpl_table_delete(distortion_table[j]) ;
            cpl_free(distortion_table) ;
            return -1 ;
        }
    }

    /* Write the table */
    plist = cpl_propertylist_new() ;
    cpl_propertylist_append_string(plist, "INSTRUME", "HAWKI") ;
    cpl_propertylist_append_string(plist, CPL_DFS_PRO_TYPE,
                                   HAWKI_PROTYPE_DISTORTION) ;
    cpl_propertylist_append_string(plist, CPL_DFS_PRO_CATG,
                                   HAWKI_CALPRO_DISTORTION) ;

    plist_ext = cpl_propertylist_new() ;
    cpl_propertylist_prepend_string(plist_ext, "EXTNAME", "CHIP1.INT1") ;
    if (cpl_dfs_save_table(framelist, NULL, parlist, rawframes, NULL, 
                           distortion_table[0], plist_ext, recipe_name,
                           plist, NULL, PACKAGE "/" PACKAGE_VERSION,
                "hawki_util_gendist.fits") != CPL_ERROR_NONE) {
        cpl_msg_error(__func__, "Cannot save the first extension table") ;
        cpl_propertylist_delete(plist_ext) ;
        cpl_propertylist_delete(plist) ;
        for (iext=0 ; iext<HAWKI_NB_DETECTORS ; iext++) 
            cpl_table_delete(distortion_table[iext]) ;
        cpl_free(distortion_table) ;
        cpl_frameset_delete(rawframes) ;
        return -1 ;
    }
    cpl_propertylist_delete(plist) ;
    cpl_propertylist_delete(plist_ext) ;

    /* Save the extensions */
    for (iext=1 ; iext<HAWKI_NB_DETECTORS; iext++) 
    {
        ichip = iext;
        //This is the actual layout of the chips in raw HAWK-I images.
        if(iext == 2)
            ichip = 3;
        if(iext == 3)
            ichip = 2;
        plist_ext = cpl_propertylist_new() ;
        sprintf(sval, "CHIP%d.INT1", ichip+1) ;
        cpl_propertylist_prepend_string(plist_ext, "EXTNAME", sval) ;
        cpl_table_save(distortion_table[ichip], NULL, plist_ext, 
                       "hawki_util_gendist.fits", CPL_IO_EXTEND);
        cpl_propertylist_delete(plist_ext) ;
    }

    /* Allocate holder for the distortion images */
    distortion_im = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(hawki_distortion*));

    /* Loop on the chips */
    for (ichip=0 ; ichip<HAWKI_NB_DETECTORS ; ichip++) 
    {
        /* Get the file name */
        cur_frame = cpl_frameset_get_position_const(rawframes, ichip);
        dist_name = cpl_frame_get_filename(cur_frame) ;

        /* Create the output image */
        cpl_msg_info(__func__, "Create the output images for extension %d", ichip+1) ;
        if ((distortion_im[ichip] = hawki_util_gendist_convert_to_images
                (distortion_table[ichip], dim_x, dim_y)) == NULL) 
        {
            int j;
            cpl_msg_error(__func__,"Cannot create the output distortion images");
            cpl_frameset_delete(rawframes);
            for (j=0 ;j < ichip; j++)
                hawki_distortion_delete(distortion_im[j]);
            cpl_free(distortion_im) ;
            return -1 ;
        }
    }

    /* Write the distortion images */
    plist = cpl_propertylist_new() ;
    cpl_propertylist_append_string(plist, "INSTRUME", "HAWKI");
    //This two keywords are needed by QC. I do not why..
    cpl_propertylist_append_double(plist, "MJD-OBS", 55128.5000000);
    cpl_propertylist_append_string(plist, "FOR_QC", "dummy.fits");
    cpl_propertylist_append_string(plist, "ORIGFILE", 
                                   "hawki_util_gendist_distx.fits") ;
    hawki_main_header_save(framelist, parlist, rawframes,
                           "hawki_util_gendist",
                           HAWKI_CALPRO_DISTORTION_X,
                           HAWKI_PROTYPE_DISTORTION_X,
                           plist, "hawki_util_gendist_distx.fits");
    cpl_propertylist_erase(plist, "ORIGFILE");
    cpl_propertylist_append_string(plist, "ORIGFILE", 
                                   "hawki_util_gendist_distx.fits") ;
    hawki_main_header_save(framelist, parlist, rawframes,
                           "hawki_util_gendist",
                           HAWKI_CALPRO_DISTORTION_Y,
                           HAWKI_PROTYPE_DISTORTION_Y,
                           plist, "hawki_util_gendist_disty.fits");
    cpl_propertylist_delete(plist) ;

    /* Save the extensions */
    //There is kind of a hack here
    //We use the distortion table as a reference to save the distortion images
    //to have a proper layout of the detectors in the FITS file.
    //For that hawki_get_extref_file has been modified.
    for (iext=0 ; iext<HAWKI_NB_DETECTORS; iext++) {
        ichip = iext;
        //This is the actual layout of the chips in raw HAWK-I images.
        if(iext == 2)
            ichip = 3;
        if(iext == 3)
            ichip = 2;
        plist_ext = cpl_propertylist_new() ;
        cpl_propertylist_append_double(plist_ext, "CRVAL1", 
                                       distortion_im[ichip]->x_crval);
        cpl_propertylist_append_double(plist_ext, "CRVAL2",
                                       distortion_im[ichip]->y_crval);
        cpl_propertylist_append_double(plist_ext, "CDELT1", 
                                       distortion_im[ichip]->x_cdelt);
        cpl_propertylist_append_double(plist_ext, "CDELT2",
                                       distortion_im[ichip]->y_cdelt);
        cpl_propertylist_append_double(plist_ext, "CRPIX1", 1);
        cpl_propertylist_append_double(plist_ext, "CRPIX2", 1);
        cpl_propertylist_append_string(plist_ext, "CTYPE1", "");
        cpl_propertylist_append_string(plist_ext, "CTYPE2", "");
        cpl_propertylist_append_string(plist_ext, "CUNIT1", "");
        cpl_propertylist_append_string(plist_ext, "CUNIT2", "");
        hawki_image_ext_save(framelist, distortion_im[ichip]->dist_x, iext + 1,
                             plist_ext, "hawki_util_gendist_distx.fits");
        hawki_image_ext_save(framelist, distortion_im[ichip]->dist_y, iext + 1, 
                             plist_ext, "hawki_util_gendist_disty.fits");
        cpl_propertylist_delete(plist_ext);
    }

    for (iext=0 ; iext<HAWKI_NB_DETECTORS ; iext++) 
        cpl_table_delete(distortion_table[iext]);
    cpl_free(distortion_table) ;
   
    for (iext=0 ; iext<HAWKI_NB_DETECTORS ; iext++)
        hawki_distortion_delete(distortion_im[iext]);
    cpl_free(distortion_im) ;
    cpl_frameset_delete(rawframes) ;

    
    /* Return */
    if (cpl_error_get_code())
    {
        cpl_msg_error(__func__,
                      "HAWK-I pipeline could not recover from previous errors");
        return -1 ;
    }
    else return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Convert the ASCII catalog to the table
  @param    filename    the ascii file
  @return   the newly allocated table or NULL in error case

  The returned table must be deallocated with cpl_table_delete().

  The input catalogs are ASCII files respecting this format:

    DXGC DYGC I J

    DXGC: x geometric correction to apply
    DYGC: y geometric correction to apply
    I: x position in pixel in the chip
    J: y position in pixel in the chip

  Example:
0.68507    -0.37879     0     0
1.49079    -0.60040   256     0
1.43017    -0.01965   512     0
1.30543     0.55538   768     0
1.15870     1.18921  1024     0
1.04449     1.91096  1280     0
 */
/*----------------------------------------------------------------------------*/
static cpl_table * hawki_util_gendist_convert(const char * filename) 
{
    cpl_table   *   out ;
    int             nbentries ;
    FILE        *   in ;
    double          dxgc, dygc ;
    int             i, j ;
    char            line[1024];
    
    /* Check entries */
    if (filename == NULL) return NULL ;

    /* Get the number of lines */
    nbentries = 0 ;
    if ((in = fopen(filename, "r")) == NULL) {
        return NULL ;
    }
    while (fgets(line, 1024, in) != NULL) {
        if (line[0] != '#') nbentries ++ ;
    }
    fclose(in) ;
    
   /* Create the table */
    out = cpl_table_new(nbentries);
    cpl_table_new_column(out, HAWKI_COL_DIST_DXGC, CPL_TYPE_DOUBLE);
    cpl_table_set_column_unit(out, HAWKI_COL_DIST_DXGC, "pixels");
    cpl_table_new_column(out, HAWKI_COL_DIST_DYGC, CPL_TYPE_DOUBLE);
    cpl_table_set_column_unit(out, HAWKI_COL_DIST_DYGC, "pixels");
    cpl_table_new_column(out, HAWKI_COL_DIST_I, CPL_TYPE_INT);
    cpl_table_set_column_unit(out, HAWKI_COL_DIST_I, "pixels");
    cpl_table_new_column(out, HAWKI_COL_DIST_J, CPL_TYPE_INT);
    cpl_table_set_column_unit(out, HAWKI_COL_DIST_J, "pixels");

    /* Parse the file */
    if ((in = fopen(filename, "r")) == NULL) {
        cpl_table_delete(out) ;
        return NULL ;
    }
    nbentries = 0 ;
    while (fgets(line, 1024, in) != NULL) {
        if (line[0] != '#') {
            if (sscanf(line, "%lg %lg %d %d", 
                        &dxgc, &dygc, &i, &j) != 4) {
                cpl_msg_error(__func__, "Bad line %d", nbentries+1) ;
                cpl_table_delete(out) ;
                fclose(in) ;
                return NULL ;
            }
            cpl_table_set_double(out, HAWKI_COL_DIST_DXGC, nbentries, dxgc);
            cpl_table_set_double(out, HAWKI_COL_DIST_DYGC, nbentries, dygc);
            cpl_table_set_int(out, HAWKI_COL_DIST_I, nbentries, i);
            cpl_table_set_int(out, HAWKI_COL_DIST_J, nbentries, j);
            nbentries ++ ;
        }
    }
    fclose(in) ;
                
    return out ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Convert the ASCII catalog to a pair of images
  @param    dist_tab    the distortion file
  @return   the newly allocated distortion or NULL in error case

  The returned struct must be deallocated with hawki_distortion_delete().

  The input tables are FITS files with this columns:

    DXGC DYGC I J

    DXGC: x geometric correction to apply
    DYGC: y geometric correction to apply
    I: x position in pixel in the chip
    J: y position in pixel in the chip

 */
/*----------------------------------------------------------------------------*/
static hawki_distortion * hawki_util_gendist_convert_to_images
(const cpl_table * dist_tab,
 int               detector_nx,
 int               detector_ny) 
{
    hawki_distortion * out ;
    int                nbentries ;
    int                ngrid;
    const int        * i_ptr;
    const int        * j_ptr;
    cpl_array        * i_vec;
    cpl_array        * j_vec;
    int                irow;
    
    
    /* Check entries */
    if (dist_tab == NULL) return NULL ;

    /* Create the table */
    nbentries = cpl_table_get_nrow(dist_tab);
    ngrid = sqrt(nbentries);
    if(ngrid * ngrid != nbentries)
    {
        cpl_msg_error(__func__,"Only square grids are supported");
        return NULL;
    }
    out = hawki_distortion_grid_new(detector_nx, detector_ny, ngrid);
    
    /* Get the crval, cdelt */
    i_ptr = cpl_table_get_data_int_const(dist_tab, HAWKI_COL_DIST_I);
    i_vec = cpl_array_wrap_int((int *)i_ptr, nbentries);
    out->x_crval = cpl_array_get_min(i_vec);
    out->x_cdelt = (cpl_array_get_max(i_vec) - cpl_array_get_min(i_vec)) /
                   (ngrid - 1);
    cpl_array_unwrap(i_vec);
    j_ptr = cpl_table_get_data_int_const(dist_tab, HAWKI_COL_DIST_J);
    j_vec = cpl_array_wrap_int((int *)j_ptr, nbentries);
    out->y_crval = cpl_array_get_min(j_vec);
    out->y_cdelt = (cpl_array_get_max(j_vec) - cpl_array_get_min(j_vec)) /
                   (ngrid - 1);
    cpl_array_unwrap(j_vec);
    

    /* Fill the image */
    for(irow = 0; irow < nbentries; ++irow)
    {
        double i_ima;
        double j_ima;
        double dx;
        double dy;
        int    null;
        
        i_ima = (cpl_table_get_int(dist_tab, HAWKI_COL_DIST_I, irow, &null) -
                 out->x_crval) / out->x_cdelt;
        if(floor(i_ima) != i_ima)
        {
            cpl_msg_error(__func__, " The distortion tables must be defined "
                          "in a regular grid");
            hawki_distortion_delete(out);
            return NULL;
        }
        j_ima = (cpl_table_get_int(dist_tab, HAWKI_COL_DIST_J, irow, &null) -
                 out->y_crval) / out->y_cdelt;
        if(floor(j_ima) != j_ima)
        {
            cpl_msg_error(__func__, " The distortion tables must be defined "
                          "in a regular grid");
            hawki_distortion_delete(out);
            return NULL;
        }
        dx = cpl_table_get_double(dist_tab, HAWKI_COL_DIST_DXGC, irow, &null); 
        dy = cpl_table_get_double(dist_tab, HAWKI_COL_DIST_DYGC, irow, &null);
        
        cpl_image_set(out->dist_x, (int)i_ima + 1, (int)j_ima + 1, dx);
        cpl_image_set(out->dist_y, (int)i_ima + 1, (int)j_ima + 1, dy);
    }
    
    return out ;
}
