/*
 * 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 <cxmemory.h>
#include <cxmessages.h>

#include <cpl_type.h>
#include <cpl_image.h>
#include <cpl_propertylist.h>
#include <cpl_error.h>

#include "gialias.h"
#include "giimage.h"


/**
 * @defgroup giimage Giraffe Images
 *
 * The module implements an Giraffe image type which inherits the cpl_image
 * type and extends it by a property list to allow for storing image meta
 * data together with its associated image. A pointer to a Giraffe image
 * can be cast into a pointer to a cpl_image.

 */

/**@{*/

struct GiImage {
    cpl_image *pixels;
    cpl_propertylist *properties;
    cpl_type type;
};


/**
 * @brief
 *   Creates an empty image container.
 *
 * @return Pointer to the newly created image container.
 *
 * The function allocates memory for an empty image container.
 */

GiImage *
giraffe_image_new(cpl_type type)
{

    GiImage *self = cx_calloc(1, sizeof *self);

    self->pixels = NULL;
    self->properties = NULL;
    self->type = type;

    return self;

}


/**
 * @brief
 *   Creates an image container of a given type.
 *
 * @param nx    Dimension in X direction
 * @param ny    Dimension in Y direction
 * @param type  Pixel type
 *
 * @return The newly created image.
 *
 * The function creates a new image container and an allocates memory for an
 * image of the given dimensions @em nx, @em ny. The allocated image is
 * suitable for pixels of type @em type. The created property list is empty.
 */

GiImage *
giraffe_image_create(cpl_type type, cxint nx, cxint ny)
{

    GiImage *self = giraffe_image_new(type);

    if (self) {

        self->pixels = cpl_image_new(nx, ny, self->type);

        if (self->pixels == NULL) {
            giraffe_image_delete(self);
            self = NULL;
        }
        else {

            self->properties = cpl_propertylist_new();

            if (self->properties == NULL) {
                giraffe_image_delete(self);
                self = NULL;
            }

        }

    }

    return self;

}


/**
 * @brief
 *   Creates a copy of an image.
 *
 * @param self  The image to copy.
 *
 * @return Pointer to the newly allocated image.
 *
 * The function creates a deep copy of the source image @em image, i.e. the
 * image data and all properties are copied.
 */

GiImage *
giraffe_image_duplicate(const GiImage *self)
{

    GiImage *clone = NULL;


    if (self) {

        clone = giraffe_image_new(self->type);

        if (clone != NULL) {

            if (self->pixels != NULL) {
                cx_assert(self->type == cpl_image_get_type(self->pixels));
                clone->pixels = cpl_image_duplicate(self->pixels);
            }

            if (self->properties != NULL) {
                clone->properties =
                    cpl_propertylist_duplicate(self->properties);
            }

        }

    }

    return clone;

}


/**
 * @brief
 *   Destroys an image.
 *
 * @param self  The image to destroy.
 *
 * The function deallocates the memory used for the image data and all
 * image properties.
 */

void
giraffe_image_delete(GiImage *self)
{

    if (self != NULL) {

        if (self->pixels != NULL) {
            cpl_image_delete(self->pixels);
            self->pixels = NULL;
        }

        if (self->properties != NULL) {
            cpl_propertylist_delete(self->properties);
            self->properties = NULL;
        }

        cx_free(self);

    }

    return;

}


/**
 * @brief
 *   Gets the image data.
 *
 * @param self  Image to query.
 *
 * @return Pointer to the container's image data.
 *
 * The function returns a reference pointer to the actual image stored in the
 * image container @em self.
 */

cpl_image *
giraffe_image_get(const GiImage *self)
{

    if (self == NULL) {
        return NULL;
    }

    return self->pixels;

}

/**
 * @brief
 *   Sets the image data.
 *
 * @param self   The image to store the data.
 * @param image  The source image the data is taken from.
 *
 * @return The function returns 0 on success, or 1 in case of errors.
 *
 * The function copies the pixel data of @em image into @em self. The
 * pixel type of the source and the target image must match. If the type
 * do not match the function returns an error.
 */

cxint
giraffe_image_set(GiImage *self, cpl_image *image)
{

    cx_assert(self != NULL);

    if (image == NULL) {
        return 1;
    }

    if (self->type != cpl_image_get_type(image)) {
        return 1;
    }

    if (self->pixels != NULL) {
        cpl_image_delete(self->pixels);
        self->pixels = NULL;
    }

    self->pixels = cpl_image_duplicate(image);

    return self->pixels ? 0 : 1;

}


