/*
 * This file is part of the GIRAFFE Pipeline
 * Copyright (C) 2002-2019 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
 */

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

#include <cxstring.h>
#include <cxstrutils.h>

#include <cpl_error.h>

#include "gialias.h"
#include "giutils.h"
#include "giframe.h"
#include "gierror.h"


/**
 * @defgroup giframe Frame Utilities
 *
 * This module defines the various symbols used as frame tags and provides
 * utility functions to offer an easy way to create product frames.
 */

/**@{*/

inline static cxint
_giraffe_frame_update_product(cpl_propertylist* properties,
                              const cxchar* name, const cxchar* tag,
                              const cxchar* type, const cxchar* tech)
{

    cxchar* dpr_tech = NULL;

    cxint science = -1;


    if (properties == NULL) {
        return -1;
    }


    /*
     * Set default values
     */

    if (type == NULL) {
        type = "REDUCED";
    }

    if (tech == NULL) {

        if (cpl_propertylist_has(properties, GIALIAS_DPRTECH) == TRUE) {

            const cxchar* s = cpl_propertylist_get_string(properties,
                                                          GIALIAS_DPRTECH);

            dpr_tech = cx_strdup(s);

        }
        else if (cpl_propertylist_has(properties, GIALIAS_PROTECH) == TRUE) {

            const cxchar* s = cpl_propertylist_get_string(properties,
                                                          GIALIAS_PROTECH);

            dpr_tech = cx_strdup(s);

        }

        if ((dpr_tech == NULL) || (dpr_tech[0] == '\0')) {
            tech = "UNDEFINED";
        }
        else {
            tech = dpr_tech;
        }

    }


    /*
     * Check whether a scientific observation was processed.
     */

    if (cpl_propertylist_has(properties, GIALIAS_PROSCIENCE) == FALSE) {

        if (cpl_propertylist_has(properties, GIALIAS_DPRCATG) == TRUE) {

            const cxchar* s = cpl_propertylist_get_string(properties,
                                                          GIALIAS_DPRCATG);

            if (s != NULL) {
                science = (strncmp(s, "SCIENCE", 7) == 0) ? 1 : 0;
            }

        }

    }


    /*
     * Clean frame properties
     */

    cpl_propertylist_erase(properties, GIALIAS_ORIGIN);
    cpl_propertylist_erase(properties, GIALIAS_DATE);
    cpl_propertylist_erase(properties, GIALIAS_DATAMD5);
    cpl_propertylist_erase(properties, GIALIAS_ORIGFILE);
    cpl_propertylist_erase(properties, GIALIAS_ARCFILE);
    cpl_propertylist_erase(properties, GIALIAS_CHECKSUM);
    cpl_propertylist_erase(properties, GIALIAS_DATASUM);

    cpl_propertylist_erase_regexp(properties, "ESO DPR.*", 0);


    giraffe_error_push();

    cpl_propertylist_update_string(properties, GIALIAS_INSTRUMENT,
                                   "GIRAFFE");
    cpl_propertylist_set_comment(properties, GIALIAS_INSTRUMENT,
                                 "Name of the Instrument.");

    cpl_propertylist_update_string(properties, GIALIAS_DATAMD5,
                                   "Not computed");
    cpl_propertylist_set_comment(properties, GIALIAS_DATAMD5,
                                 "MD5 checksum");

    if (name != NULL) {
        cpl_propertylist_update_string(properties, GIALIAS_PIPEFILE, name);
        cpl_propertylist_set_comment(properties, GIALIAS_PIPEFILE,
                                     "Filename of data product");
    }

    if (cpl_error_get_code() != CPL_ERROR_NONE) {

        cx_free(dpr_tech);
        return 1;

    }

    giraffe_error_pop();


    /*
     * Set product keywords
     */

    giraffe_error_push();

    cpl_propertylist_update_string(properties, GIALIAS_PRODID, PRODUCT_DID);
    cpl_propertylist_set_comment(properties, GIALIAS_PRODID,
                                 "Data dictionary for PRO");

    if (tag != NULL) {

        cpl_propertylist_update_string(properties, GIALIAS_PROCATG, tag);
        cpl_propertylist_set_comment(properties, GIALIAS_PROCATG,
                                     "Pipeline product category");

    }

    cpl_propertylist_update_string(properties, GIALIAS_PROTYPE, type);
    cpl_propertylist_set_comment(properties, GIALIAS_PROTYPE,
                                 "Product type");

    cpl_propertylist_update_string(properties, GIALIAS_PROTECH, tech);
    cpl_propertylist_set_comment(properties, GIALIAS_PROTECH,
                                 "Observation technique");
    cx_free(dpr_tech);
    dpr_tech = NULL;

    if (science != -1) {
        cpl_propertylist_update_bool(properties, GIALIAS_PROSCIENCE, science);
        cpl_propertylist_set_comment(properties, GIALIAS_PROSCIENCE,
                                     "Scientific product if T");
    }

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

    giraffe_error_pop();

    return 0;

}


