/* $Id: fors_qc.c,v 1.10 2010-09-14 07:49:30 cizzo Exp $
 *
 * This file is part of the FORS Library
 * Copyright (C) 2002-2010 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 St, Fifth Floor, Boston, MA  02110-1301 USA
 */

/*
 * $Author: cizzo $
 * $Date: 2010-09-14 07:49:30 $
 * $Revision: 1.10 $
 * $Name: not supported by cvs2svn $
 */

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

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <math.h>

#include <cpl.h>
#include <fors_utils.h>
#include <fors_paf.h>
#include <fors_dfs.h>
#include <fors_qc.h>

#define DICT_LINE_LENGTH    (80)
#define MAX_PAF_NAME_LENGTH (80)
#define PAF_ROOT_NAME       "qc"

/**
 * @defgroup forsqc Quality Control Utilities
 *
 * The module collects utility functions for quality control operations.
 */

/**@{*/
const char *const fors_qc_dic_version = "2.0";

static ForsPAF *pafFile = NULL;
static int     pafIndex = 0;


/**
 * @brief
 *   Initiate a new QC1 group.
 *
 * @param header           product header
 * @param qcdic_version    dictionary ID
 * @param instrument       instrument name
 * @return @c CPL_ERROR_NONE on success
 *
 * A new PAF object is initiated, with the name depending on the root
 * PAF_ROOT_NAME and the current pafIndex. If the previous QC1 PAF file
 * is found open, this is an error: fors_qc_end_group() should be called
 * first.
 */

cpl_error_code fors_qc_start_group(cpl_propertylist *header,
                                   const char *qcdic_version, 
                                   const char *instrument)
{
    char pafName[MAX_PAF_NAME_LENGTH];

    if (pafFile)
        return cpl_error_set("fors_qc_start_group", CPL_ERROR_FILE_ALREADY_OPEN);

    sprintf(pafName, "%s%.4d.paf", PAF_ROOT_NAME, pafIndex);

    if (!(pafFile = newForsPAF(pafName, "QC1 parameters", NULL, NULL)))
        return cpl_error_set("fors_qc_start_group", CPL_ERROR_FILE_NOT_CREATED);
    
    fors_qc_write_qc_string(header, 
                            "QC.DID", qcdic_version, "QC1 dictionary",
                            instrument);

    return CPL_ERROR_NONE;

}

#undef cleanup
#define cleanup \
do { \
    cpl_propertylist_delete(header); \
} while(0)
/**
 * @brief
 *   Initiate a new QC1 group and log basic QC
 *
 * @param raw_frame        input frame
 * @param pro_catg         product category
 * @param instrument       instrument name
 *
 * This function logs common QC parameters.
 */

