/*
 * 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 <cxmap.h>
#include <cxstrutils.h>

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

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



/*
 * @defgroup gilinedata Line Data Storage
 *
 * TBD
 */

/**@{*/

struct GiLineData {

    const cxchar* model;

    cxint nfibers;
    cxint nlines;
    cxint* ignore;

    cxdouble* wavelength;

    cpl_image* status;

    cx_map* values;

};


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

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

}


inline static cxptr
_giraffe_linedata_get_data(cx_map* map, const cxchar* name)
{


    cpl_image* data = cx_map_get(map, name);

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

    return cpl_image_get_data(data);

}


inline static void
_giraffe_linedata_clear(GiLineData* self)
{

    self->nfibers = 0;
    self->nlines = 0;

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

    if (self->ignore) {
        cx_free(self->ignore);
        self->ignore = NULL;
    }

    if (self->wavelength) {
        cx_free(self->wavelength);
        self->wavelength = NULL;
    }

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

    if (self->values) {
        cx_map_clear(self->values);
    }

    cx_assert(cx_map_empty(self->values));

    return;
}


inline static cxint
_giraffe_linedata_assign(GiLineData* 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->nlines) {
        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_linedata_set(GiLineData* 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->nlines,
                                          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_linedata_get(const GiLineData* 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;

}


GiLineData*
giraffe_linedata_new(void)
{

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

    self->nfibers = 0;
    self->nlines = 0;

    self->model = NULL;
    self->ignore = NULL;

    self->wavelength = NULL;
    self->status = NULL;

    self->values = cx_map_new(_giraffe_linedata_compare, cx_free,
                              (cx_free_func)cpl_image_delete);
    cx_assert(cx_map_empty(self->values));

    return self;

}


GiLineData*
giraffe_linedata_create(const cpl_table* lines, const cpl_table* fibers,
                        const cxchar* model)
{
    cxint i;

    GiLineData* self = NULL;


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

    if (!cpl_table_has_column(lines, "WLEN")) {
        return NULL;
    }

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

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

    self = cx_malloc(sizeof(GiLineData));
    cx_assert(self);

    self->nfibers = cpl_table_get_nrow(fibers);
    self->nlines = cpl_table_get_nrow(lines);

    self->model = cx_strdup(model);
    self->ignore = cx_calloc(self->nlines, sizeof(cxint));

    self->wavelength = cx_calloc(self->nlines, sizeof(cxdouble));

    for (i = 0; i < self->nlines; i++) {
        self->wavelength[i] = cpl_table_get(lines, "WLEN", i, NULL);
    }

    /* Lazy buffer creation! */
    self->status = NULL;

    self->values = cx_map_new(_giraffe_linedata_compare, cx_free,
                              (cx_free_func)cpl_image_delete);
    cx_assert(cx_map_empty(self->values));

    return self;

}


void
giraffe_linedata_delete(GiLineData* self)
{

    if (self) {
        _giraffe_linedata_clear(self);

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

        cx_free(self);
    }

    return;

}


cxint
giraffe_linedata_reset(GiLineData* self, const cpl_table* lines,
                       const cpl_table* fibers, const cxchar* model)
{

    cxint i;


    cx_assert(self != NULL);

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

    if (!cpl_table_has_column(lines, "WLEN")) {
        return 1;
    }

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

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


    self->nfibers = cpl_table_get_nrow(fibers);
    self->nlines = cpl_table_get_nrow(lines);

    if (self->model != NULL) {
        cx_free((cxchar*)self->model);
    }
    self->model = cx_strdup(model);

    if (self->ignore != NULL) {
        cx_free(self->ignore);
    }
    self->ignore = cx_calloc(self->nlines, sizeof(cxint));

    self->wavelength = cx_realloc(self->wavelength,
                                  self->nlines * sizeof(cxdouble));

    for (i = 0; i < self->nlines; i++) {

        self->wavelength[i] = cpl_table_get(lines, "WLEN", i,
                                            NULL);

    }

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

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

    return 0;

}


const cxchar*
giraffe_linedata_model(const GiLineData* self)
{

    cx_assert(self != NULL);

    return self->model;

}


cxsize
giraffe_linedata_lines(const GiLineData* self)
{

    cx_assert(self != NULL);

    return self->nlines;

}


cxsize
giraffe_linedata_fibers(const GiLineData* self)
{

    cx_assert(self != NULL);

    return self->nfibers;

}


cxbool
giraffe_linedata_contains(GiLineData* 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;

}


cxsize
giraffe_linedata_rejected(const GiLineData* self)
{

    cxint i;
    cxint* status;

    cxsize count = 0;


    cx_assert(self != NULL);

    if (self->status != NULL) {

        status = cpl_image_get_data(self->status);

        for (i = 0; i < self->nfibers * self->nlines; i++) {
            if (status[i] > 0) {
                ++count;
            }
        }

    }

    return count;

}


cxsize
giraffe_linedata_accepted(const GiLineData* self)
{

    cxsize count = 0;


    cx_assert(self != NULL);

    count = self->nfibers * self->nlines;

    return count - giraffe_linedata_rejected(self);

}


cpl_image*
giraffe_linedata_status(const GiLineData* self)
{

    cx_assert(self != NULL);

    if (self->status == NULL) {
        return cpl_image_new(self->nfibers, self->nlines, CPL_TYPE_INT);
    }

    return cpl_image_duplicate(self->status);

}


cxint
giraffe_linedata_set_status(GiLineData* self, cxint fiber, cxint line,
                            cxint status)
{

    cxint* data = NULL;


    cx_assert(self != NULL);

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

    if (line >= self->nlines) {
        return 1;
    }

    if (self->status == NULL) {
        self->status = cpl_image_new(self->nfibers, self->nlines,
                                     CPL_TYPE_INT);
        if (self->status == NULL) {
            return -1;
        }
    }

    data = cpl_image_get_data(self->status);

    data[self->nfibers * line + fiber] = status;

    if (status != 0) {
        self->ignore[line] += 1;
    }

    return 0;

}


cxint
giraffe_linedata_get_status(const GiLineData* self, cxint fiber, cxint line)
{

    cxint* data = NULL;


    cx_assert(self != NULL);

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

    if (line >= self->nlines) {
        return 1;
    }

    if (self->status == NULL) {
        return 0;
    }

    data = cpl_image_get_data(self->status);

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

}


cxint
giraffe_linedata_set_wavelength(GiLineData* self, cxint line, cxdouble lambda)
{

    cx_assert(self != NULL);

    if (line < 0 || line >= self->nlines) {
        return 1;
    }

    self->wavelength[line] = lambda;

    return 0;

}


cxdouble
giraffe_linedata_get_wavelength(const GiLineData* self, cxint line)
{

    const cxchar* const fctid = "giraffe_linedata_get_wavelength";


    cx_assert(self != NULL);

    if (line < 0 || line >= self->nlines) {
        cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
        return 0.;
    }

    return self->wavelength[line];

}


cxint
giraffe_linedata_set(GiLineData* self, const cxchar* name, cxint fiber,
                     cxint line, cxdouble value)
{

    cxint status = 0;

    cx_assert(self != NULL);

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

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

    if (line >= self->nlines) {
        return 1;
    }

    status =  _giraffe_linedata_set(self, self->values, name, fiber, line,
                                    value);

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

    return 0;

}


cxdouble
giraffe_linedata_get(const GiLineData* self, const cxchar* name, cxint fiber,
                     cxint line)
{

    const cxchar* const fctid = "giraffe_linedata_get";

    cxint status = 0;

    cxdouble value = 0.;


    cx_assert(self != NULL);

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

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

    if (line >= self->nlines) {
        return 1;
    }

    status = _giraffe_linedata_get(self, self->values, name, fiber, line,
                                   &value);

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

    return value;

}


cxint
giraffe_linedata_set_data(GiLineData* 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_linedata_assign(self, self->values, name, data);

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

    return 0;

}


const cpl_image*
giraffe_linedata_get_data(const GiLineData* self, const cxchar* name)
{

    cx_assert(self != NULL);

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

    return cx_map_get(self->values, name);

}


cxint
giraffe_linedata_load(GiLineData* self, const cxchar* filename)
{

    cxsize extension = 1;

    cpl_table* lines = NULL;

    cpl_propertylist* p = NULL;


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

    _giraffe_linedata_clear(self);


    giraffe_error_push();

    p = cpl_propertylist_load(filename, 0);

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

    if (cpl_propertylist_has(p, GIALIAS_WSOL_LMNAME) == 0) {
        return 1;
    }
    else {

        self->model = cx_strdup(cpl_propertylist_get_string(p,
            GIALIAS_WSOL_LMNAME));

    }

    if (cpl_error_get_code() != CPL_ERROR_NONE) {

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

        return 1;

    }

    giraffe_error_pop();

    cpl_propertylist_delete(p);
    p = NULL;


    /*
     * Load line wavelength and line status flags
     */

    lines = cpl_table_load(filename, extension, 0);

    if (lines == NULL) {
        _giraffe_linedata_clear(self);
        return 2;
    }

    if (cpl_table_has_column(lines, "WLEN") == FALSE) {
        _giraffe_linedata_clear(self);
        return 2;
    }
    else {

        const cxdouble* lambda = cpl_table_get_data_double(lines, "WLEN");

        self->nlines = cpl_table_get_nrow(lines);

        self->ignore = cx_calloc(self->nlines, sizeof(cxint));
        self->wavelength = cx_malloc(self->nlines * sizeof(cxdouble));

        memcpy(self->wavelength, lambda, self->nlines * sizeof(cxdouble));
    }

    ++extension;

    self->status = cpl_image_load(filename, CPL_TYPE_INT, 0, extension);

    if (self->status == NULL) {
        _giraffe_linedata_clear(self);
        return 2;
    }

    self->nfibers = cpl_image_get_size_x(self->status);

    ++extension;


    /*
     * Load the data buffers from the following extensions.
     * The extension labels are used as names to as the names
     * of the line parameters.
     */

    p = cpl_propertylist_load(filename, extension);

    // FIXME: The condition extension < 22 is needed because of a problem
    //        in cpl_propertylist_load() of CPL 4.0. It must be removed if the
    //        patched version is available on Paranal/DFO machines.

    while ((p != NULL) && (extension < 22)) {

        const cxchar* name = cpl_propertylist_get_string(p, GIALIAS_EXTNAME);

        if (name == NULL) {
            cpl_propertylist_delete(p);
            p = NULL;

            _giraffe_linedata_clear(self);

            return 3;
        }
        else {

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

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

                cpl_image_delete(buffer);
                buffer = NULL;

                cpl_propertylist_delete(p);
                p = NULL;

                _giraffe_linedata_clear(self);

                return 3;

            }

            cx_map_insert(self->values, cx_strdup(name), buffer);

        }

        ++extension;

        cpl_propertylist_delete(p);
        p = cpl_propertylist_load(filename, extension);

    }

    cpl_propertylist_delete(p);
    p = NULL;

    return 0;

}


cxint
giraffe_linedata_save(GiLineData* self, const cpl_propertylist* properties,
                      const cxchar* filename)
{

    cxint status = 0;

    cpl_propertylist* p = NULL;


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

    p = cpl_propertylist_duplicate(properties);

    status = giraffe_linedata_writer(self, p, filename, NULL);

    cpl_propertylist_delete(p);
    p = NULL;

    return status;

}


cxint
giraffe_linedata_writer(const GiLineData* self, cpl_propertylist* properties,
                        const cxchar* filename, cxcptr data)
{

    const cxchar* const fctid = "giraffe_linedata_writer";

    cx_map_const_iterator position;

    cpl_propertylist* p = NULL;

    cpl_table* lines = NULL;


    /* Unused */
    (void) data;

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

    lines = cpl_table_new(self->nlines);

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

    giraffe_error_push();

    cpl_table_new_column(lines, "WLEN", CPL_TYPE_DOUBLE);
    cpl_table_copy_data_double(lines, "WLEN", self->wavelength);

    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        cpl_table_delete(lines);
        lines = NULL;

        return 1;
    }

    giraffe_error_pop();


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

    /* FIXME: Workaround for CPL deficiency. World coordinate
     *        keywords are not removed from table headers.
     */

    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);

    cpl_propertylist_erase(properties, "EXTNAME");


    cpl_propertylist_update_string(properties, GIALIAS_WSOL_LMNAME,
                                   self->model);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_LMNAME,
                                 "Line profile model");

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

    giraffe_error_push();

    cpl_table_save(lines, properties, p, filename, CPL_IO_CREATE);

    if (cpl_error_get_code() != CPL_ERROR_NONE) {

        cpl_propertylist_delete(p);
        p = NULL;

        cpl_table_delete(lines);
        lines = NULL;

        return 2;
    }

    cpl_table_delete(lines);
    lines = NULL;

    giraffe_error_pop();

    cpl_propertylist_set_string(p, GIALIAS_EXTNAME, "LINE_FLAGS");

    giraffe_error_push();

    if (self->status == NULL) {

        cpl_image* status = cpl_image_new(self->nfibers, self->nlines,
                                          CPL_TYPE_INT);

        cpl_image_save(status, filename, CPL_BPP_16_SIGNED, p,
                       CPL_IO_EXTEND);
        cpl_image_delete(status);
        status = NULL;

    }
    else {
        cpl_image_save(self->status, filename, CPL_BPP_16_SIGNED, p,
                       CPL_IO_EXTEND);
    }

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

        return 2;
    }

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

        cxint format = 0;

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


        switch (cpl_image_get_type(ldata)) {
            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;
        }

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

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

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

            return 2;
        }

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

    }

    giraffe_error_pop();

    cpl_propertylist_delete(p);
    p = NULL;

    return 0;

}
/**@}*/
