/*
 * 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 <string.h>

#include <cxtypes.h>
#include <cxmemory.h>
#include <cxmessages.h>
#include <cxstrutils.h>
#include <cxmap.h>

#include <cpl_type.h>

#include "gialias.h"
#include "gierror.h"
#include "gipsfdata.h"


/*
 * @defgroup gipsfdata PSF Profile Data Storage
 *
 * TBD
 */

/**@{*/

struct GiPsfData {

    const cxchar* model;

    cxint nfibers;
    cxint nbins;
    cxint width;
    cxint height;

    cpl_image* bins;
    cx_map* values;

};


inline static cxbool
_giraffe_psfdata_compare(cxcptr s, cxcptr t)
{

    return strcmp(s, t) < 0 ? TRUE : FALSE;

}


inline static void
_giraffe_psfdata_clear(GiPsfData* self)
{

    if (self->model != NULL) {
        cx_free((cxptr)self->model);
        self->model = NULL;
    }

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

    if (self->values != NULL) {
        cx_map_clear(self->values);
    }

    self->nfibers = 0;
    self->nbins = 0;
    self->width = 0;
    self->height = 0;

    return;

}


inline static void
_giraffe_psfdata_resize(GiPsfData* self, cxint nfibers, cxint nbins,
                        cxint width, cxint height)
{

    cx_assert(self->values != NULL);

    self->nfibers = nfibers;
    self->nbins = nbins;
    self->width = width;
    self->height = height;

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

    if (cx_map_empty(self->values) == FALSE) {
        cx_map_clear(self->values);
        cx_assert(cx_map_empty(self->values));
    }

    return;

}


inline static cxint
_giraffe_psfdata_assign(GiPsfData* self, cx_map* map, const cxchar* name,
                         const cpl_image* values)
{

    cx_map_iterator position = cx_map_find(map, name);


    if (cpl_image_get_size_x(values) != self->nfibers) {
        return 1;
    }

    if (cpl_image_get_size_y(values) != self->nbins) {
        return 2;
    }

    if (position == cx_map_end(map)) {
        cx_map_insert(map, cx_strdup(name), values);
    }
    else {

        cpl_image* previous = cx_map_assign(map, position, values);

        if (previous != NULL) {
            cpl_image_delete(previous);
            previous = NULL;
        }

    }

    return 0;

}


inline static cxint
_giraffe_psfdata_set(GiPsfData* self, cx_map* map, const cxchar* name,
                      cxint i, cxint j, cxdouble value)
{

    cxdouble* data = NULL;

    cx_map_const_iterator position = cx_map_find(map, name);


    if (position == cx_map_end(map)) {

        cpl_image* buffer = cpl_image_new(self->nfibers, self->nbins,
                                          CPL_TYPE_DOUBLE);
        cx_map_insert(map, cx_strdup(name), buffer);

        data = cpl_image_get_data(buffer);

    }
    else {

        data = cpl_image_get_data(cx_map_get_value(map, position));

    }

    data[self->nfibers * j + i] = value;

    return 0;

}


inline static cxint
_giraffe_psfdata_get(const GiPsfData* self, const cx_map* map,
                     const cxchar* name, cxint i, cxint j, cxdouble* value)
{

    cxdouble* data = NULL;

    cx_map_const_iterator position = cx_map_find(map, name);

    if (position == cx_map_end(map)) {
        return 1;
    }

    data = cpl_image_get_data(cx_map_get_value(map, position));
    *value = data[self->nfibers * j + i];

    return 0;

}


GiPsfData*
giraffe_psfdata_new(void)
{

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

    self->nfibers = 0;
    self->nbins = 0;
    self->width = 0;
    self->height = 0;

    self->model = NULL;

    self->bins = NULL;
    self->values = cx_map_new(_giraffe_psfdata_compare, cx_free,
                              (cx_free_func)cpl_image_delete);
    cx_assert(cx_map_empty(self->values));

    return self;

}


GiPsfData*
giraffe_psfdata_create(cxint nfibers, cxint nbins, cxint width, cxint height)
{

    GiPsfData* self = giraffe_psfdata_new();

    self->nfibers = nfibers;
    self->nbins = nbins;
    self->width = width;
    self->height = height;

    self->model = NULL;

    self->bins = cpl_image_new(self->nfibers, self->nbins, CPL_TYPE_DOUBLE);

    return self;

}


void
giraffe_psfdata_delete(GiPsfData* self)
{

    if (self != NULL) {

        if (self->model != NULL) {
            cx_free((cxptr)self->model);
            self->model = NULL;
        }

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

        if (self->values != NULL) {
            cx_map_delete(self->values);
            self->values = NULL;
        }

        cx_free(self);

    }

    return;

}