/**
 * @brief
 *   Create a product frame using a provided frame creator.
 *
 * @param tag         The product tag assigned to the frame.
 * @param level       The product level of the product frame.
 * @param properties  A property list used as the product frame's properties.
 * @param object      The object to be saved as product frame.
 * @param data        Extra data passed to the frame creator.
 * @param creator     The frame creator.
 *
 * @return
 *   The function returns the newly created frame on success, or @c NULL
 *   if an error occurrs. In the latter case an appropriate error is also
 *   set.
 *
 * The function creates a product frame from a generic data object @em object
 * assigning the tag @em tag and the product level @em level to it. The
 * actual creation of the local file the created product frame points to
 * is delegated to a user defined function, which converts @em object
 * to a local FITS file. This function @em creator must have the prototype:
 * @code
 *     cxint foo(cxcptr object, cpl_propertylist *properties,
 *               const cxchar *filename, cxcptr data);
 * @endcode
 *
 * The argument @em filename passed to @em creator is generated from
 * @em tag by changing all capital letters to lower case letters and
 * appending the extension '.fits'
 */

cpl_frame *
giraffe_frame_create(const cxchar *tag, cpl_frame_level level,
                     const cpl_propertylist *properties, cxcptr object,
                     cxcptr data, GiFrameCreator creator)
{

    const cxchar *const fctid = "giraffe_frame_create";

    cxint status = 0;

    cx_string *name = NULL;

    cpl_propertylist *p = NULL;

    cpl_frame *frame = NULL;


    if (properties == NULL || creator == NULL) {
        cpl_error_set(fctid, CPL_ERROR_NULL_INPUT);
        return NULL;
    }

    if (tag == NULL) {
        cpl_error_set(fctid, CPL_ERROR_NULL_INPUT);
        return NULL;
    }

    p = cpl_propertylist_duplicate(properties);
    cx_assert(p != NULL);

    /*
     * Update the property list
     */

    name = cx_string_create(tag);
    cx_assert(name != NULL);

    cx_string_lower(name);
    cx_string_append(name, ".fits");

    _giraffe_frame_update_product(p, cx_string_get(name), tag, "REDUCED",
                                  NULL);

    frame = cpl_frame_new();
    cpl_frame_set_filename(frame, cx_string_get(name));
    cpl_frame_set_tag(frame, tag);
    cpl_frame_set_type(frame, CPL_FRAME_TYPE_IMAGE);
    cpl_frame_set_group(frame, CPL_FRAME_GROUP_PRODUCT);
    cpl_frame_set_level(frame, level);

    /*
     * Write object to file
     */

    status = creator(object, p, cx_string_get(name), data);

    if (status != 0) {
        cpl_frame_delete(frame);
        frame = NULL;
    }

    cx_string_delete(name);
    cpl_propertylist_delete(p);

    return frame;

}

/**
 * @brief
 *   Save a data object to a frame.
 *
 * @param frame       The frame the object is written to.
 * @param properties  The property list to be saved with the object.
 * @param object      The data object to save.
 * @param data        Extra data passed to the frame creator delegate.
 * @param creator     The creator function of the actual file.
 *
 * @return
 *   The function passes the return code of @em creator back to its
 *   caller. In case an error occurs in the function itself, the value
 *   @c 127 is returned.
 *
 * The function calls @em creator for the filename stored in the input
 * frame @em frame.
 */

