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

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

#include "gialias.h"
#include "gitable.h"
#include "gierror.h"
#include "gichebyshev.h"
#include "giwlresiduals.h"


/**
 * @defgroup giwlresiduals Wavelength Solution Residuals
 *
 * TBD
 */

/**@{*/

struct GiWlResidualData {

    cxint ssn;
    GiChebyshev2D *fit;

};

typedef struct GiWlResidualData GiWlResidualData;


struct GiWlResiduals {

    cx_map *data;

};


inline static cxint
_giraffe_compare_int(cxcptr first, cxcptr second)
{

    cxint *_first = (cxint *)first;
    cxint *_second = (cxint *)second;

    return *_first - *_second;

}


inline static void
_giraffe_wlresidualdata_delete(GiWlResidualData *self)
{

    if (self) {

        if (self->fit) {
            giraffe_chebyshev2d_delete(self->fit);
        }

        cx_free(self);

    }

    return;

}


inline static GiWlResidualData *
_giraffe_wlresiduals_get(const GiWlResiduals *self, cxsize idx)
{

    cx_map_const_iterator position;

    GiWlResidualData *data = NULL;


    position = cx_map_begin(self->data);

    if (position == cx_map_end(self->data)) {
        return NULL;
    }

    if (idx > 0) {

        cxsize i;

        for (i = 1; i < idx; i++) {
            position = cx_map_next(self->data, position);
        }

    }

    data = cx_map_get_value(self->data, position);

    return data;

}


inline static void
_giraffe_wlresiduals_insert(GiWlResiduals *self, cxint ssn,
                            const GiChebyshev2D *fit)
{

    GiWlResidualData *data = cx_calloc(1, sizeof *data);


    data->ssn = ssn;
    data->fit = (GiChebyshev2D *)fit;

    cx_map_insert(self->data, &data->ssn, data);

    return;

}


GiWlResiduals *
giraffe_wlresiduals_new(void)
{

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

    if (self) {

        self->data = cx_map_new(_giraffe_compare_int, NULL,
                                (cx_free_func)_giraffe_wlresidualdata_delete);
        cx_assert(cx_map_empty(self->data));

    }

    return self;

}


GiWlResiduals *
giraffe_wlresiduals_clone(const GiWlResiduals *other)
{

    GiWlResiduals *self = NULL;


    if (other != NULL) {

        self = giraffe_wlresiduals_new();

        if (!cx_map_empty(other->data)) {

            cx_map_const_iterator position = cx_map_begin(other->data);


            while (position != cx_map_end(other->data)) {

                GiWlResidualData *data = cx_map_get_value(other->data,
                                                          position);

                GiChebyshev2D *fit = giraffe_chebyshev2d_clone(data->fit);


                _giraffe_wlresiduals_insert(self, data->ssn, fit);

                position = cx_map_next(other->data, position);

            }

        }

        cx_assert(cx_map_size(self->data) == cx_map_size(other->data));

    }

    return self;

}