/**
 * @brief
 *   Get the properties of an image.
 *
 * @param self  Image to query.
 *
 * @return Pointer to the image properties stored in the container.
 *
 * The function returns a reference pointer to the properties, i.e. the
 * keyword list stored in the image container @em self.
 */

cpl_propertylist *
giraffe_image_get_properties(const GiImage *self)
{

    if (self == NULL) {
        return NULL;
    }

    return self->properties;

}


/**
 * @brief
 *   Attaches a property list to an image.
 *
 * @param self        Image the properties will be attached to.
 * @param properties  Property list to attach.
 *
 * @return The function returns 0 on success, or 1 otherwise.
 *
 * The function stores a copy of the property list @em properties
 * in the image container @em self.
 *
 * If there is already a property list stored in the container @em self, it is
 * replaced with the reference @em properties. If the properties cannot be
 * copied, the function returns an error.
 */

cxint
giraffe_image_set_properties(GiImage *self, cpl_propertylist *properties)
 {

    if (self == NULL) {
        return 1;
    }

    if (self->properties) {
        cpl_propertylist_delete(self->properties);
        self->properties = NULL;
    }

    self->properties = cpl_propertylist_duplicate(properties);

    return self->properties ? 0 : 1;

}


/**
 * @brief
 *   Copies matrix elements into an image.
 *
 * @param self    The image to which the matrix elements are copied.
 * @param matrix  The matrix whose elements will be copied.
 *
 * @return The function returns 0 on success, or 1 otherwise.
 *
 * The function copies the matrix elements of the source matrix @em matrix
 * into the pixel buffer of the image @em image.
 */

cxint
giraffe_image_copy_matrix(GiImage *self, cpl_matrix *matrix)
{

    const cxchar *const fctid = "giraffe_image_copy_matrix";

    cxint nrow = 0;
    cxint ncol = 0;

    cxdouble *elements = NULL;


    cx_assert(self != NULL);

    if (matrix == NULL) {
        return 1;
    }

    nrow = cpl_matrix_get_nrow(matrix);
    ncol = cpl_matrix_get_ncol(matrix);
    cx_assert(nrow > 0 && ncol > 0);

    elements = cpl_matrix_get_data(matrix);
    cx_assert(elements != NULL);

    if (self->pixels != NULL) {
        if (cpl_image_get_size_x(self->pixels) != ncol ||
            cpl_image_get_size_y(self->pixels) != nrow) {
                cpl_image_delete(self->pixels);
                self->pixels = cpl_image_new(ncol, nrow, self->type);
            }
    }
    else {
        self->pixels = cpl_image_new(ncol, nrow, self->type);
    }

    switch (self->type) {
    case CPL_TYPE_INT:
    {
        cxsize i;
        cxsize sz = nrow * ncol;

        cxint *pixels = cpl_image_get_data_int(self->pixels);


        for (i = 0; i < sz; i++) {
            pixels[i] = (cxint) elements[i];
        }
        break;
    }

    case CPL_TYPE_FLOAT:
    {
        cxsize i;
        cxsize sz = nrow * ncol;

        cxfloat *pixels = cpl_image_get_data_float(self->pixels);


        for (i = 0; i < sz; i++) {
            pixels[i] = (cxfloat) elements[i];
        }
        break;
    }

    case CPL_TYPE_DOUBLE:
    {
        cxsize sz = nrow * ncol * sizeof(cxdouble);

        cxptr pixels = cpl_image_get_data(self->pixels);


        memcpy(pixels, elements, sz);
        break;
    }

    default:
        cpl_error_set(fctid, CPL_ERROR_INVALID_TYPE);
        return 1;
        break;
    }

    return 0;

}


/**
 * @brief
 *   Gets image data from a file.
 *
 * @param self      Image into which the data is read.
 * @param filename  File from which the pixel data is read.
 * @param position  Image index within the file.
 * @param plane     Plane index within a data cube.
 *
 * @return The function returns 0 on success or 1 in case the pixel data
 *   could not be loaded.
 *
 * The function reads the pixel data for image number @em index from the
 * file @em filename into the target image @em self. The pixel data type is
 * converted to the target type if necessary. If the pixel data at position
 * @em index in the file form a data cube, the function loads the plane
 * with the index @em plane, otherwise @em plane is ignored.
 */