cxint
giraffe_frame_save(const cpl_frame* frame,
                   const cpl_propertylist* properties,
                   cxcptr object, cxcptr data,
                   GiFrameCreator creator)
{

    const cxchar* const fctid = "giraffe_frame_save";

    cxint status = 0;

    cpl_propertylist* p = NULL;


    if (properties == NULL || creator == NULL) {
        cpl_error_set(fctid, CPL_ERROR_NULL_INPUT);
        return 127;
    }


    /*
     * Create a local copy of the property list, to guarantee that
     * the properties passed by the caller are not modified by the
     * creator function.
     */

    p = cpl_propertylist_duplicate(properties);
    cx_assert(p != NULL);


    /*
     * Write object to file
     */

    status = creator(object, p, cpl_frame_get_filename(frame), data);

    cpl_propertylist_delete(p);
    p = NULL;

    return status;

}


/**
 * @brief
 *   Create an image product frame.
 *
 * @param image   The image from which the frame is created.
 * @param tag     The product tag assigned to the image.
 * @param level   The product level of the image.
 * @param save    Output file creation flag.
 * @param update  Update image properties.
 *
 * @return
 *   The function returns the newly created frame on success, or @c NULL
 *   if an error occurrs. In the latter case an appropriate error is also
 *   set.
 *
 * The function creates a frame object from the input image @em image. The
 * frame's tag is set to @em tag and the frame's product level is set to
 * @em level. If the flag @em save is @c TRUE the image is written to disk,
 * otherwise only the frame is created and filled. The flag @em update
 * controls the update of certain image properties. The update is done
 * only if @em update is set to @c TRUE. If @em update and @em save are
 * both @c TRUE the update of the image properties occurrs before the
 * image is written to disk.
 */

cpl_frame *
giraffe_frame_create_image(GiImage *image, const cxchar *tag,
                           cpl_frame_level level, cxbool save,
                           cxbool update)
{
    const cxchar *fctid = "giraffe_frame_create_image";

    cx_string *name;

    cpl_frame *frame;

    cpl_propertylist *properties;


    if (image == NULL || tag == NULL) {
        cpl_error_set(fctid, CPL_ERROR_NULL_INPUT);
        return NULL;
    }

    properties = giraffe_image_get_properties(image);

    if (properties == NULL) {
        cpl_error_set(fctid, CPL_ERROR_DATA_NOT_FOUND);
        return NULL;
    }

    if (update == TRUE) {

        cpl_image *_image = giraffe_image_get(image);


        if (_image == NULL) {
            cpl_error_set(fctid, CPL_ERROR_DATA_NOT_FOUND);
            return NULL;
        }

        cpl_propertylist_update_double(properties, GIALIAS_DATAMIN,
                                       cpl_image_get_min(_image));
        cpl_propertylist_set_comment(properties, GIALIAS_DATAMIN,
                                     "Minimal pixel value");

        cpl_propertylist_update_double(properties, GIALIAS_DATAMAX,
                                       cpl_image_get_max(_image));
        cpl_propertylist_set_comment(properties, GIALIAS_DATAMAX,
                                     "Maximum pixel value");

        cpl_propertylist_update_double(properties, GIALIAS_DATAMEAN,
                                       cpl_image_get_mean(_image));
        cpl_propertylist_set_comment(properties, GIALIAS_DATAMEAN,
                                     "Mean of pixel values");

        cpl_propertylist_update_double(properties, GIALIAS_DATASIG,
                                       cpl_image_get_stdev(_image));
        cpl_propertylist_set_comment(properties, GIALIAS_DATASIG,
                                     "Standard deviation of pixel values");

        cpl_propertylist_update_double(properties, GIALIAS_DATAMEDI,
                                       cpl_image_get_median(_image));
        cpl_propertylist_set_comment(properties, GIALIAS_DATAMEDI,
                                     "Median of pixel values");

        cpl_propertylist_update_int(properties, GIALIAS_NAXIS1,
                                    cpl_image_get_size_x(_image));
        cpl_propertylist_update_int(properties, GIALIAS_NAXIS2,
                                    cpl_image_get_size_y(_image));

    }


    /*
     * Update the property list
     */

    name = cx_string_create(tag);
    cx_string_lower(name);
    cx_string_append(name, ".fits");

    _giraffe_frame_update_product(properties, cx_string_get(name), tag,
                                  "REDUCED", NULL);

    frame = cpl_frame_new();
    cpl_frame_set_filename(frame, cx_string_get(name));
    cpl_frame_set_tag(frame, tag);
    cpl_frame_set_type(frame, CPL_FRAME_TYPE_IMAGE);
    cpl_frame_set_group(frame, CPL_FRAME_GROUP_PRODUCT);
    cpl_frame_set_level(frame, level);

    cx_string_delete(name);


    /*
     * Write image to disk
     */

    if (save == TRUE) {

        cxint status = 0;

        status = giraffe_image_save(image, cpl_frame_get_filename(frame));

        if (status) {
            cpl_error_set(fctid, CPL_ERROR_FILE_IO);
            cpl_frame_delete(frame);

            return NULL;
        }

    }

    return frame;

}