void fors_qc_write_group_heading(const cpl_frame *raw_frame,
                                 const char *pro_catg,
                                 const char *instrument)
{
    cpl_propertylist *header = NULL;
    assure( raw_frame != NULL, return, NULL );
    assure( cpl_frame_get_filename(raw_frame) != NULL, return, NULL );

    header = cpl_propertylist_load(cpl_frame_get_filename(raw_frame), 0);
    assure( !cpl_error_get_code(), return, "Could not load %s header",
            cpl_frame_get_filename(raw_frame));
    
    fors_qc_write_string("PRO.CATG", pro_catg,
                         "Product category", instrument);
    assure( !cpl_error_get_code(), return, "Cannot write product category to "
            "QC log file");
    
    fors_qc_keyword_to_paf(header, "ESO DPR TYPE", NULL, 
                           "DPR type", instrument);
    assure( !cpl_error_get_code(), return, "Missing keyword DPR TYPE in raw "
            "frame header");
    
    fors_qc_keyword_to_paf(header, "ESO TPL ID", NULL, 
                           "Template", instrument);
    assure( !cpl_error_get_code(), return, "Missing keyword TPL ID in raw "
            "frame header");
    
    if (cpl_propertylist_has(header, "ESO INS FILT1 NAME")) {
        fors_qc_keyword_to_paf(header, "ESO INS FILT1 NAME", NULL,
                               "Filter name", instrument);
        assure( !cpl_error_get_code(), return, "Failed to write ESO INS FILT1 NAME");
    }
    
    fors_qc_keyword_to_paf(header, "ESO INS COLL NAME", NULL,
                           "Collimator name", instrument);
    assure( !cpl_error_get_code(), return, "Missing keyword INS COLL NAME in raw "
            "frame header");
    
    fors_qc_keyword_to_paf(header, "ESO DET CHIP1 ID", NULL,
                           "Chip identifier", instrument);
    assure( !cpl_error_get_code(), return, "Missing keyword DET CHIP1 ID in raw "
            "frame header");
    
    fors_qc_keyword_to_paf(header, "ESO DET WIN1 BINX", NULL,    
                           "Binning factor along X", instrument);
    assure( !cpl_error_get_code(), return, "Missing keyword ESO DET WIN1 BINX "
            "in raw frame header");
    
    fors_qc_keyword_to_paf(header, "ESO DET WIN1 BINY", NULL,
                           "Binning factor along Y", instrument);
    assure( !cpl_error_get_code(), return, "Missing keyword ESO DET WIN1 BINY "
            "in raw frame header");
    
    fors_qc_keyword_to_paf(header, "ARCFILE", NULL,
                           "Archive name of input data", 
                           instrument);
    assure( !cpl_error_get_code(), return, "Missing keyword ARCFILE in raw "
            "frame header");
    
    {
        char *pipefile = dfs_generate_filename(pro_catg);
        fors_qc_write_string("PIPEFILE", pipefile, 
                             "Pipeline product name", instrument);
        cpl_free(pipefile); pipefile = NULL;
        assure( !cpl_error_get_code(), return, "Cannot write PIPEFILE to QC log file");
    }

    cleanup;
    return;
}



/**
 * @brief
 *   Close current QC1 PAF file.
 *
 * @return @c CPL_ERROR_NONE on success
 *
 * The current QC1 PAF object is written to disk file. If no PAF object
 * is present, this is an error: @c fors_qc_start_group() should be called
 * first. If the PAF file is empty, the PAF object is destroyed, but
 * no PAF file is created.
 */

cpl_error_code fors_qc_end_group(void)
{

    if (!pafFile)
        return cpl_error_set("fors_qc_end_group", CPL_ERROR_DATA_NOT_FOUND);

    if (!forsPAFIsEmpty(pafFile)) {
        forsPAFWrite(pafFile);
        pafIndex++;
    }

    deleteForsPAF(pafFile);
    pafFile = NULL;

    return CPL_ERROR_NONE;

}


/**
 * @brief
 *   Add string parameter to current QC1 group.
 *
 * @param name       Parameter name
 * @param value      Parameter value
 * @param comment    Parameter comment
 * @param instrument Instrument name
 *
 * @return @c CPL_ERROR_NONE on success
 *
 * To the current QC1 PAF object is appended a string parameter. If no
 * PAF object is present, this is an error: fors_qc_start_group() should be
 * called first.
 */

cpl_error_code fors_qc_write_string(const char *name, const char *value, 
                                const char *comment, const char *instrument)
{

    int status;
    int   length = strlen(instrument) + 3;
    char *allComment;


    if (comment == NULL || name == NULL || instrument == NULL)
        return cpl_error_set("fors_qc_write_string", CPL_ERROR_NULL_INPUT);

    length += strlen(comment) + 1;

    allComment = cpl_malloc(length * sizeof(char));

    sprintf(allComment, "%s [%s]", comment, instrument);

    status = forsPAFAppendString(pafFile, name, value, allComment);

    cpl_free(allComment);

    if (status)
        cpl_msg_error("fors_qc_write_string", 
                      "Cannot write parameter %s to QC1 PAF", name);

    cpl_msg_debug(cpl_func, "%s [%s] = '%s'", comment, name, value);

    return CPL_ERROR_NONE;

}

