#include "ninox.h"

static double *Rdata = NULL;  // This is our buffer
static double *Gdata = NULL;  // This is our buffer
static double *Bdata = NULL;  // This is our buffer

static int    *RCount = NULL;  // array of counters for red, one per pixel
static int    *GCount = NULL;  // array of counters for green , one per pixel
static int    *BCount = NULL;  // array of counters for blue, one per pixel

static int Frames =0;
static int NPixels=0;
static int Width,Height,Depth;
static int Bpp=0;
static int Dstwidth, Dstheight;

static int
FrameChannel(char *fname)
        {
        char *ptr = fname + strlen(fname)-1;
        int ch = atoi(ptr-4);

        if (ch<0 || ch>6) ch = 0;

        return ch;
        }

// must call this between stacking runs
int
stack_init(void)
	{
	if (Rdata) {free(Rdata); Rdata = NULL;}
	if (Gdata) {free(Gdata); Gdata = NULL;}
	if (Bdata) {free(Bdata); Bdata = NULL;}
	if (RCount) {free(RCount); RCount = NULL;}
	if (GCount) {free(GCount); GCount = NULL;}
	if (BCount) {free(BCount); BCount = NULL;}

	Bpp=NPixels=Frames=0;
	}

// channel is an integer, used to select the colour
// 2=red 3=green 4=blue
int
stack_frame(struct Image *img)
	{
	int width = img->width;
	int height = img->height;
	int depth = img->depth;
	unsigned char *data = img->data;
	int i,b,g,r,o;
	int threshhold = 1;  // anything not exactly black is ok
	unsigned short *sdata = (unsigned short *)data;
	int channel;
	double v;

	if (depth != 16 && depth != 8 && depth != 24) {
	   Print("-stack only supported for 8/16/24 bpp data\n");
	   return 0;
	   }

	if (Rdata == NULL) {
	   // First frame, allocate buffer
	   Width = width; Height=height; Depth = depth;
	   Bpp = Depth/8;
	   Dstwidth = img->dst_width;
	   Dstheight = img->dst_height;

	   NPixels = width * height;
	   Rdata = (double *)ZeroMalloc(sizeof(double) * NPixels);
	   Gdata = (double *)ZeroMalloc(sizeof(double) * NPixels);
	   Bdata = (double *)ZeroMalloc(sizeof(double) * NPixels);

	   RCount = (int *)   ZeroMalloc(sizeof(int) * NPixels);
	   GCount = (int *)   ZeroMalloc(sizeof(int) * NPixels);
	   BCount = (int *)   ZeroMalloc(sizeof(int) * NPixels);
	   }
	else // Check that image params have not changed
	   if (Depth != depth || Width != width || Height != height) {
		Print("stack_frame: Image data has changed! Now (%dx%dx%d), was (%dx%dx%d).\n",
			width,height,depth,Width,Height,Depth);
		exit(0);
		}

	channel = FrameChannel(img->src_fname);

	Print("stack to channel %d from %s\n",channel,img->src_fname);

	// Stack the frame
	switch(depth) {
	   case 8:
		switch(channel) {
		   default:
		   case 2:
			for(i=0; i<NPixels; ++i) {
	   	   	   v = data[i];
		   	   if (v>=threshhold) {
	   	      	   Rdata[i] += v * 256 ; // upgrade 8 -> 16 bpp
		      	   RCount[i]++;
		      	   }
			}
			break;

		   case 3:
			for(i=0; i<NPixels; ++i) {
	   	   	   v = data[i];
		   	   if (v>=threshhold) {
	   	      	   Gdata[i] += v * 256 ; // upgrade 8 -> 16 bpp
		      	   GCount[i]++;
		      	   }
			}
			break;

		   case 4:
			for(i=0; i<NPixels; ++i) {
	   	   	   v = data[i];
		   	   if (v>=threshhold) {
	   	      	   Bdata[i] += v * 256 ; // upgrade 8 -> 16 bpp
		      	   BCount[i]++;
		      	   }
		 	}
			break;
	   	   }
		break;
	   case 16:
		threshhold <<= 8;
		switch(channel) {
		   default:
		   case 2:
			for(i=0; i<NPixels; ++i) {
	   	   	   v = sdata[i];
		   	   if (1 || v>=threshhold) {
	   	      		Rdata[i] += v;	// 16bpp data
		      		RCount[i]++;
		      		}
			   }
			break;
		   case 3:
			for(i=0; i<NPixels; ++i) {
	   	   	   v = sdata[i];
		   	   if (1 || v>=threshhold) {
	   	      		Gdata[i] += v;	// 16bpp data
		      		GCount[i]++;
		      		}
			   }
			break;
		   case 4:
			for(i=0; i<NPixels; ++i) {
	   	   	   v = sdata[i];
		   	   if (1 || v>=threshhold) {
	   	      		Bdata[i] += v;	// 16bpp data
		      		BCount[i]++;
		      		}
			   }
			break;
	   	   }
		break;
	   case 24:
		for(i=o=0; i<NPixels; ++i,o+=3) {
	   	   b = data[o]; g = data[o+1]; r = data[o+2];
	   	   if (b*0.114 + g*0.587 + r*0.299 >= threshhold) {
	   	      Bdata[i] += b*256;	// 8bpp data, B channel, upgrade to 16bpp
	   	      Gdata[i] += g*256;	// 8bpp data, G channel, upgrade to 16bpp
	   	      Rdata[i] += r*256;	// 8bpp data, R channel, upgrade to 16bpp
		      RCount[i]++;
		      GCount[i]++;
		      BCount[i]++;
		      }
	   	   }
		break;
	   default:
		Print("Stack: Depth %d unsupported\n",depth);
		break;
	   }
	return ++Frames;
	}