/**
 * @brief
 *   Create a table product frame.
 *
 * @param table   The table from which the frame is created.
 * @param tag     The product tag assigned to the table.
 * @param level   The product level of the table.
 * @param save    Output file creation flag.
 * @param update  Update table properties.
 *
 * @return
 *   The function returns the newly created frame on success, or @c NULL
 *   if an error occurrs. In the latter case an appropriate error is also
 *   set.
 *
 * The function creates a frame object from the input table @em table. The
 * frame's tag is set to @em tag and the frame's product level is set to
 * @em level. If the flag @em save is @c TRUE the table is written to disk,
 * otherwise only the frame is created and filled. The flag @em update
 * controls the update of certain table properties. The update is done
 * only if @em update is set to @c TRUE. If @em update and @em save are
 * both @c TRUE the update of the table properties occurs before the
 * table is written to disk.
 */

cpl_frame *
giraffe_frame_create_table(GiTable *table, const cxchar *tag,
                           cpl_frame_level level, cxbool save,
                           cxbool update)
{

    const cxchar *fctid = "giraffe_frame_create_table";

    cx_string *name;

    cpl_frame *frame;

    cpl_propertylist *properties;


    if (table == NULL || tag == NULL) {
        cpl_error_set(fctid, CPL_ERROR_NULL_INPUT);
        return NULL;
    }

    properties = giraffe_table_get_properties(table);

    if (properties == NULL) {
        cpl_error_set(fctid, CPL_ERROR_DATA_NOT_FOUND);
        return NULL;
    }

    if (update == TRUE) {

        cpl_propertylist_update_string(properties, GIALIAS_EXTNAME, tag);
        cpl_propertylist_set_comment(properties, GIALIAS_EXTNAME,
                                     "FITS Extension name");

    }


    /*
     * Update the property list
     */

    name = cx_string_create(tag);
    cx_string_lower(name);
    cx_string_append(name, ".fits");

    _giraffe_frame_update_product(properties, cx_string_get(name), tag,
                                  "REDUCED", NULL);

    frame = cpl_frame_new();
    cpl_frame_set_filename(frame, cx_string_get(name));
    cpl_frame_set_tag(frame, tag);
    cpl_frame_set_type(frame, CPL_FRAME_TYPE_TABLE);
    cpl_frame_set_group(frame, CPL_FRAME_GROUP_PRODUCT);
    cpl_frame_set_level(frame, level);

    cx_string_delete(name);


    /*
     * Write table to disk
     */

    if (save == TRUE) {

        cxint status = 0;

        status = giraffe_table_save(table, cpl_frame_get_filename(frame));

        if (status) {
            cpl_error_set(fctid, CPL_ERROR_FILE_IO);
            cpl_frame_delete(frame);

            return NULL;
        }

    }

    return frame;

}


/**
 * @brief
 *   Attach a table to a product frame.
 *
 * @param frame   The frame to which the table is attached.
 * @param table   The table to attach.
 * @param tag     The product tag assigned to the table.
 * @param update  Update table properties.
 *
 * @return The function returns 0 on success, or a non-zero number in case
 *   an error occurred. In the latter case an appropriate error is also
 *   set.
 *
 * The function attaches (appends) the table @em table to the frame @em frame.
 * This requires that the frame @em frame contains the name of an already
 * existing file on disk to which the table can be attached. The tag @em tag
 * is used as the name of the attached data set. The flag @em update
 * controls the update of certain table properties. The update is done
 * only if @em update is set to @c TRUE.
 */

