#include "videoshow.h"
#include "display.h"

#define MODE_PLAY 1
#define MODE_PAUSE 2
#define MODE_STEP 3
#define MODE_NEXT 4

static int SDLDepth = 16;

static int VideoStack = 1;
static int WriteFrame = 0;
int Mode = MODE_PLAY;
int VideoCut = 0;
int Offset = 0;
double UMask = 0.0;

static int Requested_FPS = 0;

extern int DetectBrokenFrames;

int DrawFrame = 1;
int SeekColour = 0;

char * GainComp = NULL;

unsigned long FrameOffsets[10000];
int FrameCount = 0;

static int FrameChannel(char *fname);
static void Sharpen(unsigned int *buffer, int width, int height);
static void Smooth(unsigned int *buffer, int width, int height);
static int Convert_8_16_bpp(struct Image *img);
int isBrokenFrame(struct Image *img);

#define PROPERTIES_MAX 7
#define PROPERTIES_DEF 0

int PropList[PROPERTIES_MAX + 1];
static int cur_channel = PROPERTIES_DEF;

static void
VKeyPress(int key, int mod)
	{
	double g;

	switch(key) {
	   case SDLK_ESCAPE:
	   case SDLK_q:
		SDL_Quit();
		exit(1);
		break;
	   case SDLK_n:
		Mode = MODE_NEXT;
		break;
	   case SDLK_SPACE:
		if (Mode == MODE_PLAY) Mode = MODE_PAUSE;
		else Mode = MODE_PLAY;
		break;
	   case SDLK_l:
	   case SDLK_RIGHT:
		Mode = MODE_STEP;
		break;
	   case SDLK_h:
	   case SDLK_LEFT:
		if (FrameCount>1) {
		   Mode = MODE_STEP;
		   FrameCount-=2;
		   }
		break;
	   case SDLK_2:
		DrawFrame=0;
		SeekColour = 2;
		break;
	   case SDLK_3:
		DrawFrame=0;
		SeekColour = 3;
		break;
	   case SDLK_4:
		DrawFrame=0;
		SeekColour = 4;
		break;
	   case SDLK_5:
		DrawFrame=0;
		SeekColour = 5;
		break;
	   case SDLK_6:
		DrawFrame=0;
		SeekColour = 6;
		break;
	   case SDLK_w:
		WriteFrame = 1;
		break;
	   case SDLK_RIGHTBRACKET:
		AdjustProperty_gain(PropList[cur_channel], 0.1);
		break;
	   case SDLK_LEFTBRACKET:
		g = AdjustProperty_gain(PropList[cur_channel], -0.1);
		if (g<1) SetProperty_gain(PropList[cur_channel], 1.0);
		break;
	   }
	}

static void
VKeyRelease(int key, int mod)
	{
	}

int FCount=0;

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;
	}

