/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set sw=2 sts=2 et cin: */
/*
 * This file is part of the MUSE Instrument Pipeline
 * Copyright (C) 2005-2013 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.
 */

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

/*----------------------------------------------------------------------------*
 *                              Includes                                      *
 *----------------------------------------------------------------------------*/
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <cpl.h>

#include "muse_processing.h"

#include "muse_utils.h"

/*---------------------------------------------------------------------------*/
/**
 * @addtogroup muse_processing
 */
/*---------------------------------------------------------------------------*/

/**@{*/

/*----------------------------------------------------------------------------*
 *                          Static variables                                  *
 *----------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
/**
  Structure to hold the extended processing information.
 */
/*---------------------------------------------------------------------------*/
typedef struct muse_processinginfo_s {
  struct muse_processinginfo_s *prev;
  struct muse_processinginfo_s *next;
  cpl_recipe *plugin;
  cpl_recipeconfig *recipeconfig;
  /** @brief Function to check the FITS header and insert the comments */
  muse_processing_prepare_header_func *prepare_header;
  /** @brief Function to get the processing level of a frame */
  muse_processing_get_frame_level_func *get_frame_level; 
  /** @brief Function to get the processing level of a frame */
  muse_processing_get_frame_mode_func *get_frame_mode; 
} muse_processinginfo_t;

/*---------------------------------------------------------------------------*/
/**
  NULL-terminated array of all registered processing information
 */
/*---------------------------------------------------------------------------*/
static muse_processinginfo_t *muse_processinginfo = NULL;

/*---------------------------------------------------------------------------*/
/**
  @brief Get processinginfo for a certain recipe
 */
/*---------------------------------------------------------------------------*/

static muse_processinginfo_t *
muse_processinginfo_get(const cpl_recipe *aRecipe) {
  if (muse_processinginfo != NULL) {
    muse_processinginfo_t *m;
    for (m = muse_processinginfo; m != NULL; m = m->next) {
      if (m->plugin == aRecipe) {
        return m;
      }
    }
  }
  return NULL;
}

/*---------------------------------------------------------------------------*/
/**
  @brief Register extended functionalities for MUSE recipes
  @param plugin            the CPL recipe
  @param recipeconfig      the recipe configuration
  @param prepare_header    function to declare the properties of a frame
  @param get_frame_level   function that returns the processing level of
  @param get_frame_mode    function that returns the output mode of a frame

  This functions registers all extensions that are used to use our
  framework.
 */
/*---------------------------------------------------------------------------*/
void
muse_processinginfo_register
( cpl_recipe *plugin,
  cpl_recipeconfig *recipeconfig,
  muse_processing_prepare_header_func *prepare_header,
  muse_processing_get_frame_level_func *get_frame_level,
  muse_processing_get_frame_mode_func *get_frame_mode) {

  muse_processinginfo_t *m = muse_processinginfo;
  if (muse_processinginfo == NULL) {
    muse_processinginfo = cpl_calloc(1, sizeof(muse_processinginfo_t));
    m = muse_processinginfo;
  } else {
    while (m->next != NULL) {
      m = m->next;
    }
    m->next = cpl_calloc(1, sizeof(muse_processinginfo_t));
    m->next->prev = m;
    m = m->next;
  }
  m->plugin = plugin;
  m->recipeconfig = recipeconfig;
  m->prepare_header = prepare_header;
  m->get_frame_level = get_frame_level;
  m->get_frame_mode = get_frame_mode;
}

/*---------------------------------------------------------------------------*/
/**
  @brief Clear all information from the processing info and from the recipe
         config.
  @param aRecipe     the CPL recipe

  To be called before closing the program.
 */
/*---------------------------------------------------------------------------*/
void
muse_processinginfo_delete(cpl_recipe *aRecipe) {
  muse_processinginfo_t *m = muse_processinginfo_get(aRecipe);
  if (m == NULL) {
    return;
  }
  if (m == muse_processinginfo) {
    muse_processinginfo = m->next;
    if (muse_processinginfo != NULL) {
      muse_processinginfo->prev = NULL;
    }
  } else {
    m->prev->next = m->next;
    if (m->next != NULL) {
      m->next->prev = m->prev;
    }
  }
  cpl_recipeconfig_delete(m->recipeconfig);
  cpl_free(m);
}

/*---------------------------------------------------------------------------*/
/**
  @brief Prepare and check a FITS header for a certain frame tag.
  @param aRecipe     the CPL recipe
  @param aFrametag   the tag of the output frame
  @param aHeader     the FITS header of the output frame
  @return CPL_ERROR_NONE if the header was OK
 */
/*---------------------------------------------------------------------------*/
cpl_error_code muse_processing_prepare_header(const cpl_recipe *aRecipe,
                                              const char *aFrametag,
                                              cpl_propertylist *aHeader) {
  muse_processinginfo_t *m = muse_processinginfo_get(aRecipe);
  return (m != NULL)? (* m->prepare_header)(aFrametag, aHeader): CPL_ERROR_NONE;
}

/*---------------------------------------------------------------------------*/
/**
  @brief Get the level for a product frame with a certain tag
  @param aRecipe     the CPL recipe
  @param aFrametag   the tag of the output frame
  @return its frame level, CPL_FRAME_LEVEL_NONE if the tag was not registered.
 */
/*---------------------------------------------------------------------------*/
cpl_frame_level
muse_processing_get_frame_level(const cpl_recipe *aRecipe,
                                const char *aFrametag) {
  muse_processinginfo_t *m = muse_processinginfo_get(aRecipe);
  return (m != NULL)? (* m->get_frame_level)(aFrametag): CPL_FRAME_LEVEL_NONE;
}