cpl_error_code fors_qc_write_string_chat(const char *name, const char *value,
                                const char *comment, const char *instrument)
{

    int status;
    int   length = strlen(instrument) + 3;
    char *allComment;


    if (comment == NULL || name == NULL || instrument == NULL)
        return cpl_error_set("fors_qc_write_string_chat", CPL_ERROR_NULL_INPUT);

    length += strlen(comment) + 1;

    allComment = cpl_malloc(length * sizeof(char));

    sprintf(allComment, "%s [%s]", comment, instrument);

    status = forsPAFAppendString(pafFile, name, value, allComment);

    cpl_free(allComment);

    if (status)
        cpl_msg_error("fors_qc_write_string_chat",
                      "Cannot write parameter %s to QC1 PAF", name);

    cpl_msg_info(cpl_func, "%s [%s] = '%s'", comment, name, value);

    return CPL_ERROR_NONE;

}


/**
 * @brief
 *   Add double parameter to current QC1 group.
 *
 * @param name       Parameter name
 * @param value      Parameter value
 * @param unit       Parameter unit
 * @param comment    Parameter comment;
 * @param instrument Instrument name
 *
 * @return @c CPL_ERROR_NONE on success
 *
 * To the current QC1 PAF object is appended a double parameter.
 * The comment string is mandatory. The parameter unit must be
 * specified, unless the specified parameter is adimensional, otherwise
 * a null pointer should be passed. To the comment string the unit
 * string (if present) will be appended, enclosed in round brackets,
 * and then the string "[@em instrument]". If no PAF object is present, 
 * this is an error: fors_qc_start_group() should be called first.
 */

cpl_error_code fors_qc_write_double(const char *name, double value, 
                                const char *unit, const char *comment,
                                const char *instrument)
{

    cpl_error_code status;
    int   length = strlen(instrument) + 3;
    char *allComment;


    if (comment == NULL || name == NULL || instrument == NULL)
        return cpl_error_set("fors_qc_write_double", CPL_ERROR_NULL_INPUT);

    length += strlen(comment) + 1;

    if (unit)
      length += strlen(unit) + 3;

    allComment = cpl_malloc(length * sizeof(char));

    if (unit)
      sprintf(allComment, "%s (%s) [%s]", comment, unit, instrument);
    else
      sprintf(allComment, "%s [%s]", comment, instrument);

    status = forsPAFAppendDouble(pafFile, name, value, allComment);

    cpl_free(allComment);

    if (status)
        cpl_msg_error("fors_qc_write_double", 
                      "Cannot write parameter %s to QC1 PAF", name);

    cpl_msg_info(cpl_func, "%s [%s] = %f %s", 
                 comment, name, value, (unit != NULL) ? unit : "");

    return CPL_ERROR_NONE;

}


cpl_error_code fors_qc_write_int(const char *name, int value, const char *unit, 
                             const char *comment, const char *instrument)
{

    cpl_error_code status;
    int   length = strlen(instrument) + 3;
    char *allComment;


    if (comment == NULL || name == NULL || instrument == NULL)
        return cpl_error_set("fors_qc_write_int", CPL_ERROR_NULL_INPUT);

    length += strlen(comment) + 1;

    if (unit)
      length += strlen(unit) + 3;

    allComment = cpl_malloc(length * sizeof(char));

    if (unit)
      sprintf(allComment, "%s (%s) [%s]", comment, unit, instrument);
    else
      sprintf(allComment, "%s [%s]", comment, instrument);

    status = forsPAFAppendInt(pafFile, name, value, allComment);

    cpl_free(allComment);

    if (status)
        cpl_msg_error("fors_qc_write_int", 
                      "Cannot write parameter %s to QC1 PAF", name);

    cpl_msg_info(cpl_func, "%s [%s] = %d %s", 
                 comment, name, value, (unit != NULL) ? unit : "");
 
    return CPL_ERROR_NONE;

}