void
display_image(struct Image *img)
	{
	int npixels;
	SDL_Event ev;
	static int have_window = -1;
	static int Width=-1,Height=-1;
	static unsigned long tm;
	struct tm *t;
	char str[128];

	if (DetectBrokenFrames && isBrokenFrame(img))
	   return;

	if (FCount==0) tm = time(0);

	if (AMean) AlphaTrimmedMean(img,1,2);

	if (img->depth == 8) 
	   Convert_8_16_bpp(img);

	if (! DrawFrame) {
	   int c = FrameChannel(img->src_fname);
	   if (c == SeekColour) DrawFrame=1;
	   }

	if (DrawFrame) {
	   if (GainComp) ApplyGainCompensation(img);

	   if (Requested_FPS > 0) 
	      Usleep(1000000 / Requested_FPS);

	   if (VideoCut) {
	      CalculateCutout(img);
	      CutOut(img);
	      }

	   npixels = img->width * img->height;
	   if (Width != img->width || Height != img->height) {
	      if (have_window) SDL_Quit();
	      SDLDepth = 16;
	      have_window = InitDisplay("VideoShow",img->width,img->height,SDLDepth);
	      if (! have_window) {
		   SDLDepth = 32;
		   fprintf(stderr,"16bpp failed, trying 32...\n");
	           have_window = InitDisplay("VideoShow",img->width,img->height,SDLDepth);
		   if (! have_window) FatalError("Cannot start SDL");
		   }

	      Width = img->width; Height = img->height;
	      }

#ifdef MSWIN32
	   sprintf(str,"%-20s %9u / %I64u",
		img->src_fname, img->tm_sec, img->tm_tsc);
#else
	   sprintf(str,"%-20s %9u / %llu",
		img->src_fname, img->tm_sec, img->tm_tsc);
#endif

	   DrawString(img, img->height - 20, 10, str);
	   DisplayImage(img, PropList[cur_channel], 0, 0);
	   }

	if (Mode == MODE_STEP) Mode = MODE_PAUSE;

	while(1) {
	   while(SDL_PollEvent(&ev)) {
	      switch(ev.type) {
		case SDL_KEYDOWN: VKeyPress(ev.key.keysym.sym, ev.key.keysym.mod);
				  break;
		case SDL_KEYUP: VKeyRelease(ev.key.keysym.sym, ev.key.keysym.mod);
				  break;
		}
	     }
	   if (WriteFrame == 1) {
		printf("Write [%s]\n",img->dst_fname);
		WriteImage(img);
		WriteFrame=0;
		}

	   if (Mode == MODE_PLAY || Mode == MODE_STEP || Mode == MODE_NEXT) break;
	   Usleep(100000);
	   }

	++FCount;
	if (time(0) - tm > 2) {
	   if (img->tm_sec>0)
	      t = gmtime((const time_t *)(&img->tm_sec));
	   else t = NULL;

	   FCount /= 2;
	   printf("%-15.15s fps: %5.5d ",img->src_fname,FCount);
	   if (t != NULL)
		 printf("%02d:%02d:%02d", t->tm_hour, t->tm_min, t->tm_sec);
	   else  printf("00:00:00");
#ifdef MSWIN32
	   printf(" / %I64u\r", img->tm_tsc);
#else
	   printf(" / %llu\r", img->tm_tsc);
#endif
	   fflush(stdout);
	   FCount=0;
	   }

	return;
	}

static int
comp(const void *ptr1, const void *ptr2)
        {
        char **s1 = (char **)ptr1;
        char **s2 = (char **)ptr2;
        char *p1 = *s1;
        char *p2 = *s2;
        int i1,i2;

        // seek to end of string
        p1 += strlen(p1);
        p2 += strlen(p2);

        // seek back until we find the last path entry
        while(p1 != *s1 && *(p1-1) != '/' && *(p1-1) != '\\') --p1;
        while(p2 != *s2 && *(p2-1) != '/' && *(p2-1) != '\\') --p2;

        // Go forward until we find a digit
        while(*p1 && ! isdigit(*p1)) ++p1;
        while(*p2 && ! isdigit(*p2)) ++p2;

        // Hopefully we are now on a substring that can be used for comparison
        i1 = atoi(p1);
        i2 = atoi(p2);

        if (i1<i2) return -1;
        if (i1>i2) return 1;
        return 0;
        }

static char *filelist[100000];
static int FileCount=0;

void
playdir(char *dir)
	{
	struct dirent *e;
	int i;
	DIR *d = opendir(dir);
	char fname[256];

	Mode = MODE_PLAY;
	FileCount = 0;

	while(e = readdir(d)) {
	   if (strlen(e->d_name)>4 && e->d_name[0] != '.') {
		sprintf(fname,"%s/%s",dir,e->d_name);
		if (! access(fname,R_OK|F_OK) && isSupportedFile(fname)) {
		   filelist[FileCount++] = strdup(fname);
		   if ((FileCount%1000) == 0) {
			printf("Loaded %d files...\n",FileCount);
			fflush(stdout);
			}
		   }
		}
	   }

	closedir(d);

	printf("Sorting %d files...\n",FileCount);
	fflush(stdout);

	qsort(filelist,FileCount,sizeof(char *),comp);
	for(i=0; i<FileCount; ++i) {
	   if (Mode != MODE_NEXT) playfile(filelist[i]);
	   free(filelist[i]);
	   }

	FileCount=0;
	}

void
playfile(char *fname)
	{
	struct Image *img;
	int len = strlen(fname);
	static int count=0;

	if (! strcmp(fname+len-4,".fta")) {
	   playarchive(fname);

	   if (ChainArchives) {
		char *p = fname + strlen(fname)-5;
		while(p != fname && !isdigit(*p)) p--;
		if (*p && isdigit(*p)) {
		   int n = atoi(p);
		   while(n<9) {
			++(*p); ++n;
			playarchive(fname);
			}
		   }
		}
	   }
	else {
	   //printf("%-5d\r",count); count++; fflush(stdout);
	   img = LoadImage(fname,NULL);
	   display_image(img);
	   DestroyImage(img);
	   }
	}