static int
fetch_stack_u32(u32 *R, u32 *G, u32 *B)
	{
	int i;
	u32 v;

	if (! Frames) {
	   Print("fetch_stack_u32: no frames stacked\n");
	   return 0;
	   }

	Print("Averaging %d frames\n",Frames);

	for(i=0; i<NPixels; ++i) {
	   if (RCount[i]) R[i] = Rdata[i] / RCount[i] * 65536; else R[i]=0;
	   if (GCount[i]) G[i] = Gdata[i] / GCount[i] * 65536; else G[i]=0;
	   if (BCount[i]) B[i] = Bdata[i] / BCount[i] * 65536; else B[i]=0;
	   }

	return 1;
	}

static int
fetch_stack_u16(unsigned short *R, unsigned short *G, unsigned short *B)
	{
	int i;
	unsigned int v;

	if (! Frames) {
	   Print("fetch_stack_u16: no frames stacked\n");
	   return 0;
	   }

	Print("Averaging %d frames\n",Frames);

	for(i=0; i<NPixels; ++i) {
	   if (RCount[i]) R[i] = Rdata[i] / RCount[i]; else R[i]=0;
	   if (GCount[i]) G[i] = Gdata[i] / GCount[i]; else G[i]=0;
	   if (BCount[i]) B[i] = Bdata[i] / BCount[i]; else B[i]=0;
	   }

	return 1;
	}

// Write out one file for each active channel
// with suffix "-2" "-3" "-4" etc

int
write_stack_file(char *fname)
	{
	int i;

	// Is there data in channel 2 ?
	for(i=0; i<NPixels; ++i) if (RCount[i]>0) {
	   _write_stack_file(fname,2);
	   break;
	   }

	// Is there data in channel 3 ?
	for(i=0; i<NPixels; ++i) if (GCount[i]>0) {
	   _write_stack_file(fname,3);
	   break;
	   }

	// Is there data in channel 4 ?
	for(i=0; i<NPixels; ++i) if (BCount[i]>0) {
	   _write_stack_file(fname,4);
	   break;
	   }

	return 1;
	}

