/*===========================================================================
  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 order=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;
    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);

}
