/*
 * 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
 */

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


#include <stdlib.h>
#include <stdio.h>
#include <math.h>

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

#include <cpl_propertylist.h>
#include <cpl_matrix.h>
#include <cpl_image.h>
#include <cpl_msg.h>

#include "gialias.h"
#include "gimacros.h"
#include "gimath.h"
#include "gichebyshev.h"
#include "gimatrix.h"
#include "gislight.h"


/**
 * @defgroup gislight Scattered Light Correction
 *
 * TBD
 */

/**@{*/

struct GiSLightSetup {
    cxint xstep;
    cxint ystep;
    cxdouble ewidth;
    cxint iswidth;
    cxbool istrim;
    cpl_matrix* slices;
};

typedef struct GiSLightSetup GiSLightSetup;


inline static GiSLightSetup*
_giraffe_slightsetup_new(cxint nslices)
{

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

    if (self != NULL) {
        self->slices = cpl_matrix_new(nslices, 3);
    }

    return self;

}


inline static void
_giraffe_slightsetup_delete(GiSLightSetup* self)
{

    if (self != NULL) {

        if (self->slices != NULL) {
            cpl_matrix_delete(self->slices);
            self->slices = NULL;
        }

        cx_free(self);

    }

    return;

}


inline static cxint
_giraffe_compute_isregion(cpl_matrix* xis, cpl_matrix* yis, cpl_matrix* zis,
                          const cpl_image* image, const cpl_image* locy,
                          const cpl_image* locw, const GiSLightSetup* setup)
{

    cxint nx = cpl_image_get_size_y(image);
    cxint ny = cpl_image_get_size_x(image);
    cxint oxend = 0;
    cxint ns = 0;
    cxint nsmax = 0;
    cxint iscount = 0;
    cxint ismax = ny * nx;


    cx_assert(xis != NULL);
    cx_assert(yis != NULL);
    cx_assert(zis != NULL);

    cx_assert(setup->slices != NULL);


    if (nx != cpl_image_get_size_y(locy) ||
        nx != cpl_image_get_size_y(locw)) {
        return -1;
    }

    if (setup->istrim == FALSE) {

        cxint nregions = cpl_image_get_size_x(locy) - 1;
        cxint kmax = ny;
        cxint xs;
        cxint x;

        for (xs = 0; xs < cpl_matrix_get_nrow(setup->slices); xs++) {

            cxint xstart = CX_MAX(cpl_matrix_get(setup->slices, xs, 0), oxend);
            cxint xend = CX_MIN(cpl_matrix_get(setup->slices, xs, 1), nx - 1);
            cxint xstep = CX_MAX(cpl_matrix_get(setup->slices, xs, 2), 1);


            for (x = oxend; x < xstart; x += setup->xstep) {

                cxint zx = x * ny;
                cxint lx = x * cpl_image_get_size_x(locy);
                cxint ylower = 0;
                cxint yupper = 0;

                const cxdouble* _locy = cpl_image_get_data_const(locy);
                const cxdouble* _locw = cpl_image_get_data_const(locw);
                const cxdouble* _image = cpl_image_get_data_const(image);


                if (_locw[lx] <= 0. || _locw[lx + nregions] <= 0.) {
                    continue;
                }

                ylower = (cxint)floor(_locy[lx] -
                                      (_locw[lx] + setup->ewidth));
                yupper = (cxint)ceil(_locy[lx + nregions] +
                                     (_locw[lx + nregions] + setup->ewidth));

                ylower = CX_MAX(CX_MIN(ny, ylower), 0);
                yupper = CX_MAX(CX_MIN(ny, yupper), 0);


                /*
                 * First inter spectrum region
                 */

                if (ylower > setup->iswidth) {

                    register cxint k;

                    for (k = 0; k <= ylower; k += setup->ystep) {
                        cpl_matrix_set(xis, iscount, 0, x);
                        cpl_matrix_set(yis, iscount, 0, k);
                        cpl_matrix_set(zis, 0, iscount, _image[zx + k]);
                        ++iscount;
                    }

                }


                /*
                 * Last inter spectrum region
                 */

                if (ny - yupper > setup->iswidth) {

                    register cxint k;

                    for (k = yupper; k < kmax; k += setup->ystep) {
                        cpl_matrix_set(xis, iscount, 0, x);
                        cpl_matrix_set(yis, iscount, 0, k);
                        cpl_matrix_set(zis, 0, iscount, _image[zx + k]);
                        ++iscount;
                    }

                }

            }

            for (x = xstart; x <= xend; x +=xstep) {

                cxint zx = x * ny;
                cxint lx = x * cpl_image_get_size_x(locy);
                cxint ylower = 0;
                cxint yupper = 0;

                const cxdouble* _locy = cpl_image_get_data_const(locy);
                const cxdouble* _locw = cpl_image_get_data_const(locw);
                const cxdouble* _image = cpl_image_get_data_const(image);


                if (_locw[lx] <= 0. || _locw[lx + nregions] <= 0.) {
                    continue;
                }

                ylower = (cxint)floor(_locy[lx] -
                                      (_locw[lx] + setup->ewidth));
                yupper = (cxint)ceil(_locy[lx + nregions] +
                                     (_locw[lx + nregions] + setup->ewidth));

                ylower = CX_MAX(CX_MIN(ny, ylower), 0);
                yupper = CX_MAX(CX_MIN(ny, yupper), 0);


                /*
                 * First inter spectrum region
                 */

                if (ylower > setup->iswidth) {

                    register cxint k;

                    for (k = 0; k <= ylower; k += setup->ystep) {
                        cpl_matrix_set(xis, iscount, 0, x);
                        cpl_matrix_set(yis, iscount, 0, k);
                        cpl_matrix_set(zis, 0, iscount, _image[zx + k]);
                        ++iscount;
                    }

                }


                /*
                 * Last inter spectrum region
                 */

                if (ny - yupper > setup->iswidth) {

                    register cxint k;

                    for (k = yupper; k < kmax; k += setup->ystep) {
                        cpl_matrix_set(xis, iscount, 0, x);
                        cpl_matrix_set(yis, iscount, 0, k);
                        cpl_matrix_set(zis, 0, iscount, _image[zx + k]);
                        ++iscount;
                    }

                }

            }

            oxend = xend + 1;

        }

        for (x = oxend; x < nx; x += setup->xstep) {

            cxint zx = x * ny;
            cxint lx = x * cpl_image_get_size_x(locy);
            cxint ylower = 0;
            cxint yupper = 0;

            const cxdouble* _locy = cpl_image_get_data_const(locy);
            const cxdouble* _locw = cpl_image_get_data_const(locw);
            const cxdouble* _image = cpl_image_get_data_const(image);


            if (_locw[lx] <= 0. || _locw[lx + nregions] <= 0.) {
                continue;
            }

            ylower = (cxint)floor(_locy[lx] -
                                  (_locw[lx] + setup->ewidth));
            yupper = (cxint)ceil(_locy[lx + nregions] +
                                 (_locw[lx + nregions] + setup->ewidth));

            ylower = CX_MAX(CX_MIN(ny, ylower), 0);
            yupper = CX_MAX(CX_MIN(ny, yupper), 0);


            /*
             * First inter spectrum region
             */

            if (ylower > setup->iswidth) {

                register cxint k;

                for (k = 0; k <= ylower; k += setup->ystep) {
                    cpl_matrix_set(xis, iscount, 0, x);
                    cpl_matrix_set(yis, iscount, 0, k);
                    cpl_matrix_set(zis, 0, iscount, _image[zx + k]);
                    ++iscount;
                }

            }


            /*
             * Last inter spectrum region
             */

            if (ny - yupper > setup->iswidth) {

                register cxint k;

                for (k = yupper; k < kmax; k += setup->ystep) {
                    cpl_matrix_set(xis, iscount, 0, x);
                    cpl_matrix_set(yis, iscount, 0, k);
                    cpl_matrix_set(zis, 0, iscount, _image[zx + k]);
                    ++iscount;
                }

            }

        }

    }

    nsmax = cpl_image_get_size_x(locy) - 1;
    for (ns = 0; ns < nsmax; ns++) {

        cxint nregions = ns + 1;
        cxint xs = 0;
        cxint x;

        oxend = 0;

        for (xs = 0; xs < cpl_matrix_get_nrow(setup->slices); xs++) {

            cxint xstart = CX_MAX(cpl_matrix_get(setup->slices, xs, 0), oxend);
            cxint xend = CX_MIN(cpl_matrix_get(setup->slices, xs, 1), nx - 1);


            for (x = oxend; x < xstart; x += setup->xstep) {

                cxint k;
                cxint zx = x * ny;
                cxint lx = x * cpl_image_get_size_x(locy);
                cxint ylower = 0;
                cxint yupper = 0;

                const cxdouble* _locy = cpl_image_get_data_const(locy);
                const cxdouble* _locw = cpl_image_get_data_const(locw);
                const cxdouble* _image = cpl_image_get_data_const(image);


                if (_locw[lx + ns] <= 0.) {
                    continue;
                }

                while (_locw[lx + nregions] <= 0. &&
                       _locy[lx + nregions] <= 0.) {
                    ++nregions;
                }

                /* Lower border of upper spectrum */

                ylower = (cxint)floor(_locy[lx + nregions] -
                                      (_locw[lx + nregions] + setup->ewidth));

                /* Upper border of lower spectrum */

                yupper = (cxint)ceil(_locy[lx + ns] +
                                     (_locw[lx + ns] + setup->ewidth));

                ylower = CX_MAX(CX_MIN(ny, ylower), 0);
                yupper = CX_MAX(CX_MIN(ny, yupper), 0);

                if (ylower - yupper <= setup->iswidth) {
                    continue;
                }

                for (k = yupper; k <= ylower && iscount < ismax;
                     k += setup->ystep) {
                    cpl_matrix_set(xis, iscount, 0, x);
                    cpl_matrix_set(yis, iscount, 0, k);
                    cpl_matrix_set(zis, 0, iscount, _image[zx + k]);
                    ++iscount;
                }

            }

            for (x = xstart; x <= xend; x += setup->xstep) {

                cxint k;
                cxint zx = x * ny;
                cxint lx = x * cpl_image_get_size_x(locy);
                cxint ylower = 0;
                cxint yupper = 0;

                const cxdouble* _locy = cpl_image_get_data_const(locy);
                const cxdouble* _locw = cpl_image_get_data_const(locw);
                const cxdouble* _image = cpl_image_get_data_const(image);


                if (_locw[lx + ns] <= 0.) {
                    continue;
                }

                while (_locw[lx + nregions] <= 0. &&
                       _locy[lx + nregions] <= 0.) {
                    ++nregions;
                }

                /* Lower border of upper spectrum */

                ylower = (cxint)floor(_locy[lx + nregions] -
                                      (_locw[lx + nregions] + setup->ewidth));

                /* Upper border of lower spectrum */

                yupper = (cxint)ceil(_locy[lx + ns] +
                                     (_locw[lx + ns] + setup->ewidth));

                ylower = CX_MAX(CX_MIN(ny, ylower), 0);
                yupper = CX_MAX(CX_MIN(ny, yupper), 0);

                if (ylower - yupper <= setup->iswidth) {
                    continue;
                }

                for (k = yupper; k <= ylower && iscount < ismax;
                     k += setup->ystep) {
                    cpl_matrix_set(xis, iscount, 0, x);
                    cpl_matrix_set(yis, iscount, 0, k);
                    cpl_matrix_set(zis, 0, iscount, _image[zx + k]);
                    ++iscount;
                }

            }

            oxend = xend + 1;

        }

        for (x = oxend; x < nx; x += setup->xstep) {

            cxint k;
            cxint zx = x * ny;
            cxint lx = x * cpl_image_get_size_x(locy);
            cxint ylower = 0;
            cxint yupper = 0;

            const cxdouble* _locy = cpl_image_get_data_const(locy);
            const cxdouble* _locw = cpl_image_get_data_const(locw);
            const cxdouble* _image = cpl_image_get_data_const(image);


            if (_locw[lx + ns] <= 0.) {
                continue;
            }

            while (_locw[lx + nregions] <= 0. &&
                   _locy[lx + nregions] <= 0.) {
                ++nregions;
            }

            /* Lower border of upper spectrum */

            ylower = (cxint)floor(_locy[lx + nregions] -
                                  (_locw[lx + nregions] + setup->ewidth));

            /* Upper border of lower spectrum */

            yupper = (cxint)ceil(_locy[lx + ns] +
                                 (_locw[lx + ns] + setup->ewidth));

            ylower = CX_MAX(CX_MIN(ny, ylower), 0);
            yupper = CX_MAX(CX_MIN(ny, yupper), 0);

            if (ylower - yupper <= setup->iswidth) {
                continue;
            }

            for (k = yupper; k <= ylower && iscount < ismax;
                 k += setup->ystep) {
                cpl_matrix_set(xis, iscount, 0, x);
                cpl_matrix_set(yis, iscount, 0, k);
                cpl_matrix_set(zis, 0, iscount, _image[zx + k]);
                ++iscount;
            }

        }

    }

    return iscount;

}