cxint
giraffe_image_load_pixels(GiImage *self, const cxchar *filename,
                          cxint position, cxint plane)
{

    cx_assert(self != NULL);

    if (self->pixels != NULL) {
        cpl_image_delete(self->pixels);
        self->pixels = NULL;
    }

    self->pixels = cpl_image_load(filename, self->type, plane, position);

    return self->pixels ? 0 : 1;

}


/**
 * @brief
 *   Gets image properties from a file.
 *
 * @param self      Giraffe image
 * @param filename  File from which the properties are read
 * @param position  Image index within the file
 *
 * @return The function returns 0 on success or 1 in case the image
 *   properties could not be loaded.
 *
 * The function reads the image properties for the image at position
 * @em position within the file @em filename and stores them in @em self.
 *
 * If there are already properties associated to an image they will be
 * replaced by calling this function.
 */

cxint
giraffe_image_load_properties(GiImage *self, const cxchar *filename,
                              cxint position)
{

    cx_assert(self != NULL);

    if (self->properties) {
        cpl_propertylist_delete(self->properties);
        self->properties = NULL;
    }

    /*
     * Strip comments during load
     */

    self->properties = cpl_propertylist_load_regexp(filename, position,
                                                    "^COMMENT$", TRUE);

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

    return 0;

}


/**
 * @brief
 *   Gets image data and properties from a file.
 *
 * @param self      Giraffe image.
 * @param filename  File from which the pixel data and properties are read.
 * @param position  Image index within the file
 *
 * @return The function returns 0 on success or 1 in case the pixel data
 *   or the image properties could not be loaded.
 *
 * The function reads the pixel data and the properties for image number
 * @em position from the file @em filename into the target image @em self.
 *
 * In case the file contains a data cube at position @em position the plane
 * number 0 is loaded.
 *
 * @see giraffe_image_load_pixels, giraffe_image_load_properties
 */

cxint
giraffe_image_load(GiImage *self, const cxchar *filename, cxint position)
{

    cx_assert(self != NULL);

    if (giraffe_image_load_pixels(self, filename, position, 0) != 0) {
        return 1;
    }

    if (giraffe_image_load_properties(self, filename, position) != 0) {
        return 1;
    }

    return 0;

}


/**
 * @brief
 *   Write a Giraffe image to a file.
 *
 * @param  self      Giraffe image to write.
 * @param  filename  File to which @em self is written.
 *
 * @return The function returns 0 on success and 1 otherwise.
 *
 * The Giraffe image @em self is written to the file @em filename. Currently
 * a Giraffe image can only be written as a FITS file with the image data
 * in the primary data unit. The image properties are written/converted to
 * the primary FITS header.
 */

