#include <math.h>

#include "amdms.h"
#include "amdmsFits.h"

#define amdmsEPS  0.001

#define MIN(x,y) ((x) < (y) ? (x) : (y))
#define MAX(x,y) ((x) > (y) ? (x) : (y))

static void amdmsCalcLimit(amdmsLIMIT *spec, amdmsPIXEL mean, amdmsPIXEL var, amdmsPIXEL *limit, amdmsPIXEL *ref);

static amdmsCOMPL amdmsDoBadPixelPass(amdmsALGO_BAD_PIXEL_ENV *env,
				      amdmsDATA *data,
				      int dataFlag);
static amdmsCOMPL amdmsExportBadPixelMap(FILE *outfile, amdmsDATA *bpm);
static amdmsCOMPL amdmsCreateBadPixelMap(amdmsFITS *outfile,
					 amdmsFITS_FLAGS flags,
					 amdmsDATA *bpm);
static amdmsCOMPL amdmsCreateFuzzyMap(amdmsALGO_BAD_PIXEL_ENV *env,
				      amdmsFITS *outfile,
				      amdmsFITS_FLAGS flags);

amdmsCOMPL amdmsCreateBadPixelAlgo(amdmsALGO_BAD_PIXEL_ENV **env)
{
  amdmsALGO_ENV             *henv = NULL;
  amdmsALGO_BAD_PIXEL_ENV   *hhenv = NULL;

  /* allocate the structure if neccessary */
  if (*env == NULL) {
    hhenv = (amdmsALGO_BAD_PIXEL_ENV*)calloc(1L, sizeof(amdmsALGO_BAD_PIXEL_ENV));
    if (hhenv == NULL) {
      return amdmsFAILURE;
    }
    henv = &(hhenv->env);
    /* call the initialization of a simple algorithm environment */
    if (amdmsCreateAlgo(&henv) != amdmsSUCCESS) {
      free(hhenv);
      return amdmsFAILURE;
    }
    hhenv->allocated = 1;
    *env = hhenv;
  } else {
    /* call the initialization of a simple algorithm environment */
    hhenv = *env;
    henv = &(hhenv->env);
    if (amdmsCreateAlgo(&henv) != amdmsSUCCESS) {
      return amdmsFAILURE;
    }
    hhenv->allocated = 0;
  }
  /* initialize only the additional members */
  hhenv->nuicFlag = amdmsFALSE;
  hhenv->fuzzyCount = 1;
  hhenv->fuzzyLimit = 1.0;
  amdmsInitData(&(hhenv->fuzzyMap));
  hhenv->winFlag = amdmsFALSE;
  hhenv->winInnerSize = 0;
  hhenv->winOuterSize = 4;
  hhenv->nIter = 1;
  amdmsInitLimitSetup(&(hhenv->limits));
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsDestroyBadPixelAlgo(amdmsALGO_BAD_PIXEL_ENV **env)
{
  amdmsALGO_BAD_PIXEL_ENV   *henv = NULL;
  amdmsALGO_ENV             *hhenv = NULL;

  if (env == NULL) {
    return amdmsFAILURE;
  }
  if (*env == NULL) {
    return amdmsSUCCESS;
  }
  henv = *env;
  amdmsFreeData(&(henv->fuzzyMap));
  amdmsFreeLimitSetup(&(henv->limits));
  hhenv = &(henv->env);
  amdmsDestroyAlgo(&hhenv);
  if (henv->allocated) {
    henv->allocated = amdmsFALSE;
    free(henv);
    *env = NULL;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsDoBadPixel(amdmsALGO_BAD_PIXEL_ENV *env)
{
  amdmsCOMPL        retVal = amdmsSUCCESS;
  int               iInput;
  amdmsFITS        *infile = NULL;
  int               iOutput;
  amdmsFITS        *outfile = NULL;
  amdmsDATA         data;
  amdmsDATA         hdata;
  int               iPixel;
  int               nPixels;
  FILE             *fp = NULL;

  amdmsInfo(__FILE__, __LINE__, "amdmsDoBadPixel(...)");
  if (env->env.inFiles.nNames == 0) {
    /* no filename(s) given but we need exactly one file */
    amdmsError(__FILE__, __LINE__, "no filename given");
    return amdmsFAILURE;
  }
  if (amdmsReadAllMaps(&(env->env.calib)) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsAllocateData(&(env->fuzzyMap), env->env.calib.bpmData.nx, env->env.calib.bpmData.ny) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  amdmsSetData(&(env->fuzzyMap), 0.0);
  amdmsInitData(&data);
  amdmsInitData(&hdata);
  for (iInput = 0; (iInput < env->env.inFiles.nNames) && (retVal == amdmsSUCCESS); iInput++) {
    if (amdmsOpenFitsFile(&infile, env->env.inFiles.names[iInput]) != amdmsSUCCESS) {
      return amdmsFAILURE;
    }
    if (amdmsOpenData(infile, env->env.inFiles.flags[iInput], 1) != amdmsSUCCESS) {
      amdmsCloseFitsFile(&infile);
      return amdmsFAILURE;
    }
    amdmsRecalcStripes(&(env->env.stripes), infile->nx, infile->ny);
    switch (env->env.inFiles.flags[iInput].content) {
    case amdmsPIXEL_STAT_CONTENT:
      amdmsNotice(__FILE__, __LINE__, "  using PIXEL_STAT in file %s", env->env.inFiles.names[iInput]);
      retVal = amdmsReadData(infile, &data, amdmsPIXEL_STAT_MEAN_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &hdata, amdmsPIXEL_STAT_VAR_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      if (env->nuicFlag) {
	amdmsCleanUpFlatfield(&(env->env.calib), &(env->env.stripes), &data, &hdata);
      }
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_PS_MEAN_PIXEL);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &hdata, amdmsUSE_PS_VAR_PIXEL);
      break;
    case amdmsPIXEL_BIAS_CONTENT:
      amdmsNotice(__FILE__, __LINE__, "  using PIXEL_BIAS in file %s", env->env.inFiles.names[iInput]);
      retVal = amdmsReadData(infile, &data, amdmsPIXEL_BIAS_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_PIXEL_BIAS);
      break;
    case amdmsFLATFIELD_CONTENT:
      amdmsNotice(__FILE__, __LINE__, "  using FLATFIELD in file %s", env->env.inFiles.names[iInput]);
      retVal = amdmsReadData(infile, &data, amdmsFLATFIELD_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FLATFIELD);
      break;
    case amdmsFLATFIELD_FIT_CONTENT:
    case amdmsPTC_CONTENT:
      amdmsNotice(__FILE__, __LINE__, "  using FIT or PTC in file %s", env->env.inFiles.names[iInput]);
      retVal = amdmsReadData(infile, &data, amdmsFIT_DATA_BIAS_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_DATA_BIAS);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_DATA_SATURATION_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_DATA_SATURATION);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_CHI_SQR_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_CHI_SQR);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_ABS_DIST_SQR_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_ABS_DIST_SQR);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_REL_DIST_SQR_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_REL_DIST_SQR);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_LOWER_LIMIT_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_LOWER_LIMIT);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_UPPER_LIMIT_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_UPPER_LIMIT);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_A0_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_A0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_A0_STDEV_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_A0_STDEV);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_A1_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_A1);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_A1_STDEV_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_A1_STDEV);
      break;
    case amdmsCONVERSION_FACTOR_CONTENT:
      amdmsNotice(__FILE__, __LINE__, "  using CONVERSION_FACTOR in file %s", env->env.inFiles.names[iInput]);
      retVal = amdmsReadData(infile, &data, amdmsCONVERSION_FACTOR_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_CONVERSION_FACTOR);
      break;
    case amdmsREADOUT_NOISE_CONTENT:
      amdmsNotice(__FILE__, __LINE__, "  using READOUT_NOISE in file %s", env->env.inFiles.names[iInput]);
      retVal = amdmsReadData(infile, &data, amdmsREADOUT_NOISE_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_READOUT_NOISE);
      break;
    case amdmsPHOTON_NOISE_CONTENT:
      amdmsNotice(__FILE__, __LINE__, "  using PHOTON_NOISE in file %s", env->env.inFiles.names[iInput]);
      retVal = amdmsReadData(infile, &data, amdmsPHOTON_NOISE_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_PHOTON_NOISE);
      break;
    case amdmsNONLINEARITY_CONTENT:
      amdmsNotice(__FILE__, __LINE__, "  using NONLINEARITY in file %s", env->env.inFiles.names[iInput]);
      retVal = amdmsReadData(infile, &data, amdmsFIT_DATA_BIAS_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_DATA_BIAS);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_DATA_SATURATION_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_DATA_SATURATION);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_CHI_SQR_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_CHI_SQR);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_ABS_DIST_SQR_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_ABS_DIST_SQR);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_REL_DIST_SQR_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_REL_DIST_SQR);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_LOWER_LIMIT_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_LOWER_LIMIT);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_UPPER_LIMIT_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_UPPER_LIMIT);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_A0_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_A0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_A0_STDEV_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_A0_STDEV);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_A1_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_A1);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_A1_STDEV_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_A1_STDEV);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_A2_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_A2);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_A2_STDEV_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_A2_STDEV);
      break;
    case amdmsDARK_CURRENT_CONTENT:
      amdmsNotice(__FILE__, __LINE__, "  using DARK_CURRENT in file %s", env->env.inFiles.names[iInput]);
      retVal = amdmsReadData(infile, &data, amdmsFIT_DATA_BIAS_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_DATA_BIAS);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_DATA_SATURATION_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_DATA_SATURATION);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_CHI_SQR_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_CHI_SQR);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_ABS_DIST_SQR_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_ABS_DIST_SQR);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_REL_DIST_SQR_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_REL_DIST_SQR);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_LOWER_LIMIT_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_LOWER_LIMIT);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_UPPER_LIMIT_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_UPPER_LIMIT);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_A0_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_A0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_A0_STDEV_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_A0_STDEV);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_A1_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_A1);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsReadData(infile, &data, amdmsFIT_A1_STDEV_ROW, 0);
      if (retVal != amdmsSUCCESS) break;
      retVal = amdmsDoBadPixelPass(env, &data, amdmsUSE_FIT_A1_STDEV);
      break;
    default:
      amdmsError(__FILE__, __LINE__,
		"unsupported file content (%d)",
		env->env.inFiles.flags[iInput].content);
      retVal = amdmsFAILURE;
    }
    amdmsCloseFitsFile(&infile);
  }
  /* compute which pixels are bad pixels from fuzzy map */
  nPixels = env->env.calib.bpmData.nx*env->env.calib.bpmData.ny;
  env->env.calib.nGoodPixels = nPixels;
  for (iPixel = 0; iPixel < nPixels; iPixel++) {
    if (env->fuzzyMap.data[iPixel] >= env->fuzzyCount) {
      env->env.calib.bpmData.data[iPixel] = amdmsBAD_PIXEL;
    }
    if (env->env.calib.bpmData.data[iPixel] == amdmsBAD_PIXEL) {
      env->env.calib.nGoodPixels--;
    }
  }
  amdmsNotice(__FILE__, __LINE__, "DCM: GOODPIX = %d", env->env.calib.nGoodPixels);
  amdmsNotice(__FILE__, __LINE__, "DCM: BADPIX  = %d", nPixels - env->env.calib.nGoodPixels);
  /* reopen the first input file for the header keywords */
  if (amdmsOpenFitsFile(&infile, env->env.inFiles.names[0]) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsOpenData(infile, env->env.inFiles.flags[0], 1) != amdmsSUCCESS) {
    amdmsCloseFitsFile(&infile);
    return amdmsFAILURE;
  }
  /* generate all outputs */
  for (iOutput = 0; (iOutput < env->env.outFiles.nNames) && (retVal == amdmsSUCCESS); iOutput++) {
    if (env->env.outFiles.flags[iOutput].format == amdmsTEXT_FORMAT) {
      fp = fopen(env->env.outFiles.names[iOutput], "w");
      if (fp != NULL) {
	amdmsExportBadPixelMap(fp, &(env->env.calib.bpmData));
	fclose(fp);
      }
      continue;
    }
    retVal = amdmsCreateFitsFile(&outfile, env->env.outFiles.names[iOutput]);
    if (retVal != amdmsSUCCESS) break;
    retVal = amdmsCopyHeader(outfile, infile);
    if (retVal != amdmsSUCCESS) {
      amdmsCloseFitsFile(&outfile);
      break;
    }
    amdmsSetRegions(outfile, infile);
    switch (env->env.outFiles.flags[iOutput].content) {
    case amdmsBAD_PIXEL_CONTENT:
      retVal = amdmsCreateBadPixelMap(outfile, env->env.outFiles.flags[iOutput], &(env->env.calib.bpmData));
      break;
    case amdmsFUZZY_CONTENT:
      retVal = amdmsCreateFuzzyMap(env, outfile, env->env.outFiles.flags[iOutput]);
      break;
    default:
      amdmsError(__FILE__, __LINE__,
		 "unsupported file content (%d)",
		 env->env.outFiles.flags[iOutput].content);
      retVal = amdmsFAILURE;
    }
    retVal = amdmsCloseFitsFile(&outfile);
  }
  amdmsCloseFitsFile(&infile);

  if (&data != NULL) {  
     amdmsFreeData(&(data));
  }
  if (&hdata != NULL) {  
     amdmsFreeData(&(hdata));
  }

  return retVal;
}