inline static cxint
_giraffe_compute_isregion_bpm(cpl_matrix* xis, cpl_matrix* yis,
                              cpl_matrix* zis, const cpl_image* image,
                              const cpl_image* locy, const cpl_image* locw,
                              const cpl_image* bpixel,
                              const GiSLightSetup* setup)
{

    cxint nx = cpl_image_get_size_y(image);
    cxint ny = cpl_image_get_size_x(image);
    cxint oxend = 0;
    cxint ns = 0;
    cxint nsmax = 0;
    cxint iscount = 0;
    cxint ismax = ny * nx;


    cx_assert(xis != NULL);
    cx_assert(yis != NULL);
    cx_assert(zis != NULL);

    cx_assert(setup->slices != NULL);


    if (nx != cpl_image_get_size_y(locy) ||
        nx != cpl_image_get_size_y(locw)) {
        return -1;
    }

    if (nx != cpl_image_get_size_y(bpixel) ||
        nx != cpl_image_get_size_y(bpixel)) {
        return -2;
    }

    if (setup->istrim == FALSE) {

        cxint nregions = cpl_image_get_size_x(locy) - 1;
        cxint kmax = ny;
        cxint xs;
        cxint x;


        for (xs = 0; xs < cpl_matrix_get_nrow(setup->slices); xs++) {

            cxint xstart = CX_MAX(cpl_matrix_get(setup->slices, xs, 0), oxend);
            cxint xend = CX_MIN(cpl_matrix_get(setup->slices, xs, 1), nx - 1);
            cxint xstep = CX_MAX(cpl_matrix_get(setup->slices, xs, 2), 1);


            for (x = oxend; x < xstart; x += setup->xstep) {

                cxint zx = x * ny;
                cxint lx = x * cpl_image_get_size_x(locy);
                cxint ylower = 0;
                cxint yupper = 0;

                const cxdouble* _locy = cpl_image_get_data_const(locy);
                const cxdouble* _locw = cpl_image_get_data_const(locw);
                const cxdouble* _image = cpl_image_get_data_const(image);


                if (_locw[lx] <= 0. || _locw[lx + nregions] <= 0.) {
                    continue;
                }

                ylower = (cxint)floor(_locy[lx] -
                                      (_locw[lx] + setup->ewidth));
                yupper = (cxint)ceil(_locy[lx + nregions] +
                                     (_locw[lx + nregions] + setup->ewidth));

                ylower = CX_MAX(CX_MIN(ny, ylower), 0);
                yupper = CX_MAX(CX_MIN(ny, yupper), 0);


                /*
                 * First inter spectrum region
                 */

                if (ylower > setup->iswidth) {

                    register cxint k;

                    const cxint* _bpixel = cpl_image_get_data_const(bpixel);


                    for (k = 0; k <= ylower; k += setup->ystep) {

                        if (_bpixel[zx + k] & GIR_M_PIX_SET) {
                            continue;
                        }

                        cpl_matrix_set(xis, iscount, 0, x);
                        cpl_matrix_set(yis, iscount, 0, k);
                        cpl_matrix_set(zis, 0, iscount, _image[zx + k]);
                        ++iscount;
                    }

                }


                /*
                 * Last inter spectrum region
                 */

                if (ny - yupper > setup->iswidth) {

                    register cxint k;

                    const cxint* _bpixel = cpl_image_get_data_const(bpixel);


                    for (k = yupper; k < kmax; k += setup->ystep) {

                        if (_bpixel[zx + k] & GIR_M_PIX_SET) {
                            continue;
                        }

                        cpl_matrix_set(xis, iscount, 0, x);
                        cpl_matrix_set(yis, iscount, 0, k);
                        cpl_matrix_set(zis, 0, iscount, _image[zx + k]);
                        ++iscount;
                    }

                }

            }

            for (x = xstart; x <= xend; x +=xstep) {

                cxint zx = x * ny;
                cxint lx = x * cpl_image_get_size_x(locy);
                cxint ylower = 0;
                cxint yupper = 0;

                const cxdouble* _locy = cpl_image_get_data_const(locy);
                const cxdouble* _locw = cpl_image_get_data_const(locw);
                const cxdouble* _image = cpl_image_get_data_const(image);


                if (_locw[lx] <= 0. || _locw[lx + nregions] <= 0.) {
                    continue;
                }

                ylower = (cxint)floor(_locy[lx] -
                                      (_locw[lx] + setup->ewidth));
                yupper = (cxint)ceil(_locy[lx + nregions] +
                                     (_locw[lx + nregions] + setup->ewidth));

                ylower = CX_MAX(CX_MIN(ny, ylower), 0);
                yupper = CX_MAX(CX_MIN(ny, yupper), 0);


                /*
                 * First inter spectrum region
                 */

                if (ylower > setup->iswidth) {

                    register cxint k;

                    const cxint* _bpixel = cpl_image_get_data_const(bpixel);


                    for (k = 0; k <= ylower; k += setup->ystep) {

                        if (_bpixel[zx + k] & GIR_M_PIX_SET) {
                            continue;
                        }

                        cpl_matrix_set(xis, iscount, 0, x);
                        cpl_matrix_set(yis, iscount, 0, k);
                        cpl_matrix_set(zis, 0, iscount, _image[zx + k]);
                        ++iscount;
                    }

                }


                /*
                 * Last inter spectrum region
                 */

                if (ny - yupper > setup->iswidth) {

                    register cxint k;

                    const cxint* _bpixel = cpl_image_get_data_const(bpixel);


                    for (k = yupper; k < kmax; k += setup->ystep) {

                        if (_bpixel[zx + k] & GIR_M_PIX_SET) {
                            continue;
                        }

                        cpl_matrix_set(xis, iscount, 0, x);
                        cpl_matrix_set(yis, iscount, 0, k);
                        cpl_matrix_set(zis, 0, iscount, _image[zx + k]);
                        ++iscount;
                    }

                }

            }

            oxend = xend + 1;

        }

        for (x = oxend; x < nx; x += setup->xstep) {

            cxint zx = x * ny;
            cxint lx = x * cpl_image_get_size_x(locy);
            cxint ylower = 0;
            cxint yupper = 0;

            const cxdouble* _locy = cpl_image_get_data_const(locy);
            const cxdouble* _locw = cpl_image_get_data_const(locw);
            const cxdouble* _image = cpl_image_get_data_const(image);


            if (_locw[lx] <= 0. || _locw[lx + nregions] <= 0.) {
                continue;
            }

            ylower = (cxint)floor(_locy[lx] -
                                  (_locw[lx] + setup->ewidth));
            yupper = (cxint)ceil(_locy[lx + nregions] +
                                 (_locw[lx + nregions] + setup->ewidth));

            ylower = CX_MAX(CX_MIN(ny, ylower), 0);
            yupper = CX_MAX(CX_MIN(ny, yupper), 0);


            /*
             * First inter spectrum region
             */

            if (ylower > setup->iswidth) {

                register cxint k;

                const cxint* _bpixel = cpl_image_get_data_const(bpixel);


                for (k = 0; k <= ylower; k += setup->ystep) {

                    if (_bpixel[zx + k] & GIR_M_PIX_SET) {
                        continue;
                    }

                    cpl_matrix_set(xis, iscount, 0, x);
                    cpl_matrix_set(yis, iscount, 0, k);
                    cpl_matrix_set(zis, 0, iscount, _image[zx + k]);
                    ++iscount;
                }

            }


            /*
             * Last inter spectrum region
             */

            if (ny - yupper > setup->iswidth) {

                register cxint k;

                const cxint* _bpixel = cpl_image_get_data_const(bpixel);


                for (k = yupper; k < kmax; k += setup->ystep) {

                    if (_bpixel[zx + k] & GIR_M_PIX_SET) {
                        continue;
                    }

                    cpl_matrix_set(xis, iscount, 0, x);
                    cpl_matrix_set(yis, iscount, 0, k);
                    cpl_matrix_set(zis, 0, iscount, _image[zx + k]);
                    ++iscount;
                }

            }

        }

    }

    nsmax = cpl_image_get_size_x(locy) - 1;
    for (ns = 0; ns < nsmax; ns++) {

        cxint nregions = ns + 1;
        cxint xs = 0;
        cxint x;

        oxend = 0;

        for (xs = 0; xs < cpl_matrix_get_nrow(setup->slices); xs++) {

            cxint xstart = CX_MAX(cpl_matrix_get(setup->slices, xs, 0), oxend);
            cxint xend = CX_MIN(cpl_matrix_get(setup->slices, xs, 1), nx - 1);


            for (x = oxend; x < xstart; x += setup->xstep) {

                cxint zx = x * ny;
                cxint lx = x * cpl_image_get_size_x(locy);
                cxint ylower = 0;
                cxint yupper = 0;

                const cxdouble* _locy = cpl_image_get_data_const(locy);
                const cxdouble* _locw = cpl_image_get_data_const(locw);
                const cxdouble* _image = cpl_image_get_data_const(image);


                if (_locw[lx + ns] <= 0.) {
                    continue;
                }

                while (_locw[lx + nregions] <= 0. &&
                       _locy[lx + nregions] <= 0.) {
                    ++nregions;
                }

                /* Lower border of upper spectrum */

                ylower = (cxint)floor(_locy[lx + nregions] -
                                      (_locw[lx + nregions] + setup->ewidth));

                /* Upper border of lower spectrum */

                yupper = (cxint)ceil(_locy[lx + ns] +
                                     (_locw[lx + ns] + setup->ewidth));

                ylower = CX_MAX(CX_MIN(ny, ylower), 0);
                yupper = CX_MAX(CX_MIN(ny, yupper), 0);

                if (ylower - yupper > setup->iswidth) {

                    register cxint k;

                    const cxint* _bpixel = cpl_image_get_data_const(bpixel);


                    for (k = yupper; k <= ylower && iscount < ismax;
                         k += setup->ystep) {

                        if (_bpixel[zx + k] & GIR_M_PIX_SET) {
                            continue;
                        }

                        cpl_matrix_set(xis, iscount, 0, x);
                        cpl_matrix_set(yis, iscount, 0, k);
                        cpl_matrix_set(zis, 0, iscount, _image[zx + k]);
                        ++iscount;
                    }

                }

            }

            for (x = xstart; x <= xend; x += setup->xstep) {

                cxint zx = x * ny;
                cxint lx = x * cpl_image_get_size_x(locy);
                cxint ylower = 0;
                cxint yupper = 0;

                const cxdouble* _locy = cpl_image_get_data_const(locy);
                const cxdouble* _locw = cpl_image_get_data_const(locw);
                const cxdouble* _image = cpl_image_get_data_const(image);


                if (_locw[lx + ns] <= 0.) {
                    continue;
                }

                while (_locw[lx + nregions] <= 0. &&
                       _locy[lx + nregions] <= 0.) {
                    ++nregions;
                }

                /* Lower border of upper spectrum */

                ylower = (cxint)floor(_locy[lx + nregions] -
                                      (_locw[lx + nregions] + setup->ewidth));

                /* Upper border of lower spectrum */

                yupper = (cxint)ceil(_locy[lx + ns] +
                                     (_locw[lx + ns] + setup->ewidth));

                ylower = CX_MAX(CX_MIN(ny, ylower), 0);
                yupper = CX_MAX(CX_MIN(ny, yupper), 0);

                if (ylower - yupper > setup->iswidth) {

                    register cxint k;

                    const cxint* _bpixel = cpl_image_get_data_const(bpixel);

                    for (k = yupper; k <= ylower && iscount < ismax;
                         k += setup->ystep) {

                        if (_bpixel[zx + k] & GIR_M_PIX_SET) {
                            continue;
                        }

                        cpl_matrix_set(xis, iscount, 0, x);
                        cpl_matrix_set(yis, iscount, 0, k);
                        cpl_matrix_set(zis, 0, iscount, _image[zx + k]);
                        ++iscount;

                    }

                }

            }

            oxend = xend + 1;

        }

        for (x = oxend; x < nx; x += setup->xstep) {

            cxint zx = x * ny;
            cxint lx = x * cpl_image_get_size_x(locy);
            cxint ylower = 0;
            cxint yupper = 0;

            const cxdouble* _locy = cpl_image_get_data_const(locy);
            const cxdouble* _locw = cpl_image_get_data_const(locw);
            const cxdouble* _image = cpl_image_get_data_const(image);


            if (_locw[lx + ns] <= 0.) {
                continue;
            }

            while (_locw[lx + nregions] <= 0. &&
                   _locy[lx + nregions] <= 0.) {
                ++nregions;
            }

            /* Lower border of upper spectrum */

            ylower = (cxint)floor(_locy[lx + nregions] -
                                  (_locw[lx + nregions] + setup->ewidth));

            /* Upper border of lower spectrum */

            yupper = (cxint)ceil(_locy[lx + ns] +
                                 (_locw[lx + ns] + setup->ewidth));

            ylower = CX_MAX(CX_MIN(ny, ylower), 0);
            yupper = CX_MAX(CX_MIN(ny, yupper), 0);

            if (ylower - yupper > setup->iswidth) {

                register cxint k;

                const cxint* _bpixel = cpl_image_get_data_const(bpixel);


                for (k = yupper; k <= ylower && iscount < ismax;
                     k += setup->ystep) {

                    if (_bpixel[zx + k] & GIR_M_PIX_SET) {
                        continue;
                    }

                    cpl_matrix_set(xis, iscount, 0, x);
                    cpl_matrix_set(yis, iscount, 0, k);
                    cpl_matrix_set(zis, 0, iscount, _image[zx + k]);
                    ++iscount;
                }

            }

        }

    }

    return iscount;

}