void
playarchive(char *arch)
	{
	struct Image *img;
#ifndef MSWIN32
	extern FILE *fopen64();
	FILE *in = fopen64(arch,"rb");
#else
	FILE *in = fopen(arch,"rb");
#endif
	int count,err;

	if (! in) {
	   FatalError("Cannot open archive");
	   }

	FrameCount=0;
	FrameOffsets[0] = 0;

	while(1) {
	   if (fseek(in,FrameOffsets[FrameCount],SEEK_SET)) perror("");

	   if (Mode == MODE_NEXT) break;

	   if (DrawFrame)
	      img = ArchiveLoadNextImage(in,&err,ARCHIVE_LOAD);
	   else
	      img = ArchiveLoadNextImage(in,&err,ARCHIVE_SKIP);
		
	   if (err==0) break; // end of archive
	   if (err<0) FatalError("Error while reading archive");
	   //printf("%-5d (%-5d)\r",FrameCount,FrameOffsets[FrameCount]); fflush(stdout);
	   FrameCount++;
	   FrameOffsets[FrameCount] = ftell(in);

	   if (DrawFrame && PopFilter) pop_filter(img);
	   display_image(img);
	   DestroyImage(img);
	   }
	}
	   
	
#define SHARPEN_DIV 6
void
Sharpen(unsigned int *buffer, int width, int height)
	{
	int x,y,o;
	static unsigned int *bptr = NULL;
	static int bufsize = 0;
	int npixels = width * height;

	if (npixels != bufsize) {
	   if (bptr != NULL) free(bptr);
	   bptr = Malloc(npixels * 4);
	   bufsize = npixels;
	   }

	for(o=y=2; y<height-2; ++y)
	   for(x=2; x<width-2; ++x,++o) {
		int v = buffer[o] * 3;
		v -= buffer[o-2]/SHARPEN_DIV;
		v -= buffer[o+2]/SHARPEN_DIV;
		v -= buffer[o-width-width-2]/SHARPEN_DIV;
		v -= buffer[o-width-width]/SHARPEN_DIV;
		v -= buffer[o-width-width+2]/SHARPEN_DIV;
		v -= buffer[o+width-width-2]/SHARPEN_DIV;
		v -= buffer[o+width+width]/SHARPEN_DIV;
		v -= buffer[o+width+width+2]/SHARPEN_DIV;
		//bptr[o] = (unsigned int)((double)v/2.5);
		if (v>0) bptr[o] = v; else bptr[o]=0;
		}

	memcpy(buffer,bptr,npixels*4);
	}

void
UnsharpMask(unsigned int *buffer, int width, int height, double m)
	{
	static int Npixels = -1;
	static unsigned int *bptr = NULL;
	int npixels = width * height;

	if (npixels != Npixels) {
	   if (bptr != NULL) free(bptr);
	   bptr = Malloc(npixels * 4);
	   Npixels = npixels;
	   }
	
	memcpy(bptr, buffer, npixels*4);
	Smooth(bptr, width, height);
	SubtractScale(buffer,bptr,npixels,m);
	Smooth(buffer,width, height);
	}

void
Smooth(unsigned int *buffer, int width, int height)
	{
	int x,y,o;
	static unsigned int *bptr = NULL;
	static int bufsize = 0;
	int npixels = width * height;

	if (npixels != bufsize) {
	   if (bptr != NULL) free(bptr);
	   bptr = Malloc(npixels * 4);
	   bufsize = npixels;
	   }

	for(o=y=0; y<height; ++y)
	   for(x=0; x<width; ++x,++o) {
		int v = buffer[o];
		v += buffer[o-1];
		v += buffer[o+1];
		v += buffer[o-width-1];
		v += buffer[o-width];
		v += buffer[o-width+1];
		v += buffer[o+width-1];
		v += buffer[o+width];
		v += buffer[o+width+1];
		bptr[o] = v/9;
		}

	memcpy(buffer,bptr,npixels*4);
	}

// buf1 -= buf2 * m
void
SubtractScale(unsigned int *buf1, unsigned int *buf2, int npixels, double m)
	{
	while(npixels-- > 0) {
	   int v = *buf1; v -= *buf2 * m;
	   if (v<0) v=0;
	   *buf1 = v;
	   buf1++; buf2++;
	   }
	}

