#include "ninox.h"

// Keep a copy of the frames as they slide past
static struct Image **history = NULL;
static int Width = 0;
static int Height = 0;
static int Depth = 0;

static int Replaced = 0;

struct _minmax
	{
	int min;
	int max;
	int avg;
	};

static struct _minmax *Window = NULL;

void
init_streamfilter()
	{
	if (! StreamFilter) {
	   return;
	   }

	// Just throw them away, forget about the small leak
	if (history) {free(history); history = NULL;}
	if (Window) { free(Window); Window = NULL; }

	history = (struct Image **)ZeroMalloc(sizeof(struct Image *) * StreamFilter_History);
	if (! history) {
	   Print("Error allocating memory for StreamFilter\n");
	   exit(1);
	   }

	Width = Height = Depth = 0;
	
	return;
	}

struct Image *
CloneImage(struct Image *img)
	{
	struct Image *nImg = CreateImage();
        struct fits_info *nfi = ZeroMalloc(sizeof(struct fits_info));
        struct fits_info *fi = (struct fits_info *)img->info;
	int bytes;

        nImg->type=		img->type;
        nImg->width  = 		img->width;
	nImg->dst_width  = 	img->dst_width;

        nImg->height = 		img->height;
        nImg->dst_height = 	img->dst_height;
        nImg->depth = 		img->depth;

        nfi->bzero = 		fi->bzero;
        nfi->bscale = 		fi->bscale;

	if (fi->header) {
	   Print("Copying header %x\n",fi->header);
           nfi->header = 	ZeroMalloc(2880);
           memcpy(nfi->header,fi->header,2880);
	   }
	else
	   fi->header = 	NULL;

        nImg->info = nfi;
        bytes = img->width * img->height * img->depth/8;

        nImg->data = (unsigned char *)Malloc(bytes);
	memcpy(nImg->data,img->data,bytes);

        nImg->src_fname = strdup(img->src_fname);
        nImg->dst_fname = strdup(img->dst_fname);

        nImg->cutout = img->cutout;
        nImg->m = img->m;

        return nImg;
	}

static int
fill_window(struct Image *img)
	{
	int i;

	if (! Window) {
	   int npix = img->width * img->height;
	   Window = (struct _minmax *)ZeroMalloc(sizeof(struct _minmax) * npix);
	   if (! Window) {
		Print("StreamFilter: Error allocating %d pixels for minimax window\n",npix);
		exit(1);
		}
	   }

	for(i=0; i<StreamFilter_History; ++i)
	   if (! history[i]) {
		if (i==0) {
		   Width = img->width;
		   Height = img->height;
	  	   Depth = img->depth;
		   }
		history[i] = CloneImage(img);
		return 1;
		}
	return 0;
	}

static int
recalculate_minmax()
	{
	int i,h,total;
	int npixels = Width * Height;

	for(i=0; i< npixels; ++i) {
	    Window[i].min = 1000000;
	    Window[i].max = -1;
	    }

	switch(Depth) {
	   case 8:
		for(i=0; i<npixels; ++i) {
		   for(total=h=0; h<StreamFilter_History; ++h) {
		      int v = history[h]->data[i];
		      if (v < Window[i].min) Window[i].min = v;
		      if (v > Window[i].max) Window[i].max = v;
		      total += v;
		      }
		   Window[i].avg = total/h;
		   }

		break;
	   case 16:
		for(i=0; i<npixels; ++i) {
		   for(total=h=0; h<StreamFilter_History; ++h) {
		      unsigned short *uptr  = (unsigned short *)(history[h]->data);
		      int v = uptr[i];
		      if (v < Window[i].min) Window[i].min = v;
		      if (v > Window[i].max) Window[i].max = v;
		      total += v;
		      }
		   Window[i].avg = total/h;
		   }

		break;
	   }

	return 1;
	}

static unsigned short
_pixel_pop(int o, int p)
	{
	int rval = p;
	if (p < Window[o].min || p > Window[o].max) {
	   rval = (p + Window[o].avg)/2;
	   ++Replaced;
	   }

// pixel (330,330)
//if (o == 222090)
//Print("%d <-> [ %d (%d) %d] = %d\n",p,Window[o].min,Window[o].avg,Window[o].max,rval);
	return rval;
	}

// compare each pixel in this image to the pixels in the history
// and if this image has the largest or smallest then replace it with the 
// average
static int
_filter_pop(struct Image *img)
	{
	unsigned char *ptr = (unsigned char *)img->data;
	unsigned short *uptr = (unsigned short *)img->data;
	int i,npixels = img->width * img->height;
	unsigned int threshhold = ThreshHold;

	threshhold=0;

	recalculate_minmax();

	Replaced = 0;

	switch(img->depth) {
	   case 8:
		for(i=0; i<npixels; ++i) 
		   if (ptr[i]>threshhold) ptr[i] = _pixel_pop(i,(int)ptr[i]);
		break;
	   case 16:
		threshhold <<= 8;
		for(i=0; i<npixels; ++i) 
		   if (uptr[i]>threshhold) uptr[i] = _pixel_pop(i,(int)uptr[i]);
		break;
	   }

	Print("Replaced %d/%d pixels\n",Replaced,npixels);

	return 1;
	}

static int
getpixel(struct Image *img, int x, int y)
	{
	int width = img->width;
	int height = img->height;
	int depth = img->depth;
	unsigned short *udata;

	switch(depth) {
	   case 8:
		return img->data[y * width + x];
		break;
	   case 16:
		udata = (unsigned short *)img->data;
		return udata[y * width + x];
	 	break;
	   }
	return 0;
	}

int
do_StreamFilter(struct Image *img)
	{
	int i;
	struct Image *cp;

	if (history == NULL) init_streamfilter();

	// If there is an empty slot in the window then add the current frame and
	// return. This is only relevant when starting out
	if (fill_window(img)) return(0);

	// must all be the same
	if (img->width != Width || img->height != Height || img->depth != Depth) {
	   Print("cannot apply filter, images have different size or depth to reference %dx%dx%d\n",Width,Height,Depth);
	   return 0;
	   }

	cp = CloneImage(img);
	_filter_pop(img);

	// slide the window
	DestroyImage(history[0]);
	for(i=0; i<StreamFilter_History-1; ++i)
	   history[i] = history[i+1];
	history[i] = cp;
	
	return(1);
	}