static cpl_image*
_giraffe_slight_fit_polynom(const cpl_image* image, const cpl_image* locy,
                            const cpl_image* locw, const cpl_image* bpixel,
                            cxint xorder, cxint yorder, cxint *iscount,
                            const GiSLightSetup* setup)
{

    cxint i;
    cxint j;
    cxint nx = cpl_image_get_size_y(image);
    cxint ny = cpl_image_get_size_x(image);

    cpl_image* slfit = NULL;

    cpl_matrix* xis = cpl_matrix_new(nx * ny, 1);
    cpl_matrix* yis = cpl_matrix_new(nx * ny, 1);
    cpl_matrix* zis = cpl_matrix_new(1, nx * ny);


    *iscount = 0;

    if (bpixel != NULL) {
        *iscount = _giraffe_compute_isregion_bpm(xis, yis, zis, image, locy,
                                                 locw, bpixel, setup);
    }
    else {
        *iscount = _giraffe_compute_isregion(xis, yis, zis, image, locy,
                                             locw, setup);
    }

    if (*iscount <= 0 || *iscount >= nx * ny) {

        cpl_matrix_delete(xis);
        xis = NULL;

        cpl_matrix_delete(yis);
        yis = NULL;

        cpl_matrix_delete(zis);
        zis = NULL;

        return NULL;

    }
    else {

        cxint status = 0;

        cpl_matrix* base = NULL;
        cpl_matrix* coeff = NULL;
        cpl_matrix* chebyshev = NULL;

        GiChebyshev2D* fit = NULL;

        cpl_matrix_set_size(xis, *iscount, 1);
        cpl_matrix_set_size(yis, *iscount, 1);
        cpl_matrix_set_size(zis, 1, *iscount);

#if 0

        // FIXME: For tests only!

        cpl_image* tmp = cpl_image_new(ny, nx, CPL_TYPE_DOUBLE);
        cxdouble* _tmp = cpl_image_get_data(tmp);

        for (i = 0; i < *iscount; i++) {
            cxint k = cpl_matrix_get(xis, i, 0) * cpl_image_get_size_x(tmp) +
                cpl_matrix_get(yis, i, 0);
            _tmp[k] = cpl_matrix_get(zis, 0, i);
        }

        cpl_image_save(tmp, "idiot.fits", CPL_BPP_IEEE_FLOAT, NULL,
                       CPL_IO_DEFAULT);
        cpl_image_delete(tmp);
        tmp = NULL;

#endif

        base = giraffe_chebyshev_base2d(0., 0., nx, ny, xorder + 1,
                                        yorder + 1, xis, yis);

        coeff = giraffe_matrix_leastsq(base, zis);

        if (coeff == NULL) {
            cpl_matrix_delete(base);
            base = NULL;

            cpl_matrix_delete(xis);
            xis = NULL;

            cpl_matrix_delete(yis);
            yis = NULL;

            cpl_matrix_delete(zis);
            zis = NULL;

            return NULL;
        }

        cpl_matrix_delete(base);
        base = NULL;

        cpl_matrix_delete(xis);
        xis = NULL;

        cpl_matrix_delete(yis);
        yis = NULL;

        cpl_matrix_delete(zis);
        zis = NULL;


        /*
         * Compute scattered light model
         */

        chebyshev = cpl_matrix_wrap(xorder + 1, yorder + 1,
                                    cpl_matrix_get_data(coeff));

        fit = giraffe_chebyshev2d_new(xorder, yorder);
        status  = giraffe_chebyshev2d_set(fit, 0., nx, 0., ny, chebyshev);

        if (status != 0) {

            giraffe_chebyshev2d_delete(fit);
            fit = NULL;

            cpl_matrix_unwrap(chebyshev);
            chebyshev = NULL;

            cpl_matrix_delete(coeff);
            coeff = NULL;

            return NULL;

        }

        cpl_matrix_unwrap(chebyshev);
        chebyshev = NULL;

        cpl_matrix_delete(coeff);
        coeff = NULL;

        slfit = cpl_image_new(ny, nx, CPL_TYPE_DOUBLE);

        if (slfit == NULL) {
            giraffe_chebyshev2d_delete(fit);
            fit = NULL;

            return NULL;
        }


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

            cxdouble* _slfit = cpl_image_get_data(slfit);

            for (j = 0; j < ny; j++) {
                cxint k = i * ny + j;
                cxdouble value = giraffe_chebyshev2d_eval(fit, i, j);

                _slfit[k] = value > 0. ? value : 0.;
            }

        }

        giraffe_chebyshev2d_delete(fit);
        fit = NULL;

    }

    return slfit;
}