/**
 * @brief
 *   Copy a keyword value to the currently active QC1 PAF object.
 *
 * @param header     Pointer to a keyword header.
 * @param name       Keyword name.
 * @param unit       Optional unit to be associated to keyword value.
 * @param comment    Optional comment to be associated to keyword value.
 * @param instrument Instrument name
 *
 * @return @c CPL_ERROR_NONE on success
 *
 *   A keyword with the specified name is searched in the @em header. 
 *   Its type is determined, then its value is read with the appropriate 
 *   interface. From the keyword name the corresponding PAF keyword 
 *   name is derived by removing any "ESO " at keyword name beginning, 
 *   and replacing blanks with dots (e.g., "ESO TPL ID" becomes "TPL.ID").
 *   Finally, the new PAF keyword, with the same type as the header keyword, 
 *   is written to the currently active QC1 PAF object. Note that before 
 *   calling this funtion a QC1 PAF object must be created with a call to 
 *   fors_qc_start_group().
 */

cpl_error_code fors_qc_keyword_to_paf(cpl_propertylist *header, 
                                     const char *name, const char *unit, 
                                     const char *comment, 
                                     const char *instrument)
{

  const char func[] = "fors_qc_keyword_to_paf";

  char            *keyName;
  char            *keep;
  char            *pos;
  int              ivalue;
  float            fvalue;
  double           dvalue;
  char            *svalue = NULL;
  int              status;
  int              i;


  if (header == NULL) {
    cpl_msg_error(func, "Empty header");
    return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
  }

  if (!cpl_propertylist_has(header, name)) {
    cpl_msg_error(func, "Keyword %s not found", name);
    return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
  }

  switch (cpl_propertylist_get_type(header, name)) {
  case CPL_TYPE_INT :
    ivalue = cpl_propertylist_get_int(header, name);
    break;
  case CPL_TYPE_FLOAT :
    fvalue = cpl_propertylist_get_float(header, name);
    break;
  case CPL_TYPE_DOUBLE :
    dvalue = cpl_propertylist_get_double(header, name);
    break;
  case CPL_TYPE_STRING :
    svalue = (char *)cpl_propertylist_get_string(header, name);
    break;
  default :
    cpl_msg_error(func, "Unsupported keyword type");
    return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
  }


  /*
   *  Construct entry name for PAF
   */

  keep = keyName = cpl_strdup(name);

  pos = strstr(keyName, "ESO ");

  if (pos == keyName)
    keyName += 4;

  for (i = 0; keyName[i] != '\0'; i++)
    if (keyName[i] == ' ')
      keyName[i] = '.';

  /*
   *  Now write entry to PAF object.
   */

  switch (cpl_propertylist_get_type(header, name)) {
  case CPL_TYPE_INT :
    status = fors_qc_write_int(keyName, ivalue, unit, comment, instrument);
    break;
  case CPL_TYPE_FLOAT :
    dvalue = fvalue;
  case CPL_TYPE_DOUBLE :
    status = fors_qc_write_double(keyName, dvalue, unit, comment, instrument);
    break;
  default :    /* CPL_TYPE_STRING */
    status = fors_qc_write_string(keyName, svalue, comment, instrument);
  }

  if (status)
    cpl_msg_error(func, "Could not copy keyword value to QC1 PAF!");

  cpl_free(keep);

  return status;

}

/**
 * @brief
 *   Write a string value to the active QC1 PAF object and to a header.
 *
 * @return @c CPL_ERROR_NONE on success
 *
 * @param header     Product header
 * @param name       QC1 PAF entry name.
 * @param value      Value to write.
 * @param unit       Optional unit to be associated to value.
 * @param comment    Comment to be associated to value.
 * @param instrument Instrument name
 *
 *   An entry with the specified @em name is written to the current QC1 PAF
 *   object. From the entry @em name, the name of the QC keyword that
 *   should be written to header is derived prepending the string "ESO "
 *   and replacing all '.' with a blank (e.g., "QC.BIAS.MASTER.MEAN"
 *   becomes "ESO QC BIAS MASTER MEAN"). Finally, the new keyword
 *   is written to the header. Note that before calling this funtion
 *   a QC1 PAF object must be created with a call to fors_qc_start_group().
 */
