/*                                                                           *
 *   This file is part of the ESO X-shooter Pipeline                         *
 *   Copyright (C) 2006 European Southern Observatory                        *
 *                                                                           *
 *   This library 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    *
 *                                                                           */

/*
 * $Author: amodigli $
 * $Date: 2012-12-01 20:09:50 $
 * $Revision: 1.11 $
*/


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

/*---------------------------------------------------------------------------*/
/**
 * @defgroup xsh_data_image_3d     Data Cube 3d Handling functions
 * @ingroup data_handling
 */
/*---------------------------------------------------------------------------*/

/**@{*/
#include <fitsio.h>
#include <xsh_dump.h>
#include <xsh_utils.h>
#include <xsh_error.h>
#include <xsh_msg.h>
#include <xsh_dfs.h>
#include <math.h>
#include <time.h>
#include <cpl.h>
#include <xsh_data_image_3d.h>

static void xsh_image_3d_write_fits_header( fitsfile * fptr,
					    cpl_propertylist * header )
{
  int psize = cpl_propertylist_get_size( header ) ;
  int ip ;
  int fio_status = 0 ;

  /* Boucle sur tous les elements de la propertylist initiale */
  for ( ip = 0 ; ip < psize ; ip++ ) {
    const cpl_property * pcur ;	/**< Current property */
    cpl_type type ;
    char * name ;
    char * comment ;

    /* Get the type */
    pcur = cpl_propertylist_get( header, ip ) ;
    type = cpl_property_get_type( pcur ) ;
    name = (char *)cpl_property_get_name( pcur ) ;
    if ( strncmp( name, "NAXIS", 5 ) == 0 || strcmp( name, "SIMPLE" ) == 0 ||
	 strcmp( name, "BSCALE" ) == 0 || strcmp( name, "BZERO" ) == 0 ||
	 strcmp( name, "EXTEND" ) == 0 || strcmp( name, "BITPIX" ) == 0 )
      continue ;
    comment = (char *)cpl_property_get_comment( pcur ) ;

    /* Write to the file */
    switch( type ) {
    case CPL_TYPE_CHAR:
      {
	char s[2] ;
	s[0] = cpl_property_get_char( pcur ) ;
	s[1] = '\0' ;
	fits_write_key_str( fptr, name, s, comment, &fio_status ) ;
	if ( fio_status != 0 ) {
	  xsh_msg( "fits_write_key_str ERROR %d", fio_status ) ;
	}
      }
      break ;
    case CPL_TYPE_BOOL:
      {
	int b = cpl_property_get_bool( pcur ) ;
	int value = b == TRUE ? 1 : 0 ;
	fits_write_key_log( fptr, name, value, comment, &fio_status ) ;
	if ( fio_status != 0 ) {
	  xsh_msg( "fits_write_key Bool ERROR %d", fio_status ) ;
	}
      }
      break ;
    case CPL_TYPE_INT:
      {
	int value = cpl_property_get_int(pcur);

	fits_write_key_lng(fptr, name, value, comment, &fio_status);
	if ( fio_status != 0 ) {
	  xsh_msg( "fits_write_key INT ERROR %d", fio_status ) ;
	}
      }
      break;
    case CPL_TYPE_LONG:
      {
	long value = cpl_property_get_long(pcur);

	fits_write_key_lng(fptr, name, value, comment, &fio_status);
	if ( fio_status != 0 ) {
	  xsh_msg( "fits_write_key LONG ERROR %d", fio_status ) ;
	}
      }
      break;

    case CPL_TYPE_FLOAT:
      {
	float value = cpl_property_get_float(pcur);

	fits_write_key_flt(fptr, name, value, -8, comment, &fio_status);
	if ( fio_status != 0 ) {
	  xsh_msg( "fits_write_key FLOAT ERROR %d", fio_status ) ;
	}
      }
      break;
    case CPL_TYPE_DOUBLE:
      {
	double value = cpl_property_get_double(pcur);

	fits_write_key_dbl(fptr, name, value, -9, comment, &fio_status);
	if ( fio_status != 0 ) {
	  xsh_msg( "fits_write_key DOUBLE ERROR %d", fio_status ) ;
	}
      }
      break;

    case CPL_TYPE_STRING:
      {
	char *value = (char *)cpl_property_get_string(pcur);

	if (strcmp(name, "COMMENT") == 0) {
	  if(strcmp(value, "") == 0){
	    fits_write_comment(fptr, " ", &fio_status);
	    if ( fio_status != 0 ) {
	      xsh_msg( "fits_write_comment ERROR %d", fio_status ) ;
	    }
	  }
	  else{
	    fits_write_comment(fptr, value, &fio_status);
	    if ( fio_status != 0 ) {
	      xsh_msg( "fits_write_comment ERROR %d", fio_status ) ;
	    }
	  }
	}
	else if(strcmp(name, "HISTORY") == 0) {
	  if(strcmp(value, "") == 0){
	    fits_write_history(fptr, " ", &fio_status);
	    if ( fio_status != 0 ) {
	      xsh_msg( "fits_write_history ERROR %d", fio_status ) ;
	    }
	  }
	  else{
	    fits_write_history(fptr, value, &fio_status);
	    if ( fio_status != 0 ) {
	      xsh_msg( "fits_write_history ERROR %d", fio_status ) ;
	    }
	  }
	}
	else {
	  fits_write_key_str(fptr, name, value, comment, &fio_status);
	  if ( fio_status != 0 ) {
	    xsh_msg( "fits_write_key_str ERROR %d", fio_status ) ;
	}
	}
      }
      break;
    default:
      /* Do nothing !!! */
      break ;
    }
    XSH_ASSURE_NOT_ILLEGAL( fio_status == 0 ) ;
  }

 cleanup:
  return ;
}