/**
 * @brief
 *   Compute a scattered light model for a given image
 *
 * @param result        The computed scattered light model.
 * @param image         The image.
 * @param localization  The fiber localization.
 * @param bpixel        The bad pixel map.
 * @param phff          A photometric flat field image.
 * @param config        The task configuration paramters.
 *
 * @return
 *   The function returns 0 on success, or a non-zero value otherwise.

 * The inter-spectrum regions of the input image @em image are used
 * to construct a scattered light model for @em image, using the
 * model provided by the task setup parameters @em config. The position
 * of the spectra and their width are taken from the spectrum localization
 * @em localization. Optionally a bad pixel map may be provided by
 * @em bpixel.
 *
 * The supported scattered light model are a polynomial and a polynomial
 * fraction.
 */

cxint
giraffe_adjust_scattered_light(GiImage* result, const GiImage* image,
                               const GiLocalization* localization,
                               const GiImage* bpixel, GiImage* phff,
                               const GiSLightConfig* config)
{

    const cxchar *const fctid = "giraffe_adjust_scattered_light";


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

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

    if (localization->locy == NULL || localization->locw == NULL) {
        return -1;
    }

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


    /* FIXME: Remove support for unused code path, namely support for models
     *        other than 'polynom' and photometric flat fields. This was
     *        foreseen in the original code, but never implemented.
     */

    if (phff != NULL) {
        cpl_msg_warning(fctid, "Photometric flat field correction is not "
                        "implemented! Ignoring photometric flat field.");
    }


    if (strncmp(config->model, "polynom", 7) != 0) {
        cpl_msg_error(fctid, "Scattered light model `%s' is not supported!",
                      config->model);
        return -3;
    }
    else {

        cxint iscount = 0;

        cx_string* s = NULL;

        const cpl_image* _image = giraffe_image_get(image);
        const cpl_image* _locy = giraffe_image_get(localization->locy);
        const cpl_image* _locw = giraffe_image_get(localization->locw);
        const cpl_image* _bpixel = NULL;

        cpl_propertylist* properties = NULL;

        cpl_image* slfit = NULL;

        GiSLightSetup* setup = _giraffe_slightsetup_new(1);


        setup->xstep = config->xstep;
        setup->ystep = config->ystep;
        setup->ewidth = config->ewidth;
        setup->iswidth = config->iswidth;
        setup->istrim = config->istrim;

        cpl_matrix_set(setup->slices, 0, 0, 0);
        cpl_matrix_set(setup->slices, 0, 1, cpl_image_get_size_y(_image));
        cpl_matrix_set(setup->slices, 0, 2, config->xstep);


        if (bpixel != NULL) {
            _bpixel = giraffe_image_get(bpixel);
        }

        slfit = _giraffe_slight_fit_polynom(_image, _locy, _locw, _bpixel,
                                            config->xorder[0],
                                            config->yorder[0],
                                            &iscount, setup);
        if (slfit == NULL) {
            cpl_msg_error(fctid, "Fitting scattered light model failed!");

            _giraffe_slightsetup_delete(setup);
            setup = NULL;

            return 1;
        }

        _giraffe_slightsetup_delete(setup);
        setup = NULL;

        giraffe_image_set(result, slfit);

        cpl_image_delete(slfit);
        slfit = NULL;

        giraffe_image_set_properties(result,
                                     giraffe_image_get_properties(image));
        properties = giraffe_image_get_properties(result);

        cpl_propertylist_update_string(properties, GIALIAS_SLMNAME,
                                       config->model);
        cpl_propertylist_set_comment(properties, GIALIAS_SLMNAME,
                                     "Scattered light model type.");


        s = cx_string_new();

        if (strncmp(config->model, "polynom", 7) == 0) {

            cx_string_sprintf(s, "%d:%d", config->xorder[0],
                              config->yorder[0]);

            cpl_propertylist_update_string(properties, GIALIAS_SLMORDER,
                                           cx_string_get(s));
            cpl_propertylist_set_comment(properties, GIALIAS_SLMORDER,
                                         "Scattered light polynom order "
                                         "X:Y.");

        }
        else if (strncmp(config->model, "polyfrac", 8) == 0) {

            cx_string_sprintf(s, "%d:%d/%d:%d", config->xorder[0],
                              config->yorder[0], config->xorder[1],
                              config->yorder[1]);

            cpl_propertylist_update_string(properties, GIALIAS_SLMORDER,
                                           cx_string_get(s));
            cpl_propertylist_set_comment(properties, GIALIAS_SLMORDER,
                                         "Scattered light polynom fraction "
                                         "order Xn:Yn/Xd:Xd.");

        }


        cx_string_sprintf(s, "%d:%d", config->xstep, config->ystep);

        cpl_propertylist_update_string(properties, GIALIAS_SLMSTEPS,
                                       cx_string_get(s));
        cpl_propertylist_set_comment(properties, GIALIAS_SLMSTEPS,
                                     "Scattered light X, Y sampling step.");

        cx_string_delete(s);
        s = NULL;


        cpl_propertylist_update_double(properties, GIALIAS_SLMEWIDTH,
                                       config->ewidth);
        cpl_propertylist_set_comment(properties, GIALIAS_SLMEWIDTH,
                                     "Extra pixels added to spectrum "
                                     "width.");

        cpl_propertylist_update_int(properties, GIALIAS_SLMIWIDTH,
                                    config->iswidth);
        cpl_propertylist_set_comment(properties, GIALIAS_SLMIWIDTH,
                                     "Minimum required inter-spectrum "
                                     "region width.");

        cpl_propertylist_update_bool(properties, GIALIAS_SLMTRIM,
                                     config->istrim);
        cpl_propertylist_set_comment(properties, GIALIAS_SLMTRIM,
                                     "Removal of first and last "
                                     "inter-spectrum region.");

    }

    return 0;
}


