/*===========================================================================
  Copyright (C) 2001 European Southern Observatory (ESO)

  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., 675 Massachusetss Ave, Cambridge, 
  MA 02139, USA.

  Corresponding concerning ESO-MIDAS should be addressed as follows:
  Internet e-mail: midas@eso.org
  Postal address: European Southern Observatory
  Data Management Division 
  Karl-Schwarzschild-Strasse 2
  D 85748 Garching bei Muenchen 
  GERMANY
  ===========================================================================*/
/* Program  : slitdivide.c                                                 */
/* Author   : G. Mulas  -  ITAL_FLAMES Consortium                          */
/* Date     :                                                              */
/*                                                                         */
/* Purpose  : Missing                                                      */
/*                                                                         */
/*                                                                         */
/* Input:  see interface                                                   */ 
/*                                                                      */
/* Output:                                                              */
/*                                                                         */
/* DRS Functions called:                                                   */
/* none                                                                    */ 
/*                                                                         */ 
/* Pseudocode:                                                             */
/* Missing                                                                 */ 
/*                                                                         */ 
/* Version  :                                                              */
/* Last modification date: 2002/08/05                                      */
/* Who     When        Why                Where                            */
/* AMo     02-08-05   Add header         header                            */
/*-------------------------------------------------------------------------*/

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

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <flames_midas_def.h>
#include <flames_getordpos.h>
#include <flames_slitdivide.h>
#include <flames_uves.h>
#include <flames_newmatrix.h>
#include <uves_msg.h>

#define LMAX(a,b) ((int32_t)(a) > (int32_t)(b) ? (int32_t)(a) : (int32_t)(b))
#define LMIN(a,b) ((int32_t)(a) < (int32_t)(b) ? (int32_t)(a) : (int32_t)(b))