/** 
 * Create a new data cube. Initialized with zeroes. Actually this (and other
 * functions should be available in CPL, should'nt they ?
 * 
 * @param nx First (X) axis size
 * @param ny Second (Y) axis size
 * @param nz Third (Z) axis size
 * @param type Type of the image
 * 
 * @return The created data cube
 */
xsh_image_3d * xsh_image_3d_new( int nx, int ny, int nz, cpl_type type )
{
  xsh_image_3d * result = NULL ;
  int nelements, elem_size ;

  xsh_msg_dbg_low( "Entering xsh_image_3d_new" ) ;

  XSH_ASSURE_NOT_ILLEGAL( nx > 0 && ny > 0 && nz > 0 ) ;
  XSH_CALLOC( result, xsh_image_3d, 1 ) ;

  nelements = nx * ny * nz ;
  elem_size = cpl_type_get_sizeof( type ) ;
  xsh_msg_dbg_high( "%d elements of size %d [type: %d]",
		    nelements, elem_size, type ) ;

  check( result->pixels = cpl_calloc( nelements, elem_size ) ) ;

  result->nx = nx ;
  result->ny = ny ;
  result->nz = nz ;
  result->type = type ;

 cleanup:
  return result ;
}

/** 
 * Insert a 2D image into a data cube at the proper Z axis place.
 * 
 * @param img_3d The data cube
 * @param img The 2D image to be inserted into the data cube
 * @param iz The Z axis value where the image shall be inserted
 * 
 * @return An error code (CPL_ERROR_NONE if OK)
 */
cpl_error_code xsh_image_3d_insert( xsh_image_3d * img_3d, cpl_image * img,
				    int iz )
{
  int img_size, nx, ny ;
  int img_3d_nx, img_3d_ny, img_3d_nz ;
  cpl_type img_type, img_3d_type ;
  cpl_error_code err = CPL_ERROR_NONE ;
  char * p_pix ;
  int elem_size ;

  XSH_ASSURE_NOT_NULL( img_3d ) ;
  XSH_ASSURE_NOT_NULL( img ) ;

  check( img_3d_nx = xsh_image_3d_get_size_x( img_3d ) ) ;
  check( img_3d_ny = xsh_image_3d_get_size_y( img_3d ) ) ;
  check( img_3d_nz = xsh_image_3d_get_size_z( img_3d ) ) ;
  check( img_3d_type = xsh_image_3d_get_type( img_3d ) ) ;

  XSH_ASSURE_NOT_ILLEGAL( iz >= 0 && iz < img_3d_nz ) ;

  xsh_msg_dbg_high( "  Getting Image type,nx,ny" ) ;
  check( img_type = cpl_image_get_type( img ) ) ;
  check( nx = cpl_image_get_size_x( img ) ) ;
  check( ny = cpl_image_get_size_y( img ) ) ;

  xsh_msg_dbg_high( "  Input Image Size: %d,%d", nx, ny ) ;
  XSH_ASSURE_NOT_ILLEGAL( img_type == img_3d_type ) ;
  XSH_ASSURE_NOT_ILLEGAL( nx == img_3d_nx && ny == img_3d_ny ) ;

  img_size = nx*ny ;
  xsh_msg_dbg_high( "Image Size: %d", img_size ) ;

  /* Calculate position in the pixels aray where to put the image */
  check( p_pix = (char *)xsh_image_3d_get_data( img_3d ) ) ;
  elem_size = cpl_type_get_sizeof( img_type ) ;
  xsh_msg_dbg_high( " Image element size: %d (type: %d)",
		    elem_size, img_type ) ;

  if ( elem_size == 0 ) {
    err = CPL_ERROR_INVALID_TYPE ;
    goto cleanup ;
  }

  p_pix += iz*img_size*elem_size ;
  memcpy( p_pix, cpl_image_get_data( img ), img_size*elem_size ) ;

    cleanup:
  if ( err != CPL_ERROR_NONE ) {
    xsh_msg_error( "Could NOT inster image into a data cube" ) ;
  }
  return err ;
}