cpl_error_code fors_qc_write_qc_string(cpl_propertylist *header,
                                       const char *name, const char *value, 
                                       const char *comment, 
                                       const char *instrument)
{
    const char func[] = "fors_qc_write_qc_string";

    char *header_name;
    int   i;

    if (strcmp("QC.DID", name)) {
        if (fors_qc_write_string_chat(name, value, comment, instrument)) {
            cpl_error_set_where(func);
            return cpl_error_get_code();
        }
    }
    else {
        if (fors_qc_write_string(name, value, comment, instrument)) {
            cpl_error_set_where(func);
            return cpl_error_get_code();
        }
    }

    header_name = cpl_malloc((strlen(name) + 6) * sizeof(char *));

    strcpy(header_name, "ESO ");
    strcat(header_name, name);

    for (i = 0; header_name[i] != '\0'; i++)
        if (header_name[i] == '.')
            header_name[i] = ' ';

    if (cpl_propertylist_update_string(header, header_name, value)) {
        cpl_free(header_name);
        cpl_error_set_where(func);
        return cpl_error_get_code();
    }

    cpl_propertylist_set_comment(header, header_name, comment);

    cpl_free(header_name);

    return CPL_ERROR_NONE;
}

/**
 * @brief
 *   Write an integer value to the active QC1 PAF object and to a header.
 *
 * @return @c CPL_ERROR_NONE on success
 *
 * @param header     Product header
 * @param value      Value to write.
 * @param name       QC1 PAF entry name.
 * @param unit       Optional unit to be associated to value.
 * @param comment    Comment to be associated to value.
 * @param instrument Instrument name
 *
 *   This function writes the header entries directly to the header
 *   of the FITS file written to disk, using the qfits_replace_card() call.
 *   An entry with the specified @em name is written to the current QC1 PAF
 *   object. From the entry @em name, the name of the QC keyword that
 *   should be written to header is derived prepending the string "ESO "
 *   and replacing all '.' with a blank (e.g., "QC.BIAS.MASTER.MEAN"
 *   becomes "ESO QC BIAS MASTER MEAN"). Finally, the new keyword
 *   is written to the header. Note that before calling this funtion
 *   a QC1 PAF object must be created with a call to fors_qc_start_group().
 */

cpl_error_code fors_qc_write_qc_double(cpl_propertylist *header, double value, 
                                      const char *name, const char *unit, 
                                      const char *comment,
                                      const char *instrument)
{

  const char func[] = "fors_qc_write_qc_double";

  char *header_name;
  int   i;


  if (fors_qc_write_double(name, value, unit, comment, instrument)) {
      cpl_error_set_where(func);
      return cpl_error_get_code();
  }

  header_name = cpl_malloc((strlen(name) + 6) * sizeof(char *));

  strcpy(header_name, "ESO ");
  strcat(header_name, name);

  for (i = 0; header_name[i] != '\0'; i++)
    if (header_name[i] == '.')
      header_name[i] = ' ';

  if (cpl_propertylist_update_double(header, header_name, value)) {
      cpl_free(header_name);
      cpl_error_set_where(func);
      return cpl_error_get_code();
  }

  cpl_propertylist_set_comment(header, header_name, comment);

  cpl_free(header_name);

  return CPL_ERROR_NONE;

}