/*---------------------------------------------------------------------------*/
/**
  @brief Get the mode for a product frame with a certain tag
  @param aRecipe     the CPL recipe
  @param aFrametag   the tag of the output frame
  @return its frame level, MUSE_FRAME_MODE_ALL if the tag was not registered.
 */
/*---------------------------------------------------------------------------*/
int
muse_processing_get_frame_mode(const cpl_recipe *aRecipe, 
                               const char *aFrametag) {
  muse_processinginfo_t *m = muse_processinginfo_get(aRecipe);
  return (m != NULL)?(* m->get_frame_mode)(aFrametag): MUSE_FRAME_MODE_ALL;
}

/*---------------------------------------------------------------------------*/
/**
  @brief Get the recipe (frame) configuration.
  @param aRecipe Pointer to the recipe
  @return the recipe config, or NULL if not found.
 */
/*---------------------------------------------------------------------------*/
cpl_recipeconfig *
muse_processing_get_recipeconfig(cpl_recipe *aRecipe) {
  muse_processinginfo_t *m = muse_processinginfo_get(aRecipe);
  return (m != NULL)?m->recipeconfig: NULL;
}

/*---------------------------------------------------------------------------*/
/**
  @brief Prepare and check the specified property
  @param aHeader the header to be prepared
  @param aName the property name
  @param aType the property type
  @param aDescription the description (comment) of this property
  @return CPL error code

  Check the specified property for its existence and correct type and
  append the comment to it.

  @error{return CPL_ERROR_DATA_NOT_FOUND, the property was not found}
  @error{return CPL_ERROR_TYPE_MISMATCH, the property has different type}
  @error{return CPL_ERROR_NULL_INPUT, the header or the name was NULL}
 */
/*---------------------------------------------------------------------------*/
cpl_error_code
muse_processing_prepare_property(cpl_propertylist *aHeader, const char *aName,
                                 cpl_type aType, const char *aDescription)
{
  cpl_ensure_code(aHeader, CPL_ERROR_NULL_INPUT);
  cpl_ensure_code(aName, CPL_ERROR_NULL_INPUT);

  cpl_error_code rc = CPL_ERROR_NONE;

  cpl_propertylist *list = cpl_propertylist_new();
  cpl_propertylist_copy_property_regexp(list, aHeader, aName, 0);
  if (cpl_propertylist_is_empty(list)) {
    cpl_propertylist_delete(list);

    // The tag MUSE PRIVATE DOCUMENTATION is set in xmldoc.c and checked
    // here:
    // If it is not set (the normal case in a recipe), just an error
    // message is generated in case of a missing header.
    if (!cpl_propertylist_has(aHeader, "MUSE PRIVATE DOCUMENTATION")) {
      cpl_msg_warning(__func__, "Property %s (%s) not used", aName,
                      aDescription);
      return CPL_ERROR_DATA_NOT_FOUND;
    }
    // If this property is set (if the function is used from
    // muse_xmldoc), then we add missing headers with some default
    // values.
    switch(aType) {
    case CPL_TYPE_FLOAT:
        cpl_propertylist_append_float(aHeader, aName, NAN);
        break;
      case CPL_TYPE_DOUBLE:
        cpl_propertylist_append_double(aHeader, aName, NAN);
        break;
      case CPL_TYPE_STRING:
        cpl_propertylist_append_string(aHeader, aName, "");
        break;
      case CPL_TYPE_INT:
        cpl_propertylist_append_int(aHeader, aName, INT_MAX);
        break;
      case CPL_TYPE_LONG:
        cpl_propertylist_append_long(aHeader, aName, LONG_MAX);
        break;
      case CPL_TYPE_BOOL:
        cpl_propertylist_append_bool(aHeader, aName, FALSE);
        break;
      default:
        return CPL_ERROR_INVALID_TYPE;
    }

    /* XXX ugly: copied code from below for the moment to keep the case *
     *     of the documentation working                                 */
    cpl_property *property = cpl_propertylist_get_property(aHeader, aName);
    if (aDescription != NULL && strlen(aDescription)>0) {
      rc = cpl_property_set_comment(property, aDescription);
    }
    cpl_type type = cpl_property_get_type(property);
    if (type != aType) {
      cpl_msg_warning(__func__, "Type of property %s is %s but should be %s",
                      aName, cpl_type_get_name(type), cpl_type_get_name(aType));
      return CPL_ERROR_TYPE_MISMATCH;
    }
    return CPL_ERROR_NONE;
  } /* empty list of properties of regexp search */

  /* loop through the properties that matched the regular expressions */
  int i;
  for (i = 0; i < cpl_propertylist_get_size(list); i++) {
    cpl_property *prop = cpl_propertylist_get(list, i);
    cpl_property *property =
      cpl_propertylist_get_property(aHeader, cpl_property_get_name(prop));
    if (aDescription != NULL && strlen(aDescription)>0) {
      rc = cpl_property_set_comment(property, aDescription);
    }
    cpl_type type = cpl_property_get_type(property);
    if (type != aType) {
      cpl_msg_warning(__func__, "Type of property %s is %s but should be %s",
                      aName, cpl_type_get_name(type), cpl_type_get_name(aType));
      cpl_propertylist_delete(list);
      return CPL_ERROR_TYPE_MISMATCH;
    }
  } /* for i (all regexp-found properties) */
  cpl_propertylist_delete(list);
  return rc;
}

/**@}*/