xsh_image_3d * xsh_image_3d_load( const char * filename, cpl_type type,
				  int xtnum )
{
  xsh_image_3d * result = NULL ;
  fitsfile    *   fptr ;
  int             fio_status=0 ;
  char        *extname = NULL;
  int             naxis ;
  long        *   naxes=NULL ;
  //cpl_type_bpp bpp_loc ;
  int fits_type ;
  int nbpixels, nullval = 0, anynull ;

  /* First get the size (nx, ny, nz) from fits file */
  XSH_ASSURE_NOT_ILLEGAL( xtnum  >= 0 ) ;
  XSH_ASSURE_NOT_NULL( filename);

  /* Open the file extension */
  if (xtnum == 0) extname = cpl_sprintf( "%s", filename) ;
  else extname = cpl_sprintf( "%s[%d]", filename, xtnum) ;

  ffopen(&fptr, extname, READONLY, &fio_status) ;
  XSH_ASSURE_NOT_ILLEGAL( fio_status == 0);

  /* Get the file dimension */
  fits_get_img_dim(fptr, &naxis, &fio_status) ;
  XSH_ASSURE_NOT_ILLEGAL( naxis == 3);

  /* Get the file size */
  XSH_MALLOC(naxes, long, naxis);

  fits_get_img_size(fptr, naxis, naxes, &fio_status) ;
  XSH_ASSURE_NOT_ILLEGAL( fio_status == 0 ) ;
  xsh_msg_dbg_medium( "Image_3d_load(%s): Naxis: %d,  %ld x %ld x %ld", extname,
	   naxis, naxes[0], naxes[1], naxes[2] ) ;

  /* Create the result image */
  check( result = xsh_image_3d_new( naxes[0], naxes[1], naxes[2], type ) ) ;
  result->nx = naxes[0] ;
  result->ny = naxes[1] ;
  result->nz = naxes[2] ;
  nbpixels = result->nx * result->ny * result->nz ;

  /* Now read the data into the array */
  switch( type ) {
  case CPL_TYPE_INT:
    //bpp_loc = CPL_BPP_32_SIGNED ;
    fits_type = TINT ;
    break ;
  case CPL_TYPE_FLOAT:
    //bpp_loc = CPL_BPP_IEEE_FLOAT ;
    fits_type = TFLOAT ;
    break ;
  case CPL_TYPE_DOUBLE:
    //bpp_loc = CPL_BPP_IEEE_DOUBLE ;
    fits_type = TDOUBLE ;
    break ;
  default:
    //bpp_loc = CPL_BPP_32_SIGNED ;
    fits_type = TINT ;
    break ;
  }

  fits_read_img( fptr, fits_type, 1, nbpixels, &nullval, result->pixels,
		 &anynull, &fio_status ) ;
  XSH_ASSURE_NOT_ILLEGAL( fio_status == 0 ) ;

  fits_close_file( fptr, &fio_status ) ;
  XSH_ASSURE_NOT_ILLEGAL( fio_status == 0 ) ;

 cleanup:
  XSH_FREE( naxes);
  XSH_FREE( extname);
  return result ;
}

/** 
 * Save the data cube image. Note that the output file is automatically
 * rewritten if it exists.
 * 
 * @param img_3d Input Data Cube
 * @param fname Output file name
 * @param header FITS Header
 * @param mode  saving mode (default/extend)
 * 
 * @return Possible error code
 */