amdmsCOMPL amdmsDoBadPixelPass(amdmsALGO_BAD_PIXEL_ENV *env, amdmsDATA *data, int dataFlag)
{
  int          iHS;
  int          iVS;
  int          iLimit;
  int          iX;
  int          iY;
  amdmsPIXEL   meanValues;
  amdmsPIXEL   varValues;
  amdmsPIXEL   sigVar;
  amdmsLIMIT  *limitSpec = NULL;
  amdmsPIXEL   limit;
  amdmsPIXEL   ref;
  int          flags;
  int          x;
  int          y;
  int          width;
  int          height;
  amdmsPIXEL   fv;
  int          iPixel;
  int          nPixels;
  int          nNewBadPixels;
  int          iIter;
  int          hNIter;

  amdmsInfo(__FILE__, __LINE__, "    amdmsDoBadPixelPass()");
  if ((env == NULL) && (data == NULL)) {
    return amdmsFAILURE;
  }
  nPixels = data->nx*data->ny;
  for (iHS = 0; iHS < env->env.stripes.nHStripes; iHS++) {
    for (iVS = 0; iVS < env->env.stripes.nVStripes; iVS++) {
      flags = env->env.stripes.hStripes[iHS].flags & env->env.stripes.vStripes[iVS].flags;
      if ((flags & dataFlag) == 0) continue;
      x = env->env.stripes.vStripes[iVS].pos;
      y = env->env.stripes.hStripes[iHS].pos;
      width = env->env.stripes.vStripes[iVS].size;
      height = env->env.stripes.hStripes[iHS].size;
      amdmsInfo(__FILE__, __LINE__,
		"      area (%d .. %d, %d .. %d)",
		x, x + width - 1, y, y + height - 1);
      for (iLimit = 0; iLimit < env->limits.nLimits; iLimit++) {
	limitSpec = &(env->limits.limits[iLimit]);
	if ((limitSpec->flag & dataFlag) == 0) continue;
	switch (limitSpec->type) {
	case amdmsSIGMA_LOWER_LIMIT:
	case amdmsSIGMA_EQUAL_LIMIT:
	case amdmsSIGMA_UPPER_LIMIT:
	  hNIter = env->nIter;
	  break;
	default:
	  hNIter = 1;
	}
	for (iIter = 0; iIter < hNIter; iIter++) {
	  nNewBadPixels = 0;
	  amdmsCalcStat(&(env->env.calib), data, 0, x, y, width, height, &meanValues, &varValues);
	  sigVar = sqrt(varValues);
	  amdmsInfo(__FILE__, __LINE__,
		   "        iteration #%d: mean = %12.4f var = %12.4f sig = %12.4f",
		   iIter, meanValues, varValues, sigVar);
	  amdmsCalcLimit(limitSpec, meanValues, varValues, &limit, &ref);
	  amdmsInfo(__FILE__, __LINE__, "          limit = %12.4f", limit);
	  for (iY = y; iY < (y + height); iY ++) {
	    for (iX = x; iX < (x + width); iX++) {
	      iPixel = data->nx*iY + iX;
	      if (env->env.calib.bpmData.data[iPixel] != amdmsGOOD_PIXEL) continue;
	      if (env->winFlag) {
		/* we have to update the limits according to the neighbourhood */
		amdmsCalcStatBox(&(env->env.calib), data, 0,
				x, y, width, height,
				iX, iY, env->winInnerSize, env->winOuterSize,
				&meanValues, &varValues);
		amdmsCalcLimit(limitSpec, meanValues, varValues, &limit, &ref);
	      }
	      switch (limitSpec->type) {
	      case amdmsABS_LOWER_LIMIT:
	      case amdmsREL_LOWER_LIMIT:
	      case amdmsSIGMA_LOWER_LIMIT:
		fv = (data->data[iPixel] - ref)/(limit - ref);
		fv = MAX(0.0, MIN(fv, 1.0));
		if (data->data[iPixel] >= limit) {
		  /* the current pixel is a bad pixel */
		  env->env.calib.bpmData.data[iPixel] = amdmsBAD_PIXEL;
		  env->fuzzyMap.data[iPixel] += env->fuzzyCount;
		  nNewBadPixels++;
		} else if (fv > env->fuzzyLimit) {
		  /* increase the fuzzy counter */
		  env->fuzzyMap.data[iPixel] += 1.0;
		}
		break;
	      case amdmsABS_EQUAL_LIMIT:
	      case amdmsREL_EQUAL_LIMIT:
	      case amdmsSIGMA_EQUAL_LIMIT:
		if (fabs(data->data[iPixel]- limit) < amdmsEPS) {
		  /* the current pixel is a bad pixel */
		  env->env.calib.bpmData.data[iPixel] = amdmsBAD_PIXEL;
		  nNewBadPixels++;
		  env->fuzzyMap.data[iPixel] += env->fuzzyCount;
		}
		break;
	      case amdmsABS_UPPER_LIMIT:
	      case amdmsREL_UPPER_LIMIT:
	      case amdmsSIGMA_UPPER_LIMIT:
		fv = (ref - data->data[iPixel])/(ref - limit);
		fv = MAX(0.0, MIN(fv, 1.0));
		if (data->data[iPixel] <= limit) {
		  /* the current pixel is a bad pixel */
		  env->env.calib.bpmData.data[iPixel] = amdmsBAD_PIXEL;
		  nNewBadPixels++;
		  env->fuzzyMap.data[iPixel] += env->fuzzyCount;
		} else if (fv > env->fuzzyLimit) {
		  /* increase the fuzzy counter */
		  env->fuzzyMap.data[iPixel] += 1.0;
		}
		break;
	      default:;
	      }
	    }
	  }
	  amdmsInfo(__FILE__, __LINE__, "          %d bad pixels added", nNewBadPixels);
	  if (nNewBadPixels == 0) break;
	}
      }
    }
  }
  return amdmsSUCCESS;
}

