/*
 * See LICENSE.txt
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "aXe_grism.h"
#include "aXe_utils.h"
#include "spce_PET.h"
#include "inout_aper.h"
#include "trace_conf.h"
#include "aper_conf.h"
#include "spc_sex.h"
#include "disp_conf.h"
#include "spc_wl_calib.h"

#define AXE_IMAGE_PATH "AXE_IMAGE_PATH"
#define AXE_OUTPUT_PATH "AXE_OUTPUT_PATH"
#define AXE_CONFIG_PATH "AXE_CONFIG_PATH"


int
main(int argc, char *argv[])
{

  char           *opt;
  char            grism_image[MAXCHAR];
  char            grism_image_path[MAXCHAR];

  char            conf_file[MAXCHAR];
  char            conf_file_path[MAXCHAR];

  char            aper_file[MAXCHAR];
  char            aper_file_path[MAXCHAR];

  char            PET_file[MAXCHAR];
  char            PET_file_path[MAXCHAR];

  char            hdu_name[MAXCHAR];

  int             i, j, flags, for_grism;

  object        **oblist;
  observation    *obs;

  ap_pixel       *result = NULL;
  d_point         pixel;
  aperture_conf  *conf;

  dispstruct     *disp;
  calib_function *wl_calibration;
  global_disp    *gdisp;

  gsl_vector     *p_range=NULL;

  FITScards      *cards;

  int             f_status = 0;

  int             bckmode = 0;

  fitsfile       *PET_fitsptr;

  int err_num=0, dq_num=0, extver=0;

  double exptime;

  if ((argc < 3) || (opt = get_online_option("help", argc, argv))) {
    fprintf(stdout,
            "aXe_AF2PET Version %s:\n"
            "           aXe task that produce an Object or Background  Pixel Extraction\n"
            "           Table (-bck option) using an input aperture file (AF) and an aXe\n"
            "           config file which defines the geometry involved (a polynomial\n"
            "           describing the position of the spectral trace for each beam\n"
            "           (order), and a polynomial description of the wavelength\n"
            "           dispersion. Both of these can be field dependent. The PET\n"
            "           files are multi extension FITS binary table. Each extension\n"
            "           contains information about each of the pixel inside one of the\n"
            "           beam listed in the input aperture file.\n"
            "\n"
            "           Input FITS mages are looked for in $AXE_IMAGE_PATH\n"
            "           aXe config file is looked for in $AXE_CONFIG_PATH\n"
            "           All outputs are writen to $AXE_OUTPUT_PATH\n"
            "\n"
            "Usage:\n"
            "      aXe_AF2PET [g/prism image filename] [aXe config filename] [options]\n"
            "\n"
            "Options:\n"
            "           -bck               - generating a PET for a background image\n"
            "                                using a BAF file instead of a OAF file and\n"
            "                                using a background image generated by aXe_BE\n"
            "           -out_PET           - overwrite the default output PET filename\n"
            "           -SCI_hdu=[integer] - overwrite the default from the aXe config file\n"
            "           -ERR_hdu=[integer] - overwrite the default from the aXe config file\n"
            "           -DQ_hdu=[integer]  - overwrite the default from the aXe config file \n"
            "\n"
            "Example:\n"
            "        aXe_AF2PET slim_grismb.fits SLIM.conf.A.0\n"
            "\n", RELEASE);
    exit(1);
  }
  fprintf(stdout, "aXe_AF2PET: Starting...\n");

  /* Get the data file name */
  strcpy(grism_image, argv[1]);

  /* Get the configuration file name */
  strcpy(conf_file, argv[2]);
  build_path(AXE_CONFIG_PATH, conf_file, conf_file_path);

  /* Read the configuration file */
  conf = get_aperture_descriptor(conf_file_path);


  /* Determine where the various extensions are in the FITS file */
  build_path(AXE_IMAGE_PATH, grism_image, grism_image_path);
  get_extension_numbers(grism_image_path, conf, conf->optkey1, conf->optval1);
  /* Determine if we are using the special bck mode */
  /* In this mode, file names are handled diferently */
  if ((opt = get_online_option("bck", argc, argv)))
    bckmode = 1;

  /* Get or set up the input AF file name */
  if ( (opt = get_online_option("in_AF", argc, argv)) )
    {
      strcpy(aper_file, opt);
      strcpy(aper_file_path, opt);
    }
  else
    {
      replace_file_extension(grism_image, aper_file, ".fits",
                           ".OAF", conf->science_numext);
      build_path(AXE_OUTPUT_PATH, aper_file, aper_file_path);
    }

  if (bckmode) {
    /* apply aXe_AF2PET to a background image */
    build_path(AXE_OUTPUT_PATH, grism_image, grism_image_path);

    /* Fix the aperture file name */
    //replace_file_extension(grism_image, aper_file, ".fits",
    //                       ".OAF", conf->science_numext);
    //build_path(AXE_OUTPUT_PATH, aper_file, aper_file_path);
                /* Fix the grism image name to include a .BCK.fits */
    {
      char tmp[MAXCHAR];
      strcpy(tmp, grism_image);
      replace_file_extension(tmp, grism_image, ".fits"
                             ,".BCK.fits", conf->science_numext);

      build_path(AXE_OUTPUT_PATH, grism_image, grism_image_path);
      /* Get the proper extension number from the BCK file */
      get_extension_numbers(grism_image_path, conf, "None", 0);
    }
  } else {

    build_path(AXE_IMAGE_PATH, grism_image, grism_image_path);
    get_extension_numbers(grism_image_path, conf, conf->optkey1, conf->optval1);
    /* Fix the aperture file name */
    //replace_file_extension(grism_image, aper_file, ".fits",
    //                       ".OAF", conf->science_numext);
    //build_path(AXE_OUTPUT_PATH, aper_file, aper_file_path);

  }

  /* Online parameter overwrite all */
  if ((opt = get_online_option("SCI_hdu", argc, argv))) {
    conf->science_numext = atoi(opt);
  }
  if ((opt = get_online_option("ERR_hdu", argc, argv))) {
    conf->errors_numext = atoi(opt);
  }
  if ((opt = get_online_option("DQ_hdu", argc, argv))) {
    conf->dq_numext = atoi(opt);
  }
  /* Get or set up the output PET file name */
  if ((opt = get_online_option("out_PET", argc, argv))) {
    strcpy(PET_file, opt);
    strcpy(PET_file_path, opt);
  } else {
    strcpy(PET_file, grism_image);
    if (bckmode) {
      replace_file_extension(grism_image, PET_file, ".fits",
                             ".PET.fits", -1);
    } else {
      replace_file_extension(grism_image, PET_file, ".fits",
                             ".PET.fits", conf->science_numext);
    }
    build_path(AXE_OUTPUT_PATH, PET_file, PET_file_path);
  }

  fprintf(stdout,
          "aXe_AF2PET: Input configuration file name:   %s\n",
          conf_file_path);
  fprintf(stdout,
          "aXe_AF2PET: Input data file name:            %s\n",
          grism_image_path);
  fprintf(stdout,
          "aXe_AF2PET: SCI extension number:            %d\n",
          conf->science_numext);
  fprintf(stdout,
          "aXe_AF2PET: ERR extension number:            %d\n",
          conf->errors_numext);
  fprintf(stdout,
          "aXe_AF2PET: DQ extension number:             %d\n",
          conf->dq_numext);
  fprintf(stdout,
          "aXe_AF2PET: DQ mask:                         %d\n",
          conf->dqmask);
  fprintf(stdout,
          "aXe_AF2PET: Input aperture file name:        %s\n",
          aper_file_path);
  fprintf(stdout,
          "aXe_AF2PET: Output Pixel Extraction Table \n"
          "            (PET) file name:                 %s\n",
          PET_file_path);
  fprintf(stdout, "\n\n");

  /* Loading the observation data */
  fprintf(stdout, "aXe_AF2PET: ");

  //
  // try to get the descriptor 'exptime' from the 'sci'-extension
  //
  exptime = (double)get_float_from_keyword(grism_image_path, conf->science_numext, conf->exptimekey);
  if (isnan(exptime))
    exptime = (double)get_float_from_keyword(grism_image_path, 1, conf->exptimekey);
  if (isnan(exptime))
    exptime = 1.0;

  if (bckmode)
    {
      sprintf (hdu_name, "ERR");
      err_num = get_hdunum_from_hduname(grism_image_path, hdu_name,
                                        conf->optkey1,conf->optval1,extver);
      sprintf (hdu_name, "DQ");
      dq_num = get_hdunum_from_hduname(grism_image_path, hdu_name,
                                       conf->optkey1,conf->optval1,extver);


      /* Load a background image from AXE_OUTPUT_PATH */
      obs = load_image_t(grism_image_path, 2, err_num, dq_num, conf->dqmask, exptime, conf->rdnoise);
    }
  else
    {
      /* Load an image from AXE_DATA_PATH */
      obs = load_image_t(grism_image_path, conf->science_numext,
                         conf->errors_numext, conf->dq_numext,
                         conf->dqmask, exptime, conf->rdnoise);
    }



  /* Loading the object list */
  /*fprintf(stdout, "aXe_AF2PET: Loading object list...");*/
  oblist = file_to_object_list_seq(aper_file_path, obs);
  fprintf(stdout, "%d objects loaded.\n", object_list_size(oblist));

  /* Create a new empty PET file */
  PET_fitsptr = create_PET_opened(PET_file_path, 1);
  /* Copy the header info from the grism image */
  {
    FITScards      *cards;
    cards = get_FITS_cards(grism_image_path, 1);
    put_FITS_cards_opened(PET_fitsptr, cards);
    free_FITScards(cards);
  }


  i = 0;
  if (oblist != NULL) {
    while (oblist[i] != NULL) {
      /*fprintf(stdout, "aXe_AF2PET: Extracting object ID:%d",
              oblist[i]->ID);*/
      for (j = 0; j < oblist[i]->nbeams; j++) {
        /*
         * skip beam if ignore flag for thisbeam is
         * set
         */
        if (oblist[i]->beams[j].ignore !=0)
          {
            /*fprintf(stdout,", %c ( Ignored )",
                    BEAM(oblist[i]->beams[j].ID));*/
            continue;
          }
        /*fprintf(stdout, ", %c (Extracted)",
                BEAM(oblist[i]->beams[j].ID));*/

        quad_to_bbox(oblist[i]->beams[j].corners,
                     oblist[i]->beams[j].bbox,
                     oblist[i]->beams[j].bbox + 1);
        result = make_spc_table(oblist[i], j, &flags);

        /************************/
        /* Wavelength calibrate */
        /************************/

        /*
         * get the wavelength dispersion relation at
         * position "refpoint". conf->refx and conf->refy
         * are used at this point to allow for a non (0,0) centered
         * 2D field dependence.
         */
        pixel.x = oblist[i]->beams[j].refpoint.x - conf->refx;
        pixel.y = oblist[i]->beams[j].refpoint.y - conf->refy;

        /*
         * check whether it is grism (for_grism=1)
         * or prism (for_grism=0) data
         */
        for_grism = check_for_grism (conf_file_path,oblist[i]->beams[j].ID);

        // this should be done better
        // the hard limit to FORS applications
        // is no good!
        if (!strcmp(conf->camera, "FORS2"))
          {
            fprintf(stdout, "camera: %s\n", conf->camera);
            disp = get_dispstruct_at_pos(conf_file_path, for_grism,
                                         oblist[i]->beams[j].ID,pixel);
            wl_calibration = create_calib_from_gsl_vector(for_grism, disp->pol);

            gdisp = get_global_disp(conf_file_path, for_grism, oblist[i]->beams[j].ID);

            pwise_wl_calib(gdisp, pixel, oblist[i]->beams[j], for_grism, result, wl_calibration);

            free_global_disp(gdisp);
            free_calib(wl_calibration);
          }
        else
          {
            disp = get_dispstruct_at_pos(conf_file_path, for_grism,
                                         oblist[i]->beams[j].ID,pixel);

            wl_calibration = create_calib_from_gsl_vector(for_grism, disp->pol);

            /* for grism data: get the allowed trace range */
            if (!for_grism){
              p_range = get_prange(conf_file_path, oblist[i]->beams[j].ID);
              /* in case that a trace range is defined,
               * trim the PET down to only the allowed
               * trace range
               */
              if (p_range != NULL)
                result = prange_cut(result, p_range, wl_calibration);
            }

            /* apply the wavelength calibration */
            wl_calib(result, wl_calibration);

            /* free the memory in the calibration structure */
            free_calib(wl_calibration);

          }

        {
          char            ID[60], IDindex[60];
          sprintf(ID, "%d%c", oblist[i]->ID, BEAM(oblist[i]->beams[j].ID));
          sprintf(IDindex, "ID%s", ID);
          add_ALL_to_PET(result, ID, PET_fitsptr, 0);
        }

        /*
         * Add information about the object/beam into
         * FITS header
         */
        cards = beam_to_FITScards(oblist[i], j);
        put_FITS_cards_opened(PET_fitsptr, cards);
        free_FITScards(cards);

        /*
         * Add information about the wavelength
         * calibration
         */
        cards = dispstruct_to_FITScards(disp);
        put_FITS_cards_opened(PET_fitsptr, cards);
        free_FITScards(cards);

        //fprintf_ap_pixel_list (stdout, result);

        free_dispstruct(disp);
        if (result!=NULL)
          {
            free(result);
            result = NULL;
          }
      }
      /*fprintf(stdout, " Done.\n");*/
      i++;
    }
  }
  fits_close_file(PET_fitsptr, &f_status);
  if (f_status) {
    aXe_message(aXe_M_FATAL, __FILE__, __LINE__,
                "aXe_AF2PET: " "Error closing PET: %s \n",
                PET_file_path);
  }
  free_observation(obs);
  if (oblist != NULL){
        free_oblist(oblist);}
  fprintf(stdout, "aXe_AF2PET: Done...\n");

  exit(0);
}