/**
 * @brief
 *   Creates a setup structure for the scattered light computation.
 *
 * @param list  Parameter list from which the setup informations is read.
 *
 * @return
 *   A newly allocated and initialized setup structure if no errors
 *   occurred, or @c NULL otherwise.
 */

GiSLightConfig*
giraffe_slight_config_create(cpl_parameterlist* list)
{

    const cxchar* s;

    cpl_parameter* p = NULL;

    GiSLightConfig* config = NULL;


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

    config = cx_calloc(1, sizeof *config);

    p = cpl_parameterlist_find(list, "giraffe.slight.model.name");
    config->model = cx_strdup(cpl_parameter_get_string(p));

    if (strncmp(config->model, "polynom", 7) != 0 &&
        strncmp(config->model, "polyfrac", 8) != 0) {
        giraffe_slight_config_destroy(config);
        return NULL;
    }

    p = cpl_parameterlist_find(list, "giraffe.slight.model.order");
    s = cpl_parameter_get_default_string(p);

    if (sscanf(s, "%d,%d", &config->xorder[0], &config->yorder[0]) != 2) {
        giraffe_slight_config_destroy(config);
        return NULL;
    }

    config->xorder[1] = 0;
    config->yorder[1] = 0;

    s = cpl_parameter_get_string(p);

    if (s == NULL) {
        giraffe_slight_config_destroy(config);
        return NULL;
    }
    else {

        cxchar** values = cx_strsplit(s, ",", 5);

        if (values == NULL) {
            giraffe_slight_config_destroy(config);
            return NULL;
        }
        else {

            cxchar* last;

            config->xorder[0] = strtol(values[0], &last, 10);

            if (*last != '\0') {
                cx_strfreev(values);
                giraffe_slight_config_destroy(config);

                return NULL;
            }

            if (values[1] != NULL) {

                config->yorder[0] = strtol(values[1], &last, 10);

                if (*last != '\0') {
                    cx_strfreev(values);
                    giraffe_slight_config_destroy(config);

                    return NULL;
                }

            }

            if (strncmp(config->model, "polyfrac", 8) == 0) {

                if (values[2] != NULL) {

                    config->xorder[1] = strtol(values[2], &last, 10);

                    if (*last != '\0') {
                        cx_strfreev(values);
                        giraffe_slight_config_destroy(config);

                        return NULL;
                    }

                }

                if (values[3] != NULL) {

                    config->yorder[1] = strtol(values[3], &last, 10);

                    if (*last != '\0') {
                        cx_strfreev(values);
                        giraffe_slight_config_destroy(config);

                        return NULL;
                    }

                }

            }

            cx_strfreev(values);
            values = NULL;

        }

    }


    p = cpl_parameterlist_find(list, "giraffe.slight.xstep");
    config->xstep = cpl_parameter_get_int(p);

    p = cpl_parameterlist_find(list, "giraffe.slight.ystep");
    config->ystep = cpl_parameter_get_int(p);

    p = cpl_parameterlist_find(list, "giraffe.slight.xslice");
    s = cpl_parameter_get_default_string(p);


    /* FIXME: Whether slices should be supported is still TBD. Currently
     *        there the parameter is accepted, but its support is not
     *        implemented!
     */

    if (strncmp(s, "none", 4) != 0) {
        giraffe_slight_config_destroy(config);
        return NULL;
    }

    s = cpl_parameter_get_string(p);

    if (s == NULL) {
        giraffe_slight_config_destroy(config);
        return NULL;
    }
    else {

        cxchar** slices = cx_strsplit(s, ",", -1);

        if (slices == NULL) {
            giraffe_slight_config_destroy(config);
            return NULL;
        }
        else {
            cx_strfreev(slices);
            slices = NULL;
        }

    }


    p = cpl_parameterlist_find(list, "giraffe.slight.ewidth");
    config->ewidth = cpl_parameter_get_double(p);

    p = cpl_parameterlist_find(list, "giraffe.slight.iswidth");
    config->iswidth = cpl_parameter_get_int(p);

    p = cpl_parameterlist_find(list, "giraffe.slight.istrim");
    config->istrim = cpl_parameter_get_bool(p);

    p = cpl_parameterlist_find(list, "giraffe.slight.phffcorrection");
    config->phffcor = cpl_parameter_get_bool(p);

    p = cpl_parameterlist_find(list, "giraffe.slight.remove");
    config->remove = cpl_parameter_get_bool(p);

    return config;

}