int
_write_stack_file(char *fname, int channel)
	{
	FILE *out;
	double bscale = 1.0;
	int bzero = 0;
	int i,x;
	u32 *R,*G,*B,*ptr;
	struct Image *img;
	char new_fname[1024],*p;

	// Allocate the buffer
	R = (u32 *) Malloc(Width * Height * 4);
	G = (u32 *) Malloc(Width * Height * 4);
	B = (u32 *) Malloc(Width * Height * 4);

	// Fetch the image
	fetch_stack_u32(R,G,B);
	
	strcpy(new_fname,fname);
	p = new_fname + strlen(new_fname);
	while(p != new_fname && *p != '.') --p;

	// If we can't find a suffix then set up to append
	if (p == new_fname) p = new_fname + strlen(new_fname);

	sprintf(p,"-%d",channel);
	p = new_fname + strlen(new_fname);

	switch(Depth) {
	   case 8:
	   case 16:
		// Monochrome (R channel) data only. Write out as a 32BPP FITS floating point
		strcpy(p,".fit");

		switch(channel) {
		   case 2:
		   default:
			img = CreateFitsImage(new_fname,Width,Height,32,(unsigned char *)R,bzero,bscale);
			break;
		   case 3:
			img = CreateFitsImage(new_fname,Width,Height,32,(unsigned char *)G,bzero,bscale);
			break;
		   case 4:
			img = CreateFitsImage(new_fname,Width,Height,32,(unsigned char *)B,bzero,bscale);
			break;
		   }

		if (!img) {
	   	   Print("write_stack_file: Error creating output img '%s'\n",fname);
	   	   exit(1);
	   	   }
		img->dst_width = Dstwidth; img->dst_height = Dstheight;

		// Convert from 32bpp unsigned to 32bpp floating point if requested
		if (OutputStackFileDepth == -32)
		   img = ConvertToType(img, IMG_FIT, -32);

		if (! img) {
		   Print("write_stack_file: error converting from U32 to F32\n");
		   exit(1);
		   }

        	if (! WriteImage(img)) {
           	   Print("Short write on output to %s\n",fname);
           	   exit(1);
           	   }
		Print("created stack file %s %dx%dx%d\n",fname,img->dst_width,img->dst_height,img->depth);
		DestroyImage(img);
		break;
	   case 24:
		// Create separate R,G,B 16bpp FITS
		strcpy(p,"_R.fit");
		img = CreateFitsImage(new_fname,Width,Height,16,(unsigned char *)R,bzero,bscale);
		if (!img) {
	   	   Print("write_stack_file: Error creating output img '%s'\n",new_fname);
	   	   exit(1);
	   	   }
		img->dst_width = Dstwidth; img->dst_height = Dstheight;
        	if (! WriteImage(img)) {
           	   Print("Short write on output to %s\n",new_fname);
           	   exit(1);
           	   }
		DestroyImage(img);  // also frees R

		strcpy(p,"_G.fit");
		img = CreateFitsImage(new_fname,Width,Height,16,(unsigned char *)G,bzero,bscale);
		if (!img) {
	   	   Print("write_stack_file: Error creating output img '%s'\n",new_fname);
	   	   exit(1);
	   	   }
		img->dst_width = Dstwidth; img->dst_height = Dstheight;
        	if (! WriteImage(img)) {
           	   Print("Short write on output to %s\n",new_fname);
           	   exit(1);
           	   }
		DestroyImage(img); // also frees G

		strcpy(p,"_B.fit");
		img = CreateFitsImage(new_fname,Width,Height,16,(unsigned char *)B,bzero,bscale);
		if (!img) {
	   	   Print("write_stack_file: Error creating output img '%s'\n",new_fname);
	   	   exit(1);
	   	   }
		img->dst_width = Dstwidth; img->dst_height = Dstheight;
        	if (! WriteImage(img)) {
           	   Print("Short write on output to %s\n",new_fname);
           	   exit(1);
           	   }
		DestroyImage(img); // also frees B

		break;
	      }

	return(1);
	}

//====================================================================================

static struct Image *
load_mergefile(char *fname)
	{
	FILE *in;
	int i,x,count=0;
	double avg;
	int minval8 = ThreshHold;
	int minval16 = minval8<<8 ; // don't process the background
	static struct Image *MImg = NULL;
	unsigned char *ptr;
	unsigned short *uptr;

	// Image already loaded
	if (MImg) {
	   if (!strcmp(MImg->src_fname,fname)) return MImg;
	   Print("Warning: load_mergefile: changing reference image\n");
	   DestroyImage(MImg);
	   }

	MImg = LoadImage(fname,"<internal>");

	if (!MImg) {
	   Print("load_mergefile: cannot open '%s' for reading\n",fname);
	   return NULL;
	   }

	if (MImg->depth != 8 && MImg->depth != 16) {
           Print("load_mergefile: Unsupported depth: %d\n",MImg->depth);
	   DestroyImage(MImg);
           return NULL;
           }

	i = MImg->width * MImg->height;
	ptr = MImg->data;
	uptr = (unsigned short *)MImg->data;

	if (MImg->depth == 8) while(i--) {
             if (*ptr >= minval8) {avg += *ptr; ++count;}
	     ++ptr;
             }
	else if (MImg->depth == 16) while(i--) {
             if (*uptr >= minval16) {avg += *uptr; ++count;}
	     ++uptr;
             }

	MImg->m.avg = (double)avg / count;
	Print("[LoadMerge%d avg=%lf] ",MImg->depth,MImg->m.avg);

	return MImg;
	}

#define MERGE_EQ(a,b) (((a) + ((a)>>1) + ((b)>>1))>>1)

static void
merge_16_16(unsigned short *src, struct Image *r, int npix)
	{
	unsigned short *ref = (unsigned short *)r->data;
	int i,count,high_count=0,low_count=0;
	int minval = ThreshHold << 8;
	double avg,d,scale;

	// calculate average brightness for scaling purposes
	for(i=avg=count=0; i<npix; ++i)
	   if (src[i] >= minval) {avg += src[i]; ++count;}
		
	if (count < MinPixels) {
	   Print("merge_data: found no significant pixels\n");
	   exit(1);
	   }

	avg /= (double) count;
	scale = r->m.avg/avg;

	for(i=0; i<npix; ++i,++src,++ref) {
	   int s = (double)*src * scale + 0.5;
	   if (s >= minval && *ref) {
		double d = s - *ref; d /= *ref;

		if (d >= MergeThreshHold) {
		   *src = MERGE_EQ(s,*ref); ++high_count; }
		else if (d <= -MergeThreshHold) {
		   *src = MERGE_EQ(s,*ref); ++low_count; }
		}
	   }

	Print("%d/%d pixels] ",high_count,low_count);
	return;
	}