static int
Convert_8_16_bpp(struct Image *img)
        {
        int depth = img->depth;
        int width = img->width;
        int height = img->height;

        if (depth == 8) {
           register unsigned int X;
           int o = width * height;
           unsigned short *buffer = Malloc(o * 2);
           unsigned char *ptr = (unsigned char *)img->data;

           while(o-- >= 0) {
                X = ptr[o]; X<<=8; buffer[o]=X;
                }

           free(img->data);
           img->data = (unsigned char *)buffer;

           img->depth = 16;

           if (img->type == IMG_FIT) {
                struct fits_info *fi = img->info;
                fi->bzero = 32768;
                }
           }

        return 1;
        }

#ifdef MSWIN32
#include <windows.h>

extern int main(int argc, char* argv[]);

int WINAPI WinMain(HINSTANCE instance, HINSTANCE prev_instance, char* command_line, int show_command)
{
    main(__argc, __argv);
}
#endif
 

int
main(int argc, char **argv)
	{
	struct Image *img;
	int i;

	InitProperties();

	for(i=0; i<= PROPERTIES_MAX; ++i) {
	   int ch = CreateProperty(0,1.0,1.0);  // brightness offset, gain, gamma
	   PropList[i] = ch;
	   }

	cur_channel = PROPERTIES_DEF;

	Quiet=1;
	InputHistoStretch = 0;

	for(i=1; i<argc; ++i) {
	   char *arg = argv[i];

	   if (!strncmp(argv[i],"-istretch",9)) {
		InputHistoStretch = 1;
		continue;
		}

	   if (!strncmp(argv[i],"-amean",6)) {
		AMean=1;
		continue;
		}

	   if (!strncmp(argv[i],"-threshhold=",12)) {
		ThreshHold = atoi(argv[i]+12);
		continue;
		}

	   if (!strncmp(argv[i],"-stack=",7)) {
		VideoStack = atoi(argv[i]+7);
		continue;
		}

	   if (!strncmp(argv[i],"-cut=",5)) {
		int x,y;

		if (sscanf(argv[i]+5,"%d,%d",&x,&y)==2) {
		   CutX = x;
		   CutY = y;
		   }
		else {
		   int cut = atoi(argv[i]+5);
		   CutX = CutY = newWidth = newHeight = cut;
		   }

		DoCutout=1;
		VideoCut = 1;
		continue;
		}

	   if (!strncmp(argv[i],"-popfilter=",11)) {
		PopFilter = atoi(argv[i]+11);
		if (PopFilter<1) PopFilter = 3;
		continue;
		}

	   if (!strncmp(argv[i],"-fps=",5)) {
		Requested_FPS = atoi(argv[i]+5);
		continue;
		}

	   if (!strncmp(argv[i],"-offset=",6)) {
		Offset = atoi(argv[i]+8);
		continue;
		}

	   if (!strncmp(argv[i],"-gain=",6)) {
		double g = atof(argv[i]+6);
		int ch,j;
		
		for(j=0; j<= PROPERTIES_MAX; ++j) {
		   ch = PropList[j];
		   SetProperty_gain(PropList[j],g);
		   }

		continue;
		}

	   if (!strncmp(argv[i],"-gain",5) && isdigit(argv[i][5])) {
		int t = atoi(argv[i]+5);
		SetProperty_gain(PropList[t],atof(argv[i]+7));
		continue;
		}

	   if (!strncmp(argv[i],"-amean",6)) {
		AMean = 1;
		continue;
		}

	   if (!strncmp(argv[i],"-dbf",3)) {
		DetectBrokenFrames=1;
		DBF_TYPE=DBF_PLANET;
		continue;
		}

	   if (!strcmp(argv[i],"-chain")) {
		ChainArchives=1;
		continue;
		}

	   if (!strncmp(argv[i],"-umask=",7)) {
		UMask = atof(argv[i]+7);
		continue;
		}

	   if (!strncmp(argv[i],"-gamma=",7)) {
		double g = atof(argv[i]+7);

		SetProperty_gamma(PropList[cur_channel],g);
		continue;
		}

	   if (!strncmp(argv[i],"-gaincomp=",10)) {
		GainComp = strdup(argv[i]+10);
		if (! LoadGainCompRef(GainComp)) {
		   printf("gaincomp %s failed\n",GainComp);
		   exit(1);
		   }
		continue;
		}

	   if (isDirectory(arg)) {
		printf("playing dir %s\n",arg);
	      	playdir(arg);
		}
	   else if (isFile(arg)) {
		//printf("playing file %s\n",arg);
	      	playfile(arg);
		}
	   }

	ShutdownDisplay();
	}