/**
 * @brief
 *   Destroys a scattered light setup structure.
 *
 * @param config  The setup structure to destroy.
 *
 * @return
 *   Nothing.
 *
 * The function deallocates the memory used by the setup structure
 * @em config.
 */

void
giraffe_slight_config_destroy(GiSLightConfig *config)
{

    if (config != NULL) {

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

        cx_free(config);

    }

    return;

}


/**
 * @brief
 *   Adds parameters for the scattered light computation.
 *
 * @param list  Parameter list to which parameters are added.
 *
 * @return
 *   Nothing.
 *
 * TBD
 */

void
giraffe_slight_config_add(cpl_parameterlist* list)
{

    cpl_parameter* p;


    if (list == NULL) {
        return;
    }

    p = cpl_parameter_new_enum("giraffe.slight.model.name",
                               CPL_TYPE_STRING,
                               "Name of the scattered light model to use.",
                               "giraffe.slight",
                               "polynom", 2, "polynom", "polyfrac");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "slight-model");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.slight.model.order",
                                CPL_TYPE_STRING,
                                "Scattered light model fit X and Y order.",
                                "giraffe.slight",
                                "4,2");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "slight-order");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.slight.xstep",
                                CPL_TYPE_INT,
                                "Interspectrum region sampling step along "
                                "the dispersion direction.",
                                "giraffe.slight",
                                10);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "slight-xstep");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.slight.ystep",
                                CPL_TYPE_INT,
                                "Interspectrum region sampling step along "
                                "the spatial direction.",
                                "giraffe.slight",
                                1);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "slight-ystep");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.slight.xslice",
                                CPL_TYPE_STRING,
                                "Interspectrum region sampling step along "
                                "the dispersion direction for a specific "
                                "region. This overrides 'xstep' for the "
                                "given region.",
                                "giraffe.slight",
                                "none");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "slight-xslice");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.slight.ewidth",
                                CPL_TYPE_DOUBLE,
                                "Extra width [pixels] added to both sides "
                                "of a spectrum trace.",
                                "giraffe.slight",
                                0.5);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "slight-ewidth");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.slight.iswidth",
                                CPL_TYPE_INT,
                                "Minimum width [pixels] required for "
                                "interspectrum regions.",
                                "giraffe.slight",
                                2);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "slight-iswidth");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.slight.istrim",
                                CPL_TYPE_BOOL,
                                "Turn off using the first and last "
                                "interspectrum region.",
                                "giraffe.slight",
                                TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "slight-istrim");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.slight.phffcorrection",
                                CPL_TYPE_BOOL,
                                "Use photometric flat field correction.",
                                "giraffe.slight",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "slight-phff");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.slight.remove",
                                CPL_TYPE_BOOL,
                                "Remove scattered light from the input "
                                "frame.",
                                "giraffe.slight",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "slight-remove");
    cpl_parameterlist_append(list, p);

    return;

}
/**@}*/