void
giraffe_psfdata_clear(GiPsfData* self)
{
    _giraffe_psfdata_clear(self);
    return;
}


void
giraffe_psfdata_resize(GiPsfData* self, cxint nfibers, cxint nbins,
                       cxint width, cxint height)
{

    cx_assert(self != NULL);

    _giraffe_psfdata_resize(self, nfibers, nbins, width, height);
    self->bins = cpl_image_new(self->nfibers, self->nbins, CPL_TYPE_DOUBLE);

    return;

}


cxsize
giraffe_psfdata_fibers(const GiPsfData* self)
{

    cx_assert(self != NULL);
    return self->nfibers;

}


cxsize
giraffe_psfdata_bins(const GiPsfData* self)
{

    cx_assert(self != NULL);
    return self->nbins;

}


cxsize
giraffe_psfdata_xsize(const GiPsfData* self)
{

    cx_assert(self != NULL);
    return self->width;

}


cxsize
giraffe_psfdata_ysize(const GiPsfData* self)
{

    cx_assert(self != NULL);
    return self->height;

}


cxsize
giraffe_psfdata_parameters(const GiPsfData* self)
{

    cx_assert(self != NULL);
    return cx_map_size(self->values);

}


cxbool
giraffe_psfdata_contains(const GiPsfData* self, const cxchar* name)
{

    cx_map_const_iterator position;


    cx_assert(self != NULL);

    if (name == NULL) {
        return FALSE;
    }

    position = cx_map_find(self->values, name);

    if (position == cx_map_end(self->values)) {
        return FALSE;
    }

    return TRUE;

}


const cxchar*
giraffe_psfdata_get_name(const GiPsfData* self, cxsize position)
{

    const cxchar* name = NULL;


    cx_assert(self != NULL);

    if (position < cx_map_size(self->values)) {

        cxsize i = 0;

        cx_map_const_iterator pos = cx_map_begin(self->values);


        while (i < position) {
            pos = cx_map_next(self->values, pos);
            ++i;
        }

        name = cx_map_get_key(self->values, pos);

    }

    return name;

}


cxint
giraffe_psfdata_set_model(GiPsfData* self, const cxchar* name)
{

    cx_assert(self != NULL);

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

    if (self->model != NULL) {
        cx_free((cxptr)self->model);
        self->model = NULL;
    }

    self->model = cx_strdup(name);

    return 0;

}


const cxchar*
giraffe_psfdata_get_model(const GiPsfData* self)
{

    cx_assert(self != NULL);
    return self->model;

}


cxint
giraffe_psfdata_set_bin(GiPsfData* self, cxint fiber, cxint bin,
                        cxdouble position)
{

    cxdouble* data = NULL;


    cx_assert(self != NULL);

    if ((fiber < 0) || (fiber >= self->nfibers) ||
        (bin < 0) || (bin >= self->nbins)) {
        return 1;
    }
    else {

        if (self->bins == NULL) {
            self->bins = cpl_image_new(self->nfibers, self->nbins,
                                       CPL_TYPE_DOUBLE);
        }

        data = cpl_image_get_data_double(self->bins);
        data[self->nfibers * bin + fiber] = position;

    }

    return 0;

}


cxdouble
giraffe_psfdata_get_bin(const GiPsfData* self, cxint fiber, cxint bin)
{

    const cxchar* const fctid = "giraffe_psfdata_get_bin";

    cxdouble* data = NULL;


    cx_assert(self != NULL);

    if ((fiber < 0) || (fiber >= self->nfibers) ||
        (bin < 0) || (bin >= self->nbins)) {
        cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
        return 0.;
    }

    if (self->bins == NULL) {

        GiPsfData* _self = (GiPsfData*) self;

        _self->bins = cpl_image_new(self->nfibers, self->nbins,
                                    CPL_TYPE_DOUBLE);

    }

    data = cpl_image_get_data_double(self->bins);

    return data[self->nfibers * bin + fiber];

}


const cpl_image*
giraffe_psfdata_get_bins(const GiPsfData* self)
{
    cx_assert(self != NULL);
    return self->bins;
}


cxint
giraffe_psfdata_set(GiPsfData* self, const cxchar* name, cxint fiber,
                    cxint bin, cxdouble value)
{

    cxint status = 0;

    cx_assert(self != NULL);

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

    if (fiber >= self->nfibers) {
        return 1;
    }

    if (bin >= self->nbins) {
        return 1;
    }

    status = _giraffe_psfdata_set(self, self->values, name, fiber, bin,
                                  value);

    if (status != 0) {
        return 1;
    }

    return 0;

}