cxint
giraffe_frame_attach_table(cpl_frame *frame, GiTable *table,
                           const cxchar *tag, cxbool update)
{

    const cxchar *fctid = "giraffe_frame_attach_table";


    cxchar *name;

    cxint status = 0;

    cpl_propertylist *properties;

    cpl_table *data;



    if (frame == NULL || table == NULL || tag == NULL) {
        cpl_error_set(fctid, CPL_ERROR_NULL_INPUT);
        return 1;
    }

    name = (cxchar *)cpl_frame_get_filename(frame);

    if (name == NULL) {
        cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
        return 1;
    }

    properties = giraffe_table_get_properties(table);

    if (properties == NULL) {
        cpl_error_set(fctid, CPL_ERROR_DATA_NOT_FOUND);
        return 1;
    }

    data = giraffe_table_get(table);

    if (data == NULL) {
        cpl_error_set(fctid, CPL_ERROR_DATA_NOT_FOUND);
        return 1;
    }


    if (update == TRUE) {

        /* For future extensions */
        ;

    }


    /*
     * Update the property list
     */

    cpl_propertylist_update_string(properties, GIALIAS_EXTNAME, tag);
    cpl_propertylist_set_comment(properties, GIALIAS_EXTNAME,
                                 "FITS Extension name");


    /*
     * Write table to disk
     */

    status = cpl_table_save(data, NULL, properties, name, CPL_IO_EXTEND);

    if (status != CPL_ERROR_NONE) {
        cpl_error_set(fctid, CPL_ERROR_FILE_IO);
        return 1;
    }

    return 0;

}


/**
 * @brief
 *   Get a frame from a frame set.
 *
 * @param set    The frame set to query.
 * @param tag    The frame tag to look for.
 * @param group  The frame group to look for.
 *
 * @return
 *   A handle for the product frame, or @c NULL if no such product
 *   was found.
 *
 * The function searches the frame set @em set for the first frame with
 * the tag @em tag in the group @em group.
 */

cpl_frame *
giraffe_get_frame(const cpl_frameset *set, const cxchar *tag,
                  cpl_frame_group group)
{

    cpl_frameset* _set = (cpl_frameset*)set;

    cpl_frame *frame = NULL;


    if (_set == NULL || tag == NULL) {
        return NULL;
    }

    frame = cpl_frameset_find(_set, tag);

    if (group != CPL_FRAME_GROUP_NONE) {

        while (frame != NULL && cpl_frame_get_group(frame) != group) {
            frame = cpl_frameset_find(_set, NULL);
        }

    }

    return frame;

}


/**
 * @brief
 *   Get the slit geometry frame from a frame set.
 *
 * @param set  The frame set to be queried.
 *
 * @return
 *   The function returns a handle for the slit geometry frame, or @c NULL
 *   if no slit geometry frame was found or an error occurred.
 *
 * The function searches the frame set @em set for a slit geometry frame.
 * It first looks for a setup specific slitgeometry and then for a slit
 * geometry master. The slitgeometry frames are identified by the tags
 * @c GIFRAME_SLITSETUP and @c GIFRAME_SLITMASTER respectively. If none
 * of the two is found the tag @em GIFRAME_SLITGEOMETRY is tried as a
 * last resort.
 */

cpl_frame *
giraffe_get_slitgeometry(const cpl_frameset *set)
{

    cpl_frameset* _set = (cpl_frameset*)set;

    cpl_frame *frame = NULL;


    if (_set == 0) {
        return 0;
    }

    frame = cpl_frameset_find(_set, GIFRAME_SLITSETUP);
    frame = frame == 0 ? cpl_frameset_find(_set, GIFRAME_SLITMASTER) : frame;
    frame = frame == 0 ? cpl_frameset_find(_set, GIFRAME_SLITGEOMETRY) : frame;

    return frame;

}
/**@}*/