GiWlResiduals *
giraffe_wlresiduals_create(GiTable *wlsolution)
{

    const cxchar *const fctid = "giraffe_wlresiduals_create";


    cpl_propertylist *properties = NULL;

    cpl_table *residuals = NULL;

    GiWlResiduals *self = giraffe_wlresiduals_new();


    if (wlsolution == NULL) {

        cpl_error_set(fctid, CPL_ERROR_DATA_NOT_FOUND);
        giraffe_wlresiduals_delete(self);

        return NULL;

    }

    properties = giraffe_table_get_properties(wlsolution);

    if (properties == NULL) {

        cpl_error_set(fctid, CPL_ERROR_DATA_NOT_FOUND);
        giraffe_wlresiduals_delete(self);

        return NULL;
    }

    residuals = giraffe_table_get(wlsolution);

    if (residuals == NULL) {

        cpl_error_set(fctid, CPL_ERROR_DATA_NOT_FOUND);
        giraffe_wlresiduals_delete(self);

        return NULL;
    }

    if (!cpl_table_has_column(residuals, "XMIN") ||
        !cpl_table_has_column(residuals, "XMAX") ||
        !cpl_table_has_column(residuals, "YMIN") ||
        !cpl_table_has_column(residuals, "YMAX")) {

//        cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
//        giraffe_wlresiduals_delete(self);

//        return NULL;

        cpl_table_new_column(residuals, "XMIN", CPL_TYPE_DOUBLE);
        cpl_table_new_column(residuals, "XMAX", CPL_TYPE_DOUBLE);
        cpl_table_new_column(residuals, "YMIN", CPL_TYPE_DOUBLE);
        cpl_table_new_column(residuals, "YMAX", CPL_TYPE_DOUBLE);

        cpl_table_set_double(residuals, "XMIN", 0, 0.);
        cpl_table_set_double(residuals, "XMAX", 0, 4096.);
        cpl_table_set_double(residuals, "YMIN", 0, 0.);
        cpl_table_set_double(residuals, "YMAX", 0, 2048.);

    }

//    else {

    if (1) {

        cxint i;
        cxint xorder = 0;
        cxint yorder = 0;
        /*cxint nc = 0;*/

        cx_string *label = NULL;

        cpl_matrix *coeffs = NULL;


        if (!cpl_propertylist_has(properties, GIALIAS_WSOL_XRORDER)) {

            cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
            giraffe_wlresiduals_delete(self);

            return NULL;

        }
        else {


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

            cxchar **orders = cx_strsplit(s, ":", 3);

            if (orders[1] == NULL) {

                cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
                giraffe_wlresiduals_delete(self);
                cx_strfreev(orders);

                return NULL;
            }

            xorder = strtol(orders[0], NULL, 10);
            yorder = strtol(orders[1], NULL, 10);

            cx_strfreev(orders);

        }


        /*nc = (xorder + 1) * (yorder + 1);*/

        label = cx_string_new();
        coeffs = cpl_matrix_new(xorder + 1, yorder + 1);


        for (i = 0; i < cpl_table_get_nrow(residuals); i++) {

            cxint j;
            cxint l = 0;
            cxint ssn = cpl_table_get_int(residuals, "SSN", i, NULL);

            cxdouble ax = cpl_table_get(residuals, "XMIN", i, NULL);
            cxdouble bx = cpl_table_get(residuals, "XMAX", i, NULL);
            cxdouble ay = cpl_table_get(residuals, "YMIN", i, NULL);
            cxdouble by = cpl_table_get(residuals, "YMAX", i, NULL);

            GiChebyshev2D *fit = NULL;


            for (j = 0; j <= xorder; j++) {

                cxint k;

                for (k = 0; k <= yorder; k++) {

                    cxdouble coeff = 0.;

                    cx_string_sprintf(label, "XC%-d", l);
                    coeff = cpl_table_get(residuals, cx_string_get(label),
                                          i, NULL);

                    cpl_matrix_set(coeffs, j, k, coeff);
                    ++l;

                }

            }

            fit = giraffe_chebyshev2d_new(xorder, yorder);
            giraffe_chebyshev2d_set(fit, ax, bx, ay, by, coeffs);

            _giraffe_wlresiduals_insert(self, ssn, fit);

        }

        cpl_matrix_delete(coeffs);
        cx_string_delete(label);

    }

    return self;

}


void
giraffe_wlresiduals_delete(GiWlResiduals *self)
{

    if (self) {

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

        cx_free(self);

    }

    return;

}


cxsize
giraffe_wlresiduals_get_size(const GiWlResiduals *self)
{

    cx_assert(self != NULL);

    return cx_map_size(self->data);

}


cxint
giraffe_wlresiduals_get_subslit(const GiWlResiduals *self, cxsize idx)
{

    const GiWlResidualData *data = NULL;


    cx_assert(self != NULL);

    data = _giraffe_wlresiduals_get(self, idx);

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

    return data->ssn;

}


GiChebyshev2D *
giraffe_wlresiduals_get_element(const GiWlResiduals *self, cxsize idx)
{

    const GiWlResidualData *data = NULL;


    cx_assert(self != NULL);

    data = _giraffe_wlresiduals_get(self, idx);

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

    return data->fit;

}


cxint
giraffe_wlresiduals_set(GiWlResiduals *self, cxint ssn,
                        const GiChebyshev2D *residuals)
{

    cx_assert(self != NULL);

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

    cx_map_erase(self->data, &ssn);
    _giraffe_wlresiduals_insert(self, ssn , residuals);

    return 0;

}