cpl_error_code xsh_image_3d_save( xsh_image_3d * img_3d,
				  const char * fname,
				  cpl_propertylist * header,
				  unsigned mode )
{
  cpl_type_bpp bpp_loc ;
  int fits_type ;
  fitsfile * fptr ;
  long naxes[3] ;
  int fio_status = 0 ;
  int size ;

  xsh_msg_dbg_low( "Entering xsh_image_3d_save" ) ;

  XSH_ASSURE_NOT_NULL( img_3d ) ;
  //XSH_ASSURE_NOT_NULL( header ) ;
  XSH_ASSURE_NOT_NULL( fname ) ;

  switch( img_3d->type ) {
  case CPL_TYPE_INT:
    bpp_loc = CPL_BPP_32_SIGNED ;
    fits_type = TINT ;
    break ;
  case CPL_TYPE_FLOAT:
    bpp_loc = CPL_BPP_IEEE_FLOAT ;
    fits_type = TFLOAT ;
    break ;
  case CPL_TYPE_DOUBLE:
    bpp_loc = CPL_BPP_IEEE_DOUBLE ;
    fits_type = TDOUBLE ;
    break ;
  default:
    bpp_loc = CPL_BPP_32_SIGNED ;
    fits_type = TINT ;
    break ;
  }
  naxes[0] = xsh_image_3d_get_size_x( img_3d ) ;
  naxes[1] = xsh_image_3d_get_size_y( img_3d ) ;
  naxes[2] = xsh_image_3d_get_size_z( img_3d ) ;
  size = naxes[0]*naxes[1]*naxes[2] ;

  if ( mode == CPL_IO_DEFAULT ) {
    /* First exension, delete file if it exists */
    xsh_msg_dbg_low( "Writing first extension to file %s", fname ) ;
    if ( access( fname, 0 ) == 0 ) {
      /* File exists, delete it */
      unlink( fname ) ;
    }
    fits_create_file( &fptr, fname, &fio_status ) ;
    xsh_msg_dbg_high( "--> fits_create_file = %d", fio_status ) ;
    XSH_ASSURE_NOT_ILLEGAL( fio_status == 0 ) ;
  }
  else {
    xsh_msg_dbg_low( "Writing extension to file %s", fname ) ;
    ffopen( &fptr, fname, READWRITE, &fio_status ) ;
    xsh_msg_dbg_high( "--> fits_create_file = %d", fio_status ) ;
    XSH_ASSURE_NOT_ILLEGAL( fio_status == 0 ) ;
  }

  fits_create_img( fptr, bpp_loc, 3, naxes, &fio_status);
  xsh_msg_dbg_high( "--> fits_create_img = %d", fio_status ) ;
  XSH_ASSURE_NOT_ILLEGAL( fio_status == 0 ) ;

  fits_write_img( fptr, fits_type, 1, size, img_3d->pixels, &fio_status ) ;
  xsh_msg_dbg_high( "--> fits_write_image = %d", fio_status ) ;
  XSH_ASSURE_NOT_ILLEGAL( fio_status == 0 ) ;

  /* Add the propertylist */
  if ( header != NULL )
    check( xsh_image_3d_write_fits_header( fptr, header ) );

  fits_close_file( fptr, &fio_status ) ;
  xsh_msg_dbg_high( "--> fits_close_file = %d", fio_status ) ;
  XSH_ASSURE_NOT_ILLEGAL( fio_status == 0 ) ;

 cleanup:
  return cpl_error_get_code() ;

}

int xsh_image_3d_get_size_x( xsh_image_3d * img_3d )
{
  int nx = 0 ;

  XSH_ASSURE_NOT_NULL( img_3d ) ;
  nx = img_3d->nx ;

 cleanup:
  return nx ;
}

int xsh_image_3d_get_size_y( xsh_image_3d * img_3d )
{
  int ny = 0 ;

  XSH_ASSURE_NOT_NULL( img_3d ) ;
  ny = img_3d->ny ;

 cleanup:
  return ny ;
}

int xsh_image_3d_get_size_z( xsh_image_3d * img_3d )
{
  int nz = 0 ;

  XSH_ASSURE_NOT_NULL( img_3d ) ;
  nz = img_3d->nz ;

 cleanup:
  return nz ;
}

void * xsh_image_3d_get_data( xsh_image_3d * img_3d )
{
  void * result = NULL ;

  XSH_ASSURE_NOT_NULL( img_3d ) ;
  result = img_3d->pixels ;

 cleanup:
  return result ;
}

cpl_type xsh_image_3d_get_type( xsh_image_3d * img_3d )
{
  cpl_type result = 0 ;

  XSH_ASSURE_NOT_NULL( img_3d ) ;
  result = img_3d->type ;

 cleanup:
  return result ;

}

void xsh_image_3d_free( xsh_image_3d ** img_3d )
{
  if ( img_3d != NULL && *img_3d != NULL ) {
    XSH_FREE( (*img_3d)->pixels);
    XSH_FREE( *img_3d);
    *img_3d = NULL ;
  }
}

/**@}*/
