/*
 * This file is part of the ESO SINFONI Pipeline
 * Copyright (C) 2004,2005 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, 51 Franklin St, Fifth Floor, Boston, MA  02111-1307  USA
 */
/*----------------------------------------------------------------------------

File name    :       sinfo_new_cubes_coadd.c
Author       :    J. Schreiber
Created on   :    December 3, 2003
Description  :    Creates data cubes or merges data cubes
out of jittered object-sky
nodding observations
---------------------------------------------------------------------------*/
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

/*----------------------------------------------------------------------------
  Includes
  ---------------------------------------------------------------------------*/
#include "sinfo_new_cubes_coadd.h"
#include "sinfo_pfits.h"
#include "sinfo_baryvel.h"
#include "sinfo_pro_save.h"
#include "sinfo_objnod_ini_by_cpl.h"
#include "sinfo_functions.h"
#include "sinfo_utilities_scired.h"
#include "sinfo_wave_calibration.h"
#include "sinfo_cube_construct.h"
#include "sinfo_error.h"
#include "sinfo_utils_wrappers.h"
//Used only for sinfo_propertylist_has
#include "sinfo_dfs.h"
/*----------------------------------------------------------------------------
  Defines
  ---------------------------------------------------------------------------*/
#define PI_NUMB        (3.1415926535897932384626433832795) /* pi */


/*----------------------------------------------------------------------------
  Function Definitions
  ---------------------------------------------------------------------------*/

/**@{*/
/**
 * @addtogroup sinfo_rec_jitter Cube construction
 *
 * TBD
 */


/*----------------------------------------------------------------------------
  Function     : sinfo_new_cubes_coadd()
  In           : ini_file: file name of according .ini file
  Out          : integer (0 if it worked, -1 if it doesn't)
  Job          : this routine carries through the data cube creation of an
  object science observation using object-sky nodding
  and jittering. This script expects jittered frames that
  were already sky-subtracted
  averaged, flatfielded, spectral tilt corrected and
  interleaved if necessary
  ---------------------------------------------------------------------------*/