void amdmsCalcLimit(amdmsLIMIT *spec, amdmsPIXEL mean, amdmsPIXEL var, amdmsPIXEL *limit, amdmsPIXEL *ref)
{
  switch (spec->type) {
  case amdmsABS_LOWER_LIMIT:
  case amdmsABS_EQUAL_LIMIT:
  case amdmsABS_UPPER_LIMIT:
    *limit = spec->value;
    *ref = spec->ref;
    break;
  case amdmsREL_LOWER_LIMIT:
  case amdmsREL_EQUAL_LIMIT:
  case amdmsREL_UPPER_LIMIT:
    *limit = spec->value*mean;
    *ref = spec->ref*mean;
    break;
  case amdmsSIGMA_LOWER_LIMIT:
  case amdmsSIGMA_EQUAL_LIMIT:
  case amdmsSIGMA_UPPER_LIMIT:
    *limit = mean + spec->value*sqrt(var);
    *ref = mean + spec->ref*sqrt(var);
    break;
  default:
    *limit = 0.0;
    *ref = 0.0;
  }
}

amdmsCOMPL amdmsExportBadPixelMap(FILE *outfile, amdmsDATA *bpm)
{
  int      iPixel;
  int      nPixels = bpm->nx*bpm->ny;
  int      x, y;

  for (iPixel = 0; iPixel < nPixels; iPixel++) {
    if (bpm->data[iPixel] != amdmsBAD_PIXEL) continue;
    x = iPixel%bpm->ny;
    y = iPixel/bpm->ny;
    fprintf(outfile, "%d %d\n", x, y);
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsCreateBadPixelMap(amdmsFITS *outfile, amdmsFITS_FLAGS flags, amdmsDATA *bpm)
{
  int      nPixels = bpm->nx*bpm->ny;
  int      nGoodPixels = nPixels;
  int      nBadPixels = 0;
  int      iPixel;

  for (iPixel = 0; iPixel < nPixels; iPixel++) {
    if (bpm->data[iPixel] == amdmsBAD_PIXEL) {
      nGoodPixels--;
      nBadPixels++;
    }
  }
  if (amdmsUpdateKeywordString(outfile,
			      "HIERARCH ESO DPR CATG", "CALIB",
			      "Observation category")
      != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsUpdateKeywordString(outfile,
			      "HIERARCH ESO DPR TECH", "INTERFEROMETRY",
			      "Observation technique")
      != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsUpdateKeywordString(outfile,
			      "HIERARCH ESO DPR TYPE", "BADPIX",
			      "Observation type")
      != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsUpdateKeywordInt(outfile,
			   "GOODPIX", nGoodPixels,
			   "number of good pixels")
      != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsUpdateKeywordInt(outfile,
			   "BADPIX", nBadPixels,
			   "number of bad pixels")
      != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsCreateData(outfile, flags, amdmsBAD_PIXEL_NROWS, 1) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  bpm->index = 1.0;
  if (amdmsWriteData(outfile, bpm, amdmsBAD_PIXEL_ROW, 0) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsCreateFuzzyMap(amdmsALGO_BAD_PIXEL_ENV *env, amdmsFITS *outfile, amdmsFITS_FLAGS flags)
{
  int      nPixels = env->env.calib.bpmData.nx*env->env.calib.bpmData.ny;

  if (amdmsUpdateKeywordString(outfile,
			      "HIERARCH ESO DPR CATG", "CALIB",
			      "Observation category")
      != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsUpdateKeywordString(outfile,
			      "HIERARCH ESO DPR TECH", "INTERFEROMETRY",
			      "Observation technique")
      != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsUpdateKeywordString(outfile,
			      "HIERARCH ESO DPR TYPE", "BADPIX",
			      "Observation type")
      != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsUpdateKeywordInt(outfile,
			   "GOODPIX", env->env.calib.nGoodPixels,
			   "number of good pixels")
      != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsUpdateKeywordInt(outfile,
			   "BADPIX", nPixels - env->env.calib.nGoodPixels,
			   "number of bad pixels")
      != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsCreateData(outfile, flags, amdmsFUZZY_NROWS, 1) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  env->fuzzyMap.index = 1.0;
  if (amdmsWriteData(outfile, &(env->fuzzyMap), amdmsFUZZY_DATA_ROW, 0) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsInitLimitSetup(amdmsLIMIT_SETUP *setup)
{
  int iLimit;

  if (setup == NULL) {
    return amdmsFAILURE;
  }
  setup->nLimits = 0;
  for (iLimit = 0; iLimit < amdmsMAX_LIMITS; iLimit++) {
    setup->limits[iLimit].flag = 0;
    setup->limits[iLimit].type = 0;
    setup->limits[iLimit].value = 0.0;
    setup->limits[iLimit].ref = 0.0;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsFreeLimitSetup(amdmsLIMIT_SETUP *setup)
{
  int iLimit;

  if (setup == NULL) {
    return amdmsFAILURE;
  }
  setup->nLimits = 0;
  for (iLimit = 0; iLimit < amdmsMAX_LIMITS; iLimit++) {
    setup->limits[iLimit].flag = 0;
    setup->limits[iLimit].type = 0;
    setup->limits[iLimit].value = 0.0;
    setup->limits[iLimit].ref = 0.0;
  }
  return amdmsSUCCESS;
}