flames_err slitdivide(allslitflats *slitflats, orderpos *ordpos, 
                      flames_frame *inframe, flames_frame *outframe)
{
    double **ordercentre=0;
    double **uplimit=0;
    double **lowlimit=0;
    frame_mask **badbuffer=0;
    double x=0;

    double minfibrepos=0;
    double maxfibrepos=0;
    double yshift=0;
    int32_t ix=0;
    int32_t iy=0;
    int32_t iorder=0;
    int32_t iframe=0;
    int32_t ifibre=0;
    int32_t nfibres=0;
    int32_t orderoffset=0;
    int32_t realfirstorder=0;
    int32_t reallastorder=0;
    char found=0;
    frame_data inframepixel=0;
    frame_data slitpixel_1=0;
    frame_data slitpixel_2=0;

    flames_err status=0;
    char output[200];

    slitFF *myslit=0;
    double *dvecbuf1=0;
    double *dvecbuf2=0;
    double *dvecbuf3=0;
    frame_data *fdvecbuf1=0;
    frame_data *fdvecbuf2=0;
    frame_data *fdvecbuf3=0;
    frame_data *fdvecbuf4=0;
    frame_data *fdvecbuf5=0;
    frame_data *fdvecbuf6=0;
    frame_mask *fmvecbuf1=0;
    frame_mask *fmvecbuf2=0;
    frame_mask *fmvecbuf3=0;
    frame_mask *fmvecbuf4=0;
    int32_t *lvecbuf1=0;
    int32_t *lvecbuf2=0;
    int32_t *lvecbuf3=0;
    int32_t *lvecbuf4=0;
    int32_t iorderixoffset=0;
    int32_t iorderixindex=0;
    int32_t iorderixstart=0;
    int32_t iorderixend=0;
    int32_t iorderm1ixindex=0;
    int32_t iorderp1ixindex=0;
    int32_t orderixoffset=0;
    int32_t offsetiorderixindex=0;
    int32_t iystart=0;
    int32_t iyend=0;
    int32_t iyixend=0;
    int32_t iyixindex=0;


    iyixend = (inframe->subrows*inframe->subcols)-1;
    memset(output, 0, 200);

    orderoffset = slitflats->tab_io_oshift-ordpos->tab_io_oshift;
    realfirstorder = ordpos->firstorder;
    if (orderoffset > 0) {
        sprintf(output, "Warning: the first %d order(s) is/are not covered in \
slit flats and will be dropped in the subsequent reduction.", orderoffset);
        SCTPUT(output);
        strcpy(output, "Consider recreating the slit flats structure using the \
current order definition table");
        SCTPUT(output);
        realfirstorder += orderoffset;
    }
    else if (orderoffset < 0) {
        sprintf(output, "Warning: the first %d order(s) is/are present in the \
slit flats, but were not detected in the current order table.", orderoffset);
        SCTPUT(output);
        strcpy(output, "Consider repeating the order/fibre positioning step to \
detect those lost orders");
        SCTPUT(output);
    }

    reallastorder = slitflats->lastorder+orderoffset;
    if (reallastorder < ordpos->lastorder) {
        sprintf(output, "Warning: the last %d order(s) is/are not covered in \
slit flats and will be dropped in the subsequent reduction.", 
ordpos->lastorder-reallastorder);
        SCTPUT(output);
        strcpy(output, "Consider recreating the slit flats structure using the \
current order definition table");
        SCTPUT(output);
    }
    else if (reallastorder > ordpos->lastorder) {
        sprintf(output, "Warning: the last %d order(s) is/are present in the \
slit flats, but were not detected in the current order table.", 
reallastorder-ordpos->lastorder);
        SCTPUT(output);
        strcpy(output, "Consider repeating the order/fibre positioning step to \
detect those lost orders");
        SCTPUT(output);
        reallastorder = ordpos->lastorder;
    }



    ordercentre = dmatrix(0,(ordpos->lastorder) -
                    (ordpos->firstorder),
                    0, inframe->subcols-1);
    uplimit = dmatrix(0, (ordpos->lastorder) -
                    (ordpos->firstorder),
                    0, inframe->subcols-1);
    lowlimit = dmatrix(0,(ordpos->lastorder) -
                    (ordpos->firstorder),
                    0, inframe->subcols-1);
    if (outframe->badpixel == inframe->badpixel) {
        badbuffer = fmmatrix(0, outframe->subrows-1, 0, outframe->subcols-1);
    }
    else {
        badbuffer = outframe->badpixel;
    }

    dvecbuf1 = ordercentre[0];
    dvecbuf2 = lowlimit[0];
    dvecbuf3 = uplimit[0];
    fdvecbuf1 = inframe->frame_array[0];
    fdvecbuf2 = inframe->frame_sigma[0];
    fmvecbuf1 = inframe->badpixel[0];
    fdvecbuf3 = outframe->frame_array[0];
    fdvecbuf4 = outframe->frame_sigma[0];
    fmvecbuf2 = outframe->badpixel[0];
    fmvecbuf3 = badbuffer[0];
    lvecbuf1 = slitflats->lowbound[0];
    lvecbuf2 = slitflats->highbound[0];

    /* find the minimum and maximum fibre position */
    nfibres = 0;
    /* find one lit fibre in inframe */
    for (ifibre = 0; ifibre <= ordpos->maxfibres-1; ifibre++) {
        if (inframe->fibremask[ifibre] == TRUE) {
            minfibrepos = maxfibrepos = ordpos->fibrepos[ifibre];
            nfibres++;
        }
    }

    if (nfibres == 0) {
        /* why the heck should I flatfield a frame with no lit fibres?
       anyway, just take the first and last lit fibre in ordpos then */
        for (ifibre = 0; ifibre <= ordpos->maxfibres-1; ifibre++) {
            if (ordpos->fibremask[ifibre] == TRUE) {
                minfibrepos = maxfibrepos = ordpos->fibrepos[ifibre];
                nfibres++;
            }
        }
        if (nfibres == 0) {
            /* no lit fibres in ordpos? there is something very wrong, bail out */
            return(MAREMMA);
        }
        else {
            /* take the first and last lit fibres in ordpos */
            for (ifibre = 0; ifibre <= ordpos->maxfibres-1; ifibre++) {
                if (ordpos->fibremask[ifibre] == TRUE) {
                    if (ordpos->fibrepos[ifibre] > maxfibrepos) {
                        maxfibrepos = ordpos->fibrepos[ifibre];
                    }
                    if (ordpos->fibrepos[ifibre] < minfibrepos) {
                        minfibrepos = ordpos->fibrepos[ifibre];
                    }
                }
            }
        }
    }
    else {
        /* find the first and last lit fibres in inframe */
        for (ifibre = 0; ifibre <= ordpos->maxfibres-1; ifibre++) {
            if (inframe->fibremask[ifibre] == TRUE) {
                if (ordpos->fibrepos[ifibre] > maxfibrepos) {
                    maxfibrepos = ordpos->fibrepos[ifibre];
                }
                if (ordpos->fibrepos[ifibre] < minfibrepos) {
                    minfibrepos = ordpos->fibrepos[ifibre];
                }
            }
        }
    }

    /* find the average yshift of inframe, if any */
    yshift = 0;

    if (inframe->nflats > 0) {
        for (iframe=0; iframe<=inframe->nflats-1; iframe++) {
            yshift += inframe->yshift[iframe];
        }
        yshift /= (double) inframe->nflats;
    }

    uves_msg("avg yshift=%f",yshift);

    /* to begin with, mark all pixels in outframe as bad, good ones will
     be marked as such when they are computed, later in the function */
    for (iyixindex=0; iyixindex<=iyixend; iyixindex++){
        fmvecbuf3[iyixindex] = 1;
    }

    /* This function will have to loop over orders, x's, fibres and y's. */
    /* loop over x */
    for (iorder = realfirstorder-ordpos->firstorder;
                    iorder<=(reallastorder-ordpos->firstorder);
                    iorder++) {
        iorderixoffset = iorder*inframe->subcols;
        double order = (double) (iorder+ordpos->firstorder);
        for (ix=0; ix<=(inframe->subcols-1); ix++) {
            iorderixindex = iorderixoffset+ix;
            /* compute x in world coordinates */
            x = inframe->substartx+(inframe->substepx)*((double) ix);
            /* now, find order distances and define which
	 pixels belong to which order, which is not trivial in case of 
	 overlapping orders */
            /* find the unshifted central position of this order at
	 this x */
            /* bail out if the function call return an error status */
            if ((status = get_ordpos(ordpos, order, x, dvecbuf1+iorderixindex))
                            !=NOERR) {
                return(status);
            }
            dvecbuf1[iorderixindex] += yshift;
            dvecbuf3[iorderixindex] = dvecbuf1[iorderixindex]+maxfibrepos+
                            ordpos->halfibrewidth;
            dvecbuf2[iorderixindex] = dvecbuf1[iorderixindex]+minfibrepos-
                            ordpos->halfibrewidth;
        }
    }
    /* do I have more than 1 order? */
    if(reallastorder > realfirstorder) {
        /* run a loop over the central orders */
        iorderixstart = (realfirstorder-ordpos->firstorder+1)*inframe->subcols;
        iorderixend = (reallastorder-ordpos->firstorder+1)*inframe->subcols-1;
        for (iorderixindex=iorderixstart; iorderixindex <= iorderixend;
                        iorderixindex++) {
            iorderm1ixindex = iorderixindex-inframe->subcols;
            if (dvecbuf1[iorderm1ixindex]<dvecbuf1[iorderixindex]) {
                if (dvecbuf3[iorderm1ixindex] > dvecbuf2[iorderixindex]) {
                    dvecbuf3[iorderm1ixindex] = dvecbuf2[iorderixindex] =
                                    (dvecbuf3[iorderm1ixindex]+dvecbuf2[iorderixindex])/2;
                }
            }
            else if (dvecbuf1[iorderm1ixindex] > dvecbuf1[iorderixindex]) {
                if (dvecbuf2[iorderm1ixindex] < dvecbuf3[iorderixindex]) {
                    dvecbuf2[iorderm1ixindex] = dvecbuf3[iorderixindex] =
                                    (dvecbuf2[iorderm1ixindex]+dvecbuf3[iorderixindex])/2;
                }
            }
            else {
                /* coincident order centres? bail out! */
                return MAREMMA;
            }
        }

        /* find the actual intervals */
        iorderixstart = (realfirstorder-ordpos->firstorder)*inframe->subcols;
        iorderixend = (reallastorder-ordpos->firstorder+1)*inframe->subcols-1;
        for (iorderixindex = iorderixstart; iorderixindex <= iorderixend;
                        iorderixindex++) {
            /* convert to pixel units and truncate to nearest integer */
            dvecbuf2[iorderixindex] = (dvecbuf2[iorderixindex]-inframe->substarty)/
                            inframe->substepy;
            dvecbuf2[iorderixindex] = floor(dvecbuf2[iorderixindex]+.5);
            dvecbuf3[iorderixindex] = (dvecbuf3[iorderixindex]-inframe->substarty)/
                            inframe->substepy;
            dvecbuf3[iorderixindex] = floor(dvecbuf3[iorderixindex]+.5);
        }
        iorderixstart = (realfirstorder-ordpos->firstorder)*inframe->subcols;
        iorderixend = (reallastorder-ordpos->firstorder)*inframe->subcols-1;
        for (iorderixindex=iorderixstart; iorderixindex<=iorderixend;
                        iorderixindex++) {
            iorderp1ixindex = iorderixindex+inframe->subcols;
            /* make sure that we have disjoint intervals */
            if (dvecbuf1[iorderixindex]<dvecbuf1[iorderp1ixindex]) {
                if((int32_t)(dvecbuf3[iorderixindex]) ==
                                (int32_t)(dvecbuf2[iorderp1ixindex])) {
                    (dvecbuf2[iorderp1ixindex])++;
                }
            }
            else if (dvecbuf1[iorderixindex] > dvecbuf1[iorderp1ixindex]) {
                if((int32_t)(dvecbuf2[iorderixindex]) ==
                                (int32_t)(dvecbuf3[iorderp1ixindex])) {
                    (dvecbuf2[iorderixindex])++;
                }
            }
            else {
                /* coincident order centres? bail out! */
                return MAREMMA;
            }
        }

    }

    else {
        /* I put here this code just in case, but what is one order only good
       for? */
        iorderixstart = (realfirstorder-ordpos->firstorder)*inframe->subcols;
        iorderixend = iorderixstart+inframe->subcols-1;
        for (iorderixindex=iorderixstart; iorderixindex<=iorderixend;
                        iorderixindex++) {
            /* convert to pixel units and truncate to nearest integer */
            dvecbuf2[iorderixindex] = (dvecbuf2[iorderixindex]-inframe->substarty)/
                            inframe->substepy;
            dvecbuf2[iorderixindex] = floor(dvecbuf2[iorderixindex]+.5);
            dvecbuf3[iorderixindex] = (dvecbuf3[iorderixindex]-inframe->substarty)/
                            inframe->substepy;
            dvecbuf3[iorderixindex] = floor(dvecbuf3[iorderixindex]+0.5);
        }
    }




    //Added check on bounds
    for (iframe=0; (iframe<=slitflats->nflats-1) ;iframe++) {
        myslit = slitflats->slit+iframe;
        lvecbuf3 = myslit->lowbound[0];
        lvecbuf4 = myslit->highbound[0];

    }
    /* now proceed with the final loop over orders which are both in the
     frame to be flatfielded and in the int32_t slit flat fields */
    orderixoffset = orderoffset*inframe->subcols;
    for (iorder=realfirstorder-ordpos->firstorder;
                    iorder<=(reallastorder-ordpos->firstorder);
                    iorder++) {
        iorderixoffset = iorder*inframe->subcols;
        /* now run over the pixels belonging to each order, provided that the
       slit FF is defined on them, and do the division */
        for (ix=0; ix<=(inframe->subcols-1); ix++) {
            iorderixindex = iorderixoffset+ix;
            offsetiorderixindex = iorderixindex-orderixoffset;
            if ((iystart=(int32_t)dvecbuf2[iorderixindex])<
                            lvecbuf1[offsetiorderixindex]) {
                iystart = lvecbuf1[offsetiorderixindex];
            }
            if ((iyend=(int32_t)dvecbuf3[iorderixindex])>
            lvecbuf2[offsetiorderixindex]) {
                iyend = lvecbuf2[offsetiorderixindex];
            }
            for (iy=iystart; iy<=iyend; iy++) {
                iyixindex = (iy*inframe->subcols)+ix;
                /* only bother to flatfield if the pixel is good */
                if(fmvecbuf1[iyixindex] == 0) {
                    /* now we must decide which of the int32_t slit FF frames must be
	     used for the division: take the first one which includes the 
	     pixel at hand */
                    found = 0;
                    for (iframe=0; (iframe<=slitflats->nflats-1) && (found == 0);
                                    iframe++) {
                        myslit = slitflats->slit+iframe;
                        lvecbuf3 = myslit->lowbound[0];
                        lvecbuf4 = myslit->highbound[0];
                        if((iy>=lvecbuf3[offsetiorderixindex]) &&
                                        (iy<=lvecbuf4[offsetiorderixindex])) {
                            /* ok, it is in this frame */
                            found = 1;
                            fmvecbuf4 = myslit->badpixel[0];
                            /* only divide if the slit FF is good here */
                            if (fmvecbuf4[iyixindex]==0) {
                                /* this pixel is good in the outframe */
                                fmvecbuf3[iyixindex] = 0;
                                fdvecbuf5 = myslit->data[0];
                                fdvecbuf6 = myslit->sigma[0];
                                inframepixel = fdvecbuf1[iyixindex];
                                slitpixel_1 = 1./fdvecbuf5[iyixindex];
                                slitpixel_2 = slitpixel_1*slitpixel_1;

                                /* set the sigma of the normalised pixel value */
                                fdvecbuf4[iyixindex] = fdvecbuf2[iyixindex]*slitpixel_2+
                                                fdvecbuf6[iyixindex]*(inframepixel*inframepixel)*
                                                (slitpixel_2*slitpixel_2);
                                /* set the normalised pixel value */
                                fdvecbuf3[iyixindex] = inframepixel*slitpixel_1;
                            }
                        }
                    }
                }
            }
        }
    }

    /* although this is strictly unneeded, explicitly zero all frame_array
     and frame_sigma values for bad pixels (it helps purify checks) */
    if (fmvecbuf3 != fmvecbuf2) {
        memcpy(fmvecbuf2, fmvecbuf3,
                        outframe->subrows*outframe->subcols*sizeof(frame_mask));
    }
    iyixend = (outframe->subrows*outframe->subcols)-1;
    for (iyixindex=0; iyixindex<=iyixend; iyixindex++) {
        if (fmvecbuf3[iyixindex] != 0) {
            fdvecbuf3[iyixindex] = 0;
            fdvecbuf4[iyixindex] = 0;
        }
    }

    /* free temporary memory allocations */
    free_dmatrix(ordercentre,0,(ordpos->lastorder) -
                 (ordpos->firstorder),
                 0, inframe->subcols-1);
    free_dmatrix(uplimit,0,(ordpos->lastorder) -
                 (ordpos->firstorder),
                 0, inframe->subcols-1);
    free_dmatrix(lowlimit,0,(ordpos->lastorder) -
                 (ordpos->firstorder),
                 0, inframe->subcols-1);
    if (outframe->badpixel == inframe->badpixel) {
        /* I used a temporary badpixel buffer, free it */
        free_fmmatrix(badbuffer, 0, outframe->subrows-1, 0, outframe->subcols-1);
    }

    return(NOERR);

}