int
sinfo_new_cubes_coadd (const char* plugin_id,cpl_parameterlist* config,
                       cpl_frameset* sof, const char* procatg)
{

   object_config * cfg=NULL ;
   cpl_image * im=NULL ;
   cpl_image* jitter_image=NULL;
   cpl_imagelist  ** cube_tmp=NULL ;

   cpl_imagelist  ** cubeobject=NULL ;
   cpl_imagelist  * jittercube=NULL ;
   cpl_imagelist  * maskcube=NULL ;
   cpl_propertylist* plist=NULL;
   int sky_cor=0;
   int pdensity=0;

   int i=0;
   int n=0 ;
   int partind = 0 ;
   int centralpix=0 ;
   int z_siz=0;
   int z_min=0;
   int z_max=0;
   int z=0;
   int z_stp=100;
   int scales_sky=0;
   int ks_clip=0;
   double kappa=2.0;

   float ref_offx=0;
   float ref_offy=0;
   float newcenter_x=0 ;
   float newcenter_y=0 ;
   float cd1_1=0;
   float cd1_2=0;
   float cd2_1=0;
   float cd2_2=0;
   double pixelscale=0;
   double angle=0;
   float radangle=0 ;
   double exptime=0 ;

   double * times=NULL ;
   float * offsetx=NULL;
   float * offsety=NULL;
   float offx_min=1.e10;
   float offy_min=1.e10;
   float offx_max=-1.e10;
   float offy_max=-1.e10;

   double dis=0 ;
   double centralLambda=0 ;
   double mjd_obs=0;

   char name_jitter[MAX_NAME_SIZE] ;
   char pro_mjit[MAX_NAME_SIZE];
   char pro_obs[MAX_NAME_SIZE];
   char pro_med[MAX_NAME_SIZE];


   char * name=NULL ;
   char * partname=NULL;
   char * partname2=NULL ;
   char file_name[MAX_NAME_SIZE];
   int vllx=0;
   int vlly=0;
   int vurx=0;
   int vury=0;

   int onp=0;
   int j=0;
   cpl_image* j_img=NULL;
   cpl_image* m_img=NULL;
   cpl_table* qclog_tbl=NULL;
   cpl_image* ill_cor=NULL;
   cpl_frame* frame=NULL;
   cpl_frameset* stk=NULL;
   cpl_parameter* p=NULL;
   int mosaic_max_size=0;
   double barycor=0;
   double helicor=0;
   cpl_table* qclog=NULL;

   check_nomsg(p=cpl_parameterlist_find(config,
                                        "sinfoni.objnod.mosaic_max_size"));
   check_nomsg(mosaic_max_size=cpl_parameter_get_int(p));
   check_nomsg(p=cpl_parameterlist_find(config,"sinfoni.product.density"));
   check_nomsg(pdensity=cpl_parameter_get_int(p));

   if (strcmp(procatg,PRO_COADD_STD) == 0) {
      strcpy(pro_mjit,PRO_MASK_COADD_STD);
      strcpy(pro_obs,PRO_OBS_STD);
      strcpy(pro_med,PRO_MED_COADD_STD);

   } else if (strcmp(procatg,PRO_COADD_PSF) == 0) {
      strcpy(pro_mjit,PRO_MASK_COADD_PSF);
      strcpy(pro_obs,PRO_OBS_PSF);
      strcpy(pro_med,PRO_MED_COADD_PSF);
   } else {
      strcpy(pro_mjit,PRO_MASK_COADD_OBJ);
      strcpy(pro_obs,PRO_OBS_OBJ);
      strcpy(pro_med,PRO_MED_COADD_OBJ);
   }

   /*----parse input data and parameters to set cube_config cfg---*/
   check_nomsg(stk = cpl_frameset_new());

   cknull(cfg = sinfo_parse_cpl_input_objnod(config,sof,&stk),
          "Error setting parameter configuration");

   ck0(sinfo_check_input_data(cfg),"error checking input");

   if ( cfg->jitterind == 1 )
   {
      cknull(times = (double*) cpl_calloc (cfg->nframes, sizeof (double)),
             " could not allocate memory!") ;

      cknull(offsetx = (float*) cpl_calloc (cfg->nframes, sizeof(float)),
             " could not allocate memory!") ;

      cknull(offsety = (float*) cpl_calloc (cfg->nframes, sizeof(float)),
             " could not allocate memory!") ;
   }

   if (cfg->jitterind == 0)
   {
      if ( NULL != (partname = strtok(cfg->outName, ".")))
      {
         partname2 = strtok (NULL, ".") ;
         partind = 1 ;
      }
   }

   ck0(sinfo_auto_size_cube5(cfg,&ref_offx,&ref_offy,
                             &offx_min,&offy_min,
                             &offx_max,&offy_max),
       "Error resizing cube");

   if(NULL != cpl_frameset_find(sof,PRO_ILL_COR)) {
      frame = cpl_frameset_find(sof,PRO_ILL_COR);
      ill_cor=cpl_image_load(cpl_frame_get_filename(frame),CPL_TYPE_FLOAT,0,0);
   } else {
      sinfo_msg("Illumination correction image not provided");
      cpl_error_reset();
   }

   for ( n = 0 ; n < cfg->nframes ; n++ )
   {

      sinfo_msg_debug("Read FITS information");
      name = cfg->framelist[n] ;
      if (n == 0)
      {
         strcpy (name_jitter, name) ;
      }
      if( sinfo_is_fits_file(name) != 1) {
         sinfo_msg_error("Input file %s is not FITS",name);
         goto cleanup;
      }


      /* get some header values and compute the CD-sinfo_matrix */
      plist=cpl_propertylist_load(name,0);

      if (sinfo_propertylist_has(plist, KEY_NAME_MJD_OBS)) {
         mjd_obs=cpl_propertylist_get_double(plist, KEY_NAME_MJD_OBS);
      } else {
         sinfo_msg_error("keyword %s does not exist",KEY_NAME_MJD_OBS);
         cpl_propertylist_delete(plist) ;
         return -1;
      }


      pixelscale = sinfo_pfits_get_pixscale(plist) /2;
      angle = sinfo_pfits_get_posangle(plist) ;
      /* in PUPIL data there is not posangle info: we reset the error */
      if(cpl_error_get_code() != CPL_ERROR_NONE) {
         cpl_error_reset();
      }

      sinfo_free_propertylist(&plist);
      // The following 5 lines are not really used here!
      radangle = angle * PI_NUMB / 180. ;
      cd1_1 = cos(radangle) ;
      cd1_2 = sin(radangle) ;
      cd2_1 = -sin(radangle) ;
      cd2_2 = cos(radangle) ;

      sinfo_msg_debug("frame no.: %d, name: %s\n", n, name) ;
      cknull(im = cpl_image_load(name,CPL_TYPE_FLOAT,0,0),
             " could not load frame %s!",name) ;

      if (cfg->jitterind == 1)
      {
         exptime = sinfo_pfits_get_ditndit(name) ;

         if (exptime == FLAG)
         {
            sinfo_msg_error("could not read fits header keyword exptime!");
            goto cleanup;
         }
         times[n] = exptime ;

         ck0(sinfo_new_assign_offset(n,name,offsetx,offsety,
                                     ref_offx,ref_offy),
             "Error assigning offsets");

      }
      sinfo_free_image(&im);

   } /* end loop over n (nframes) */


   /* leak free */
   if(cfg->jitterind == 0) {
      goto exit;
   }

   /* Here in case of autojitter we estimate the sky */
   if( (cfg->size_x*cfg->size_y*cfg->nframes) > (100*mosaic_max_size) ) {
      sinfo_msg_warning("Coadd cube size:%d,%d. N frames: %d",
                        cfg->size_x,cfg->size_y,cfg->nframes);
      sinfo_msg_warning("Max allowed should be such "
                        "that sixeX*sixeY*Nframes < 100*%d",mosaic_max_size);
      goto exit;
   }

   if ( cfg->jitterind == 1 )
   {
      check_nomsg(p = cpl_parameterlist_find(config, "sinfoni.objnod.vllx"));
      check_nomsg(vllx = cpl_parameter_get_int(p));
      check_nomsg(p = cpl_parameterlist_find(config, "sinfoni.objnod.vlly"));
      check_nomsg(vlly = cpl_parameter_get_int(p));
      check_nomsg(p = cpl_parameterlist_find(config, "sinfoni.objnod.vurx"));
      check_nomsg(vurx = cpl_parameter_get_int(p));
      check_nomsg(p = cpl_parameterlist_find(config, "sinfoni.objnod.vury"));
      check_nomsg(vury = cpl_parameter_get_int(p));
      cknull(cube_tmp = (cpl_imagelist**) cpl_calloc (cfg->nframes,
                                                      sizeof (cpl_imagelist*)),
             "Could not allocate memory for cube_tmp");


      cknull(cubeobject = (cpl_imagelist**) cpl_calloc (cfg->nframes,
                                                        sizeof (cpl_imagelist*)),
             "Could not allocate memory for cubeobject");
      check_nomsg(p=cpl_parameterlist_find(config,
                                           "sinfoni.objnod.sky_cor"));
      check_nomsg(sky_cor=cpl_parameter_get_bool(p));


      for ( n = 0 ; n < cfg->nframes ; n++ ) {
         if(sky_cor == 1 && (pdensity == 1 || pdensity == 3) &&
            strcmp(cfg->sky_dist,"no_sky")!=0) {
            sinfo_msg("load sky corrected cubes");

            snprintf(file_name,MAX_NAME_SIZE-1,"%s%2.2d%s","out_cube_obj_cor",
                     n,".fits");

         } else {
            snprintf(file_name,MAX_NAME_SIZE-1,"%s%2.2d%s","out_cube_obj",n,
                     ".fits");
         }
         check_nomsg(cube_tmp[n] = cpl_imagelist_load(file_name,
                                                      CPL_TYPE_FLOAT,0));


       	 check_nomsg(cubeobject[n] = sinfo_new_cube_getvig(
							 cube_tmp[n],
                             1+vllx,1+vlly,
                             64 - vurx, 64 - vury));
       	 check_nomsg(sinfo_free_imagelist(&cube_tmp[n]));

      }
      sinfo_free_array_imagelist(&cube_tmp);


   }


   /*
     ---------------------------------------------------------------------
     ------------------------JITTERING------------------------------------
     ---------------------------------------------------------------------
   */

   if (cfg->jitterind == 1)
   {
      sinfo_msg("Jittering...");

      sinfo_msg("Coadded cube size. x: %d y: %d",
                cfg->size_x,cfg->size_y);
      check_nomsg(jittercube = cpl_imagelist_new()) ;


      /*
         ---------------------------------------------------------------------
         -------------------THOMAS ALGORITHM----------------------------------
         ---------------------------------------------------------------------
      */

      check_nomsg(p=cpl_parameterlist_find(config,
                                           "sinfoni.objnod.scales_sky"));
      check_nomsg(scales_sky=cpl_parameter_get_bool(p));
      check_nomsg(p=cpl_parameterlist_find(config,"sinfoni.objnod.ks_clip"));
      check_nomsg(ks_clip = cpl_parameter_get_bool(p));
      check_nomsg(p=cpl_parameterlist_find(config,"sinfoni.objnod.kappa"));
      check_nomsg(kappa = cpl_parameter_get_double(p));


      if(scales_sky == 1) {
         sinfo_msg("Subtract spatial sinfo_median to each cube plane");
         for(n=0;n<cfg->nframes;n++) {
            sinfo_msg("Process cube %d\n",n);
            sinfo_new_sinfoni_correct_median_it(&(cubeobject[n]));
         }
      }


      /* AMO CHECK */

      cknull(maskcube=cpl_imagelist_new(),"could not allocate cube!");

      /* Illumination correction */
      if(ill_cor != NULL) {
         for(n=0;n<cfg->nframes;n++) {
            cpl_imagelist_divide_image(cubeobject[n],ill_cor);
         }
      }
      sinfo_free_image(&ill_cor);

      sinfo_msg("Combine jittered cubes");


      if(ks_clip == 1) {
         sinfo_msg("Cube coaddition with kappa-sigma");
      }
      check_nomsg(onp=cpl_imagelist_get_size(cubeobject[0]));
      for(z=0;z<onp;z+=z_stp) {
         z_siz=(z_stp < (onp-z)) ? z_stp : (onp-z);
         z_min=z;
         z_max=z_min+z_siz;
         sinfo_msg("Coadding cubes: range [%4.4d,%4.4d) of 0-%d\n",
                   z_min,z_max,onp);

         for(j=z_min;j<z_max;j++) {
            check_nomsg(j_img=cpl_image_new(cfg->size_x,
                                            cfg->size_y,CPL_TYPE_FLOAT));
            check_nomsg(cpl_imagelist_set(jittercube,j_img,j));
            check_nomsg(m_img = cpl_image_new(cfg->size_x,
                                              cfg->size_y,CPL_TYPE_FLOAT));
            check_nomsg(cpl_imagelist_set(maskcube,m_img,j));
         }
         if(ks_clip == 1) {
            sinfo_new_combine_jittered_cubes_thomas_range(cubeobject,
                                                          jittercube,
                                                          maskcube,
                                                          cfg->nframes,
                                                          offsetx,offsety,
                                                          times,
                                                          cfg->kernel_type,
                                                          z_min,
                                                          z_max,
                                                          kappa);

         } else {
            sinfo_new_combine_jittered_cubes_range(cubeobject,
                                                   jittercube,
                                                   maskcube,
                                                   cfg->nframes,
                                                   offsetx,
                                                   offsety,
                                                   times,
                                                   cfg->kernel_type,
                                                   z_min,
                                                   z_max) ;
         }
      }
      sinfo_new_convert_0_to_ZERO_for_cubes(jittercube) ;

      if (jittercube == NULL)
      {
         sinfo_msg_error(" could not allocate new data cube!") ;
         goto cleanup;
      }

      if (maskcube == NULL)
      {
         sinfo_msg_error(" could not merge the jittered data cubes\n") ;
         goto cleanup;
      }

      for ( i = 0 ; i <cfg->nframes  ; i++ ) {
         sinfo_free_imagelist(&cubeobject[i]);
         //sinfo_msg("offx[%d]=%f,offy[%d]=%f",i,offsetx[i],i,offsety[i]);
      }
      sinfo_free_array_imagelist(&cubeobject);

      //Old setting
      //newcenter_x = cfg->size_x / 2. + 0.5 ;
      //newcenter_y = cfg->size_y / 2. + 0.5 ;


      /* new setting */
      /*
        sinfo_msg("offx_min=%f,offy_min=%f",offx_min,offy_min);
        sinfo_msg("offx_max=%f,offy_max=%f",offx_max,offy_max);
        sinfo_msg("ref_offx=%f,ref_offy=%f",ref_offx,ref_offy);

        newcenter_x=2*floor(fabs(offx_min)+0.5)+32+8;
        newcenter_y=-2*floor(fabs(offy_max)+0.5)+32-2;


        newcenter_x=cfg->size_x / 2. -offsetx[0];
        newcenter_y=cfg->size_y / 2. -offsety[0];
      */



      if (mjd_obs > 53825. ) {
         /* April 1st 2006 */
         newcenter_x=cfg->size_x / 2. +2*ref_offx;
         newcenter_y=cfg->size_y / 2. +2*ref_offy;
      } else if ((mjd_obs > 53421.58210082 ) && (mjd_obs <= 53825.)){
         /* after detector's upgrade */
         newcenter_x=cfg->size_x / 2. -2*ref_offx;
         newcenter_y=cfg->size_y / 2. +2*ref_offy;
      } else {
         /* before detector's upgrade */
         newcenter_x=cfg->size_x / 2. +2*ref_offx;
         newcenter_y=cfg->size_y / 2. -2*ref_offy;
      }


      //sinfo_msg("CRPIX=%f,%f", newcenter_x, newcenter_y);
      //newcenter_x=2*floor(fabs(ref_offx)+0.5);
      //newcenter_y=-2*floor(fabs(ref_offy)+0.5);
      //sinfo_msg_warning("Center image: %f %f",newcenter_x,newcenter_y);
      cknull_nomsg(qclog = sinfo_qclog_init());
      plist=cpl_propertylist_load(file_name,0);
      if(sinfo_baryvel(plist, &barycor,&helicor) != CPL_ERROR_NONE) {
         sinfo_msg_warning("Could not compute velocity corrections");
         cpl_error_reset();
      } else {

         check_nomsg(sinfo_qclog_add_double(qclog,
                                            "QC VRAD BARYCOR",
                                            barycor,
                                            "Barycentric radial velocity correction ",
                                            "%13.6f"));

         check_nomsg(sinfo_qclog_add_double(qclog,
                                            "QC VRAD HELICOR",
                                            helicor,
                                            "Heliocentric radial velocity correction ",
                                            "%13.6f"));
         sinfo_msg("Barycor=%g Helicor=%g",barycor,helicor);

         sinfo_free_propertylist(&plist);

      }

      ck0(sinfo_pro_save_ims(jittercube,sof,sof,cfg->outName,
                             procatg,qclog,plugin_id,config),
          "cannot save cube %s", cfg->outName);

      sinfo_free_table(&qclog);

      /* we need to set again the following 3 */
      plist=cpl_propertylist_load(file_name,0);
      dis=sinfo_pfits_get_cdelt3(plist);
      centralLambda=sinfo_pfits_get_crval3(plist);
      centralpix=sinfo_pfits_get_crpix3(plist);
      sinfo_free_propertylist(&plist);
      sinfo_new_set_wcs_cube(jittercube, cfg->outName,
                             centralLambda, dis,
                             centralpix, newcenter_x, newcenter_y);

      cknull(jitter_image = sinfo_new_median_cube(jittercube),
             " could not do sinfo_medianCube()");
      cknull_nomsg(qclog = sinfo_qclog_init());
      update_bad_pixel_map(jitter_image);
   	  check_nomsg(sinfo_qclog_add_double(qclog,
                                               "QC FRMDIF MEANSTD",
                                               cpl_image_get_mean(jitter_image),
                                               "mean of the collapesd cube",
                                               "%13.6f"));
      check_nomsg(sinfo_qclog_add_double(qclog,
                                               "QC FRMDIF STDEV",
                                               cpl_image_get_stdev(jitter_image),
                                               "standard deviation of the collapesd cube",
                                               "%13.6f"));
      ck0(sinfo_pro_save_ima(jitter_image,sof,sof,cfg->med_cube_name,
                             pro_med,qclog,plugin_id,config),
          "cannot save ima %s", cfg->outName);
      sinfo_free_table(&qclog);

      sinfo_new_set_wcs_image(jitter_image, cfg->med_cube_name,
                              newcenter_x,newcenter_y);

      sinfo_free_image(&jitter_image);

      ck0(sinfo_pro_save_ims(maskcube,sof,sof,cfg->maskname,
                             pro_mjit,NULL,plugin_id,config),
          "cannot save cube %s", cfg->maskname);

      sinfo_new_set_wcs_cube(maskcube, cfg->maskname,
                             centralLambda, dis, centralpix,
                             newcenter_x, newcenter_y);

      sinfo_free_double(&times) ;
      sinfo_free_float(&offsetx) ;
      sinfo_free_float(&offsety) ;
      sinfo_free_imagelist(&maskcube) ;
      sinfo_free_imagelist(&jittercube) ;

   } /* end of jittering */

  exit:

   /* free memory */
   sinfo_objnod_free(&cfg);
   sinfo_free_frameset(&stk);
   return 0;

  cleanup:

   sinfo_free_image(&jitter_image);
   sinfo_free_imagelist(&jittercube) ;
   sinfo_free_imagelist(&maskcube) ;
   sinfo_free_table(&qclog);

   if(cfg != NULL) {
      if(cube_tmp != NULL) {
         for ( n = 0 ; n < cfg->nframes ; n++ ) {
            sinfo_free_imagelist(&(cube_tmp[n]));
         }
         sinfo_free_array_imagelist(&cube_tmp);
      }
      if(cubeobject != NULL) {
         for ( n = 0 ; n < cfg->nframes ; n++ ) {
            sinfo_free_imagelist(&(cubeobject[n]));
         }
         sinfo_free_array_imagelist(&cubeobject);
      }

   }
   sinfo_free_table(&qclog_tbl);
   sinfo_free_image(&im);
   sinfo_free_image(&ill_cor);
   sinfo_free_float(&offsety);
   sinfo_free_float(&offsetx);
   sinfo_free_double(&times);
   sinfo_objnod_free(&cfg);
   sinfo_free_frameset(&stk);

   if (cpl_error_get_code() != CPL_ERROR_NONE) {
      sinfo_check_rec_status(0);
   }
   return -1;



}

/**@}*/