cxint
giraffe_image_save(GiImage *self, const cxchar *filename)
{

    const cxchar *fctid = "giraffe_image_save";


    if (filename == NULL) {
        return 1;
    }

    if (self) {

        cxint code;
        cxint bits_per_pixel;


        switch (self->type) {
            case CPL_TYPE_INT:
                bits_per_pixel = CPL_BPP_32_SIGNED;
                break;

            case CPL_TYPE_FLOAT:
                bits_per_pixel = CPL_BPP_IEEE_FLOAT;
                break;

            case CPL_TYPE_DOUBLE:

                /* CPL_TYPE_DOUBLE images are saved as float */

                bits_per_pixel = CPL_BPP_IEEE_FLOAT;
                break;

            default:

                /*
                 * If self was properly created using the constructors we
                 * should never reach this point.
                 */

                cpl_error_set(fctid, CPL_ERROR_INVALID_TYPE);
                return 1;
                break;
        }

        code = cpl_image_save(self->pixels, filename, bits_per_pixel,
                              self->properties, CPL_IO_DEFAULT);

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

    return 0;

}


/**
 * @brief
 *   Paste an image into another at a given position.
 *
 * @param self   The destination image.
 * @param image  The source image.
 * @param x      Pixel offset along the x-axis.
 * @param y      Pixel offset along the y-axis.
 * @param clip   Flag controlling the image clipping.
 *
 * @return The function returns 0 on success or a non-zero value if an
 *   error occurred.
 *
 * The function pastes the image @em image into the destination image
 * @em self at the pixel position given by the coordinates @em x and
 * @em y. The coordinates @em x and @em y indicate the position of the
 * lower left pixel of @em image, starting from (0, 0). If @em clip is
 * set to @c true, the image @em image is properly clipped, if parts of
 * @em image would fall outside of the destination image because of the
 * given coordinates @em x and @em y. If @em clip is @c false and the
 * source image does not fit entirely into the destination image the
 * function fails and the error code @c CPL_ERROR_ACCESS_OUT_RANGE
 * is set.
 */

cxint
giraffe_image_paste(GiImage *self, const GiImage *image, cxint x, cxint y,
                    cxbool clip)
{

    const cxchar *fctid = "giraffe_image_paste";


    cx_assert(self != NULL);

    if (x < 0 || y < 0) {
        cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
        return -1;
    }

    if (image != NULL) {

        cpl_image *_self = giraffe_image_get(self);
        cpl_image *_image = giraffe_image_get((GiImage *) image);

        cxint i;
        cxint nx = cpl_image_get_size_x(_self);
        cxint ny = cpl_image_get_size_y(_self);
        cxint sx = cpl_image_get_size_x(_image);
        cxint sy = cpl_image_get_size_y(_image);
        cxint ys = y * nx;

        cxptr _spixel = cpl_image_get_data(_self);
        cxptr _ipixel = cpl_image_get_data(_image);

        cpl_type type = cpl_image_get_type(_self);


        if (type != cpl_image_get_type(_image)) {
            cpl_error_set(fctid, CPL_ERROR_TYPE_MISMATCH);
            return -4;
        }

        if ((x + sx) > nx) {
            if (clip == FALSE) {
                cpl_error_set(fctid, CPL_ERROR_ACCESS_OUT_OF_RANGE);
                return -2;
            }

            sx -= nx - x;
        }

        if ((y + sy) > ny) {
            if (clip == FALSE) {
                cpl_error_set(fctid, CPL_ERROR_ACCESS_OUT_OF_RANGE);
                return -3;
            }

            sy -= ny - y;
        }

        for (i = 0; i < sy; i++) {

            cxint bytes = cpl_type_get_sizeof(type);
            cxint soffset = (ys + nx * i + x) * bytes;
            cxint ioffset = (sx * i) * bytes;
            cxint sz = sx * bytes;

            memcpy((cxchar*)_spixel + soffset,
                   (cxchar*)_ipixel + ioffset, sz);

        }

    }

    return 0;

}


/**
 * @brief
 *   Prints status information about a Giraffe image.
 *
 * @param self  Giraffe image
 *
 * @return Nothing.
 *
 * The function prints internal status information abaout the Giraffe image
 * @em self to the standard output. This function is provided for
 * debugging purposes.
 */

void
giraffe_image_print(GiImage *self)
{

    if (self) {
        cx_print("Resources for Giraffe image at %p:", self);
        cx_print("  properties at %p", self->properties);
        cx_print("    list size: %" CPL_SIZE_FORMAT "\n",
                 cpl_propertylist_get_size(self->properties));
        cx_print("  pixels at %p:", cpl_image_get_data(self->pixels));
        cx_print("    type: %02x", cpl_image_get_type(self->pixels));
        cx_print("    x-size: %" CPL_SIZE_FORMAT,
                 cpl_image_get_size_x(self->pixels));
        cx_print("    y-size: %" CPL_SIZE_FORMAT "\n",
                 cpl_image_get_size_y(self->pixels));
    }
   else {
        cx_print("Invalid input image at %p", self);
    }

    return;

}


/**
 * @brief
 *   Add additional frame information to an image.
 *
 * TBD
 */

cxint
giraffe_image_add_info(GiImage *image, const GiRecipeInfo *info ,
                       const cpl_frameset *set)
{

    cxint status = 0;

    cpl_propertylist *properties = NULL;


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

    properties = giraffe_image_get_properties(image);

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

    if (info != NULL) {
        status = giraffe_add_recipe_info(properties, info);

        if (status != 0) {
            return -3;
        }

        if (set != NULL) {
            status = giraffe_add_frameset_info(properties, set,
                                               info->sequence);

            if (status != 0) {
                return -4;
            }
        }
    }

    return 0;

}
/**@}*/