cxdouble
giraffe_psfdata_get(const GiPsfData* self, const cxchar* name, cxint fiber,
                    cxint bin)
{

    const cxchar* const fctid = "giraffe_psfdata_get";

    cxint status = 0;

    cxdouble value = 0.;


    cx_assert(self != NULL);

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

    if (fiber >= self->nfibers) {
        return 1;
    }

    if (bin >= self->nbins) {
        return 1;
    }

    status = _giraffe_psfdata_get(self, self->values, name, fiber, bin,
                                  &value);

    if (status != 0) {
        cpl_error_set(fctid, CPL_ERROR_DATA_NOT_FOUND);
        return 0.;
    }

    return value;

}


cxint
giraffe_psfdata_set_data(GiPsfData* self, const cxchar* name,
                         const cpl_image* data)
{

    cxint status = 0;


    cx_assert(self != NULL);

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

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

    status = _giraffe_psfdata_assign(self, self->values, name, data);

    if (status != 0) {
        return 1;
    }

    return 0;

}


const cpl_image*
giraffe_psfdata_get_data(const GiPsfData* self, const cxchar* name)
{

    cx_assert(self != NULL);

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

    return cx_map_get(self->values, name);

}


cxint giraffe_psfdata_load(GiPsfData* self, const cxchar* filename)
{

    const cxchar* model = NULL;

    cxint nfibers = 0;
    cxint nbins = 0;
    cxint nx = 0;
    cxint ny = 0;
    cxint nparameters = 0;

    cxsize i = 0;
    cxsize extension = 1;

    cpl_propertylist* p = NULL;


    if (self == NULL || filename == NULL) {
        return -1;
    }

    giraffe_error_push();

    p = cpl_propertylist_load(filename, 0);

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

    if (cpl_propertylist_has(p, GIALIAS_PSFMODEL) == 0) {
        return 1;
    }
    else {
        model = cpl_propertylist_get_string(p, GIALIAS_PSFMODEL);
    }

    if (cpl_propertylist_has(p, GIALIAS_PSFNS) == 0) {
        return 1;
    }
    else {
        nfibers = cpl_propertylist_get_int(p, GIALIAS_PSFNS);
    }

    if (cpl_propertylist_has(p, GIALIAS_PSFXBINS) == 0) {
        return 1;
    }
    else {
        nbins = cpl_propertylist_get_int(p, GIALIAS_PSFXBINS);
    }

    if (cpl_propertylist_has(p, GIALIAS_PSFPRMS) == 0) {
        return 1;
    }
    else {
        nparameters = cpl_propertylist_get_int(p, GIALIAS_PSFPRMS);
    }

    if (cpl_propertylist_has(p, GIALIAS_PSFNX) == 0) {
        return 1;
    }
    else {
        nx = cpl_propertylist_get_int(p, GIALIAS_PSFNX);
    }

    if (cpl_propertylist_has(p, GIALIAS_PSFNY) == 0) {
        return 1;
    }
    else {
        ny = cpl_propertylist_get_int(p, GIALIAS_PSFNY);
    }

    if (cpl_error_get_code() != CPL_ERROR_NONE) {

        if (p != NULL) {
            cpl_propertylist_delete(p);
            p = NULL;
        }

        return 1;

    }

    giraffe_error_pop();


    /*
     * Prepare the psf data object. The model must be set before
     * the property list is deallocated, since a reference is used!
     *
     * Note that the x-axis corresponds to the fibers, while the y-axis
     * corresponds to the bins. Stick to OGL terminology.
     */

    giraffe_psfdata_set_model(self, model);
    _giraffe_psfdata_resize(self, nfibers, nbins, ny, nx);

    cpl_propertylist_delete(p);
    p = NULL;


    /*
     * Load bin positions from the first extension.
     */

    self->bins = cpl_image_load(filename, CPL_TYPE_DOUBLE, 0, extension);

    if (self->bins == NULL) {
        _giraffe_psfdata_clear(self);
        return 2;
    }

    if ((cpl_image_get_size_x(self->bins) != self->nfibers) ||
        (cpl_image_get_size_y(self->bins) != self->nbins)) {
        _giraffe_psfdata_clear(self);
        return 2;
    }

    ++extension;

    /*
     * Load the data buffers from the nparameters following extensions.
     * The extension labels are used as names to look up the individual
     * parameters.
     */

    for (i = extension; i < extension + nparameters; i++) {

        cpl_image* buffer = cpl_image_load(filename, CPL_TYPE_DOUBLE, 0, i);

        if (buffer == NULL) {
            _giraffe_psfdata_clear(self);
            return 2;
        }

        if ((cpl_image_get_size_x(buffer) != self->nfibers) ||
            (cpl_image_get_size_y(buffer) != self->nbins)) {

            cpl_image_delete(buffer);
            buffer = NULL;

            _giraffe_psfdata_clear(self);

                cpl_image_delete(buffer);
                buffer = NULL;
                return 2;
        }
        else {

            const cxchar* name = NULL;

            p = cpl_propertylist_load(filename, i);

            if (p == NULL) {

                cpl_image_delete(buffer);
                buffer = NULL;

                return 2;

            }

            if (cpl_propertylist_has(p, GIALIAS_EXTNAME) == 0) {

                cpl_propertylist_delete(p);
                p = NULL;

                cpl_image_delete(buffer);
                buffer = NULL;

                return 2;

            }

            name = cpl_propertylist_get_string(p, GIALIAS_EXTNAME);
            cx_map_insert(self->values, cx_strdup(name), buffer);

            cpl_propertylist_delete(p);
            p = NULL;

        }

    }

    return 0;

}