GiChebyshev2D *
giraffe_wlresiduals_get(const GiWlResiduals *self, cxint ssn)
{

    const GiWlResidualData *data = NULL;


    cx_assert(self != NULL);

    data = cx_map_get(self->data, &ssn);
    return data->fit;

}


cpl_table *
giraffe_wlresiduals_table(const GiWlResiduals *self)
{

    const cxchar *label = "SSN";

    cxint i = 0;
    cxint xorder = 0;
    cxint yorder = 0;
    cxint ncoeff = 0;

    cx_string *s = NULL;

    cx_map_const_iterator position;

    cpl_propertylist *sorting_order = NULL;

    cpl_table *residuals = NULL;

    const GiWlResidualData *data = NULL;


    cx_assert(self != NULL);

    if (cx_map_empty(self->data)) {
        return NULL;
    }

    position = cx_map_begin(self->data);

    data = cx_map_get_value(self->data, position);
    cx_assert(data != NULL);

    giraffe_chebyshev2d_get_order(data->fit, &xorder, &yorder);
    ncoeff = (xorder + 1) * (yorder + 1);


    residuals = cpl_table_new(cx_map_size(self->data));

    s = cx_string_new();

    giraffe_error_push();

    cpl_table_new_column(residuals, label, CPL_TYPE_INT);

    cpl_table_new_column(residuals, "XMIN", CPL_TYPE_DOUBLE);
    cpl_table_new_column(residuals, "XMAX", CPL_TYPE_DOUBLE);
    cpl_table_new_column(residuals, "YMIN", CPL_TYPE_DOUBLE);
    cpl_table_new_column(residuals, "YMAX", CPL_TYPE_DOUBLE);


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

        cx_string_sprintf(s, "XC%-d", i);
        cpl_table_new_column(residuals, cx_string_get(s), CPL_TYPE_DOUBLE);

    }

    if (cpl_error_get_code() != CPL_ERROR_NONE) {

        cpl_table_delete(residuals);
        cx_string_delete(s);

        return NULL;
    }

    giraffe_error_pop();


    i = 0;

    while (position != cx_map_end(self->data)) {

        cxint ssn = 0;

        cxsize j;
        cxsize l = 0;
        cxsize nx = 0;
        cxsize ny = 0;

        cxdouble ax = 0.;
        cxdouble bx = 0.;
        cxdouble ay = 0.;
        cxdouble by = 0.;

        cpl_matrix *_coeffs = NULL;

        const GiChebyshev2D *fit = NULL;


        data = cx_map_get_value(self->data, position);

        ssn = data->ssn;

        fit = data->fit;
        cx_assert(fit != NULL);

        _coeffs = (cpl_matrix *)giraffe_chebyshev2d_coeffs(fit);
        giraffe_chebyshev2d_get_range(fit, &ax, &bx, &ay, &by);

        cpl_table_set_int(residuals, label, i , ssn);

        cpl_table_set_double(residuals, "XMIN", i, ax);
        cpl_table_set_double(residuals, "XMAX", i, bx);
        cpl_table_set_double(residuals, "YMIN", i, ay);
        cpl_table_set_double(residuals, "YMAX", i, by);

        nx = cpl_matrix_get_nrow(_coeffs);
        ny = cpl_matrix_get_ncol(_coeffs);

        cx_assert(nx * ny == (cxsize)((xorder + 1) * (yorder + 1)));

        for (j = 0; (cxsize)j < nx; j++) {

            cxint k;

            for (k = 0; (cxsize)k < ny; k++) {

                cxdouble value = cpl_matrix_get(_coeffs, j, k);

                cx_string_sprintf(s, "XC%-" CX_PRINTF_FORMAT_SIZE_TYPE, l);
                cpl_table_set_double(residuals, cx_string_get(s), i, value);
                ++l;

            }

        }

        position = cx_map_next(self->data, position);
        ++i;

    }

    cx_string_delete(s);
    s = NULL;

    sorting_order = cpl_propertylist_new();
    cpl_propertylist_append_bool(sorting_order, label, 0);

    cpl_table_sort(residuals, sorting_order);

    cpl_propertylist_delete(sorting_order);
    sorting_order = NULL;

    return residuals;

}
/**@}*/