static void
merge_8_8(unsigned char *src, struct Image *r, int npix)
	{
	unsigned char *ref = r->data;
	int i,count,high_count=0,low_count=0;
	int minval = ThreshHold;
	double avg,d,scale;

	// calculate average brightness for scaling purposes
	for(i=avg=count=0; i<npix; ++i)
	   if (src[i] >= minval) {avg += src[i]; ++count;}
		
	if (count < MinPixels) {
	   Print("merge_data: found no significant pixels\n");
	   exit(1);
	   }

	avg /= (double) count;
	scale = r->m.avg/avg;

	for(i=0; i<npix; ++i,++src,++ref) {
	   int s = (double)*src * scale + 0.5;
	   if (s >= minval && *ref) {
		double d = s - *ref; d /= *ref;

		if (d >= MergeThreshHold) {
		   *src = MERGE_EQ(s,*ref); ++high_count; }
		else if (d <= -MergeThreshHold) {
		   *src = MERGE_EQ(s,*ref); ++low_count; }
		}
	   }

	Print("%d/%d pixels] ",high_count,low_count);
	return;
	}

static void
merge_16_8(unsigned short *src, struct Image *r, int npix)
	{
	unsigned char *ref = r->data;
	int i,count,high_count=0,low_count=0;
	int minval = ThreshHold << 8;
	double avg,d,scale;

	// calculate average brightness for scaling purposes
	for(i=avg=count=0; i<npix; ++i)
	   if (src[i] >= minval) {avg += src[i]>>8; ++count;}
		
	if (count < MinPixels) {
	   Print("merge_data: found no significant pixels\n");
	   exit(1);
	   }

	avg /= (double) count;
	scale = r->m.avg/avg;

	for(i=0; i<npix; ++i,++src,++ref) {
	   int s = (double)*src * scale + 0.5;
	   int r = *ref << 8;

	   if (s >= minval && r) {
		double d = (s - r) / r;

		if (d >= MergeThreshHold) {
		   *src = MERGE_EQ(s,r); ++high_count; }
		else if (d <= -MergeThreshHold) {
		   *src = MERGE_EQ(s,r); ++low_count; }
		}
	   }

	Print("avg=%lf scale=%lf %d/%d pixels] ",avg,scale,high_count,low_count);
	return;
	}

static void
merge_8_16(unsigned char *src, struct Image *r, int npix)
	{
	unsigned short *ref = (unsigned short *)r->data;
	int i,count,high_count=0,low_count=0;
	int minval = ThreshHold;
	double avg,d,scale;

	// calculate average brightness for scaling purposes
	for(i=avg=count=0; i<npix; ++i)
	   if (src[i] >= minval) {avg += src[i]<<8; ++count;}
		
	if (count < MinPixels) {
	   Print("merge_data: found no significant pixels\n");
	   exit(1);
	   }

	avg /= (double) count;
	scale = r->m.avg/avg;

	for(i=0; i<npix; ++i,++src,++ref) {
	   int s = (double)*src * scale + 0.5;
	   int r = *ref >> 8;

	   if (s >= minval && r) {
		double d = (s - r) / r;

		if (d >= MergeThreshHold) {
		   *src = MERGE_EQ(s,r); ++high_count; }
		else if (d <= -MergeThreshHold) {
		   *src = MERGE_EQ(s,r); ++low_count; }
		}
	   }

	Print("%d/%d pixels] ",high_count,low_count);
	return;
	}

int
merge_data(char *mfile, struct Image *img)
	{
	int width = img->width;
	int height = img->height;
	int depth = img->depth;
	unsigned char *data = img->data;
	struct Image *ref;
	int npix;

	ref = load_mergefile(mfile);

	if (!ref || (width != ref->width) || height != ref->height) {
	   Print("merge_data: Frame size does not match mergefile\n");
	   return 0;
	   }

	npix = width * height;
	Print("[merge %d+%d, ",depth,ref->depth);
	if (depth==16) {
	   if (ref->depth==16)     merge_16_16((unsigned short *)data, ref,npix);
	   else if (ref->depth==8) merge_16_8((unsigned short *)data, ref,npix);
	   }
	else if (depth==8) {
	   if (ref->depth==16) 	   merge_8_16(data, ref,npix);
	   else if (ref->depth==8) merge_8_8(data, ref,npix);
	   }

	return(1);
	}