cxint
giraffe_psfdata_save(const GiPsfData* self, cpl_propertylist* properties,
                     const cxchar* filename, cxcptr data)
{

    const cxchar* const fctid = "giraffe_psfdata_save";

    cx_map_const_iterator position;

    cpl_propertylist* p = NULL;


    /* Unused */
    (void) data;

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

    cpl_propertylist_update_string(properties, GIALIAS_PSFMODEL,
                                   self->model);
    cpl_propertylist_update_int(properties, GIALIAS_PSFPRMS,
                                cx_map_size(self->values));
    cpl_propertylist_update_int(properties, GIALIAS_PSFXBINS,
                                self->nbins);
    cpl_propertylist_update_int(properties, GIALIAS_PSFNX,
                                self->height);
    cpl_propertylist_update_int(properties, GIALIAS_PSFNY,
                                self->width);
    cpl_propertylist_update_int(properties, GIALIAS_PSFNS,
                                self->nfibers);

    cpl_propertylist_erase(properties, "BSCALE");
    cpl_propertylist_erase(properties, "BZERO");
    cpl_propertylist_erase(properties, "BUNIT");

    cpl_propertylist_erase_regexp(properties, "^CRPIX[0-9]$", 0);
    cpl_propertylist_erase_regexp(properties, "^CRVAL[0-9]$", 0);
    cpl_propertylist_erase_regexp(properties, "^CDELT[0-9]$", 0);
    cpl_propertylist_erase_regexp(properties, "^CTYPE[0-9]$", 0);

    cpl_propertylist_erase_regexp(properties, "^DATA(MIN|MAX)", 0);

    giraffe_error_push();


    cpl_image_save(NULL, filename, CPL_BPP_IEEE_FLOAT,
                   properties, CPL_IO_DEFAULT);

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

    giraffe_error_pop();

    p = cpl_propertylist_new();
    cpl_propertylist_append_string(p, GIALIAS_EXTNAME, "Bin");
    cpl_propertylist_set_comment(p, GIALIAS_EXTNAME, "FITS Extension name");

    giraffe_error_push();

    cpl_image_save(self->bins, filename, CPL_BPP_IEEE_FLOAT, p,
                   CPL_IO_EXTEND);

    if (cpl_error_get_code() != CPL_ERROR_NONE) {

        cpl_propertylist_delete(p);
        p = NULL;

        return 1;
    }

    giraffe_error_pop();

    position = cx_map_begin(self->values);
    while (position != cx_map_end(self->values)) {

        cxint format = 0;

        const cpl_image* psfdata = cx_map_get_value(self->values, position);


        switch (cpl_image_get_type(psfdata)) {
        case CPL_TYPE_INT:
            format = CPL_BPP_32_SIGNED;
            break;

        case CPL_TYPE_FLOAT:
            format = CPL_BPP_IEEE_FLOAT;
            break;

        case CPL_TYPE_DOUBLE:
            format = CPL_BPP_IEEE_FLOAT;
            break;

        default:
            cpl_propertylist_delete(p);
            p = NULL;

            cpl_error_set(fctid, CPL_ERROR_TYPE_MISMATCH);
            return 2;

            break;
        }

        giraffe_error_push();

        cpl_propertylist_set_string(p, GIALIAS_EXTNAME,
                                    cx_map_get_key(self->values, position));

        cpl_image_save(psfdata, filename, format, p, CPL_IO_EXTEND);

        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_propertylist_delete(p);
            p = NULL;

            return 2;
        }

        giraffe_error_pop();

        position = cx_map_next(self->values, position);

    }

    cpl_propertylist_delete(p);
    p = NULL;

    return 0;

}
/**@}*/