cpl_error_code fors_qc_write_qc_int(cpl_propertylist *header, int value,
                                   const char *name, const char *unit,
                                   const char *comment,
                                   const char *instrument)
{

  const char func[] = "fors_qc_write_qc_int";

  char *header_name;
  int   i;


  if (fors_qc_write_int(name, value, unit, comment, instrument)) {
      cpl_error_set_where(func);
      return cpl_error_get_code();
  }

  header_name = cpl_malloc((strlen(name) + 6) * sizeof(char *));

  strcpy(header_name, "ESO ");
  strcat(header_name, name);

  for (i = 0; header_name[i] != '\0'; i++)
    if (header_name[i] == '.')
      header_name[i] = ' ';

  if (cpl_propertylist_update_int(header, header_name, value)) {
      cpl_free(header_name);
      cpl_error_set_where(func);
      return cpl_error_get_code();
  }

  cpl_propertylist_set_comment(header, header_name, comment);

  cpl_free(header_name);

  return CPL_ERROR_NONE;

}

/*
 * @brief
 *   Write an integer value to the active QC1 PAF object and to a header.
 *
 * @return @c CPL_ERROR_NONE on success
 *
 * @param filnam  Name of existing FITS file.
 * @param value   Value to write.
 * @param name    QC1 PAF entry name.
 * @param unit    Optional unit to be associated to value.
 * @param comment Optional comment to be associated to value.
 *
 * @doc
 *   This function writes the header entries directly to the header 
 *   of the FITS file written to disk, using the qfits_replace_card() call.
 *   An entry with the specified @em name is written to the current QC1 PAF 
 *   object. From the entry @em name, the name of the QC keyword that
 *   should be written to header is derived prepending the string "ESO "
 *   and replacing all '.' with a blank (e.g., "QC.BIAS.MASTER.MEAN"
 *   becomes "ESO QC BIAS MASTER MEAN"). Finally, the new keyword
 *   is written to the header. Note that before calling this funtion 
 *   a QC1 PAF object must be created with a call to fors_qc_start_group().
 */

/*
cpl_error_code fors_qc_write_qc_int(char *filnam, int value, const char *name,
                               const char *unit, const char *comment,
                               const char *instrument)
{

  const char func[] = "fors_qc_write_qc_int";

  char             line[81];
  char             val[81];
  char            *descName;
  int              i;


  if (fors_qc_write_int(name, value, unit, comment, instrument)) {
    cpl_msg_error(func, "Could not copy value to QC1 PAF!");
    cpl_error_set_where(func);
    return cpl_error_get_code();
  }

  descName = cpl_malloc((strlen(name) + 15) * sizeof(char *));

  strcpy(descName, "HIERARCH ESO ");
  strcat(descName, name);

  for (i = 0; descName[i] != '\0'; i++)
    if (descName[i] == '.')
      descName[i] = ' ';

  sprintf(val, "%d", value);
  keytuple2str(line, descName, val, (char *)comment);
  qfits_replace_card(filnam, descName, line);

  cpl_free(descName);

  return CPL_ERROR_NONE;

}


cpl_error_code fors_qc_write_qc_double(char *filnam, double value, 
                                      const char *name, const char *unit, 
                                      const char *comment,
                                      const char *instrument)
{

  const char func[] = "fors_qc_write_qc_double";

  char             line[81];
  char             val[81];
  char            *descName;
  int              i;


  if (fors_qc_write_double(name, value, unit, comment, instrument)) {
    cpl_msg_error(func, "Could not copy value to QC1 PAF!");
    cpl_error_set_where(func);
    return cpl_error_get_code();
  }

  descName = cpl_malloc((strlen(name) + 15) * sizeof(char *));

  strcpy(descName, "HIERARCH ESO ");
  strcat(descName, name);

  for (i = 0; descName[i] != '\0'; i++)
    if (descName[i] == '.')
      descName[i] = ' ';

  sprintf(val, "%1.6e", value);
  keytuple2str(line, descName, val, (char *)comment);
  qfits_replace_card(filnam, descName, line);

  cpl_free(descName);

  return CPL_ERROR_NONE;

}

*/

/**@}*/
