#include "ninox.h"

#ifdef MSWIN32
#include <windows.h>
#endif

static void ShowCopyright(void);
static void mysetenv(const char *name, const char *value);

// realloc() with error trapping
void *
Realloc(void *buf, int nbytes)
        {
        buf = realloc(buf,nbytes);

        if (! buf) {
           Print("Realloc: Out of Memory\n");
           exit(1);
           }

        return buf;
        }

// malloc() with error trapping
void *
Malloc(int nbytes)
        {
        void *buf = malloc(nbytes);

        if (! buf) {
           Print("Malloc: Out of Memory\n");
           exit(1);
           }

        return buf;
        }

void *
ZeroMalloc(int nbytes)
	{
	char *buf = Malloc(nbytes);
	int i;

	for(i=0; i<nbytes;++i) buf[i]=0;

	//_vector_zero_F((FLOAT *)buf,nbytes/4);
	return buf;
	}

char *
VStrdup(char *str)
	{
	char *new;

	if (str==NULL) {
	   Printf("VStrdup: passed NULL\n");
	   exit(1);
	   }

//Printf("strdup '%s'\n",str); fflush(stdout);

	new = VMalloc(strlen(str)+1);
	strcpy(new,str);
//Printf("done strdup\n"); fflush(stdout);
	return new;
	}

char *
TypeName(int type)
	{
	switch (type) {
	   case IMG_BMP: return "bmp"; break;
	   case IMG_FIT: return "fit"; break;
	   case IMG_PPM: return "ppm"; break;
	   }

	return "(unknown)";
	}

// return random value between 0 and (val-1) inclusive
int
Random(int val)
	{
	int rval;

	if (val<=0) return 0;

	again:
	rval = (int)((double)rand()/RAND_MAX * val);
	if (rval==val) goto again; // catch bad case

	return rval;
	}

int
Srandom()
	{
	srand(time(0));
	return(1);
	}

#ifdef MSWIN32
// We need our own version of strcasestr
char *
strcasestr(char *haystack, char *needle)
	{
	int len = strlen(haystack) - strlen(needle) + 1;
	int nlen = strlen(needle);
	int i,match=-1;

	if (len<0) return NULL;

	for(i=0; i<len; ++i)
	   if (! strncasecmp(haystack+i,needle,nlen)) {
		match=i;
		break;
	   	}

	if (match>=0) return haystack+match;
	return NULL;
	}
#endif

int
Usleep(int n)
	{
#ifdef MSWIN32
	Sleep((n / 1000));
#else
	usleep(n);
#endif
	return 0;
	}

void
Mkdir(char *dirname)
        {
	if (dirname==NULL || dirname[0]==0)  return;

        if (! isDirectory(dirname)) {
#ifdef MSWIN32
             mkdir(dirname);
#else
             mkdir(dirname,0755);
#endif

            if (! isDirectory(dirname)) {
               Print("Cannot create output directory %s\n",dirname);
               exit(1);
               }
           }
        }

FILE *
OpenLogfile(char *fname, char *mode)
	{
	char *logdir = ".ninox";
	char logfile[256];
	FILE *z;

	if (NoSave) return NULL;

	Mkdir(logdir);

	if (! isDirectory(logdir)) {
	   Printf("Cannot create log directory %s\n",logdir);
	   exit(1);
	   }

	sprintf(logfile,"%s/%s",logdir,fname);
	z = fopen(logfile,mode);
	return z;
	}

// Given an output filename, return 1 if we are allowed
// to write to that file
int
AllowWrite(char *fname)
	{
	struct stat st;

	// If we have "-nosave" then we are not
	// allowed to write
	if (NoSave) return 0;

	// If we have enabled "-overwrite" then we
	// can always write
	if (AllowOverwrite) return 1;

	// If the file doesn't exist then we can
	// always write to it
	if (access(fname,R_OK)) return 1;

	// stat the file. If this failes then give up
	if (stat(fname,&st)) {
	   Print("stat error on [%s]\n",fname);
	   exit(1);
	   }

	// If file exists with size 0 then we can overwrite
	if (st.st_size == 0) return 1;

	return 0;
	}

int
isDirectory(char *str)
        {
        struct stat st;
        int val;

        val = stat(str,&st);

        if (val) {
                //fprintf(stderr,"isDirectory: Cannot stat '%s'\n",str);
                return(0);
                }

        val = (st.st_mode & S_IFDIR);
        return (val);
        }

int
isFile(char *str)
        {
        struct stat st;
        int val;

        if (stat(str,&st)) {
                //Print("Cannot stat '%s'\n",str);
                return(0);
                }

        val = (st.st_mode & S_IFREG);
        return(val);
        }

// return 1 if this file is of a known supported type
int
isSupportedFile(char *fname)
	{
	char *ptr;

	if (!fname) return(0);

	ptr = fname + strlen(fname)-1;
	while(ptr != fname && *ptr != '.') --ptr;
	if (*ptr != '.') return 0;

	if (!strcasecmp(ptr,".bmp")) return 1;
	if (!strcasecmp(ptr,".fit")) return 1;
	if (!strcasecmp(ptr,".ppm")) return 1;
	if (!strcasecmp(ptr,".fta")) return 1;
	if (!strcasecmp(ptr,".ser")) return 1;

	return 0;
	}

void ShowImage(struct Image *img)
        {
        static int prop = -1;  // display properties for SDL
	static int last_needs_destroy = 0;
	static int last_depth = 0;
	static struct Image *I;
	int d_width,d_height;
	int c_width,c_height;  // cutout width and height

	if (last_needs_destroy) {
	   DestroyImage(I);
	   last_needs_destroy = 0;
	   }

	if (img->depth == 16)
	   I = img;
	else {
	   I = ConvertImage(img,img->type,16);
	   if (img != I) last_needs_destroy = 1;
	   }

        if (DisplayFrames) {
           int x1,y1,x2,y2;

	   // may be displaying a subregion
	   x1 = img->cutout.x1; y1 = img->cutout.y1; x2 = img->cutout.x2; y2 = img->cutout.y2;

	   if (x1<0) x1=0; if (x2>=I->width) x2=I->width-1;
	   if (y1<0) y1=0; if (y2>= I->height) y2=I->height-1;

	   if (x1==0 && x2==0) x2 = I->width-1;
	   if (y1==0 && y2==0) y2 = I->height-1;

	   c_width  = x2-x1+1;	// dimensions of region we are transferring
	   c_height = y2-y1+1;

	    // round up the display width and height to multiples of 8. This makes the display
	    // less volatile
	    d_height = c_height + 8; d_height &= ~8;
	    d_width  = c_width  + 8; d_width  &= ~8;

            // If the image dimensions have changed then shutdown and restart the display
            if (HaveWindow && (c_width > GetDisplayWidth() || c_height > GetDisplayHeight())) {
		Print("current display is (%dx%d), we have image (%dx%d) - mismatch, delete and re-init display\n",
				GetDisplayWidth(), GetDisplayHeight(), c_width, c_height);

                ShutdownDisplay();
                HaveWindow = 0;
                }

            if (! HaveWindow && (last_depth==0 || last_depth==16)) {
                int Depth = 16;
                HaveWindow = InitDisplay("Ninox",d_width,d_height,Depth);
		if (HaveWindow) {
			last_depth = Depth;
                	InitProperties();
			}
                }

            if (! HaveWindow && (last_depth==0 || last_depth==32)) {
                int Depth = 32;
                HaveWindow = InitDisplay("VideoShow",d_width,d_height,Depth);
		if (HaveWindow) {
			last_depth = Depth;
			InitProperties();
			}
                }

           if (! HaveWindow) {
                Message("Cannot start SDL, display disabled");
                DisplayFrames = 0;
                }

           if (HaveWindow) {
	      int kb,y_off,x_off;
	      int dw = GetDisplayWidth();
	      int dh = GetDisplayHeight();

              if (prop<0) prop = CreateProperty(0,1.0,1.0);

	      x_off=y_off=0;  // no offset by default
              if (c_height < dh) y_off = (dh - c_height)/2;
              if (c_width < dw)  x_off = (dw - c_width)/2;

              DisplayImage(I,prop,x_off,y_off);

	      if (DisplayPause)
		while(PollKeyboard() != 2) {Usleep(100);}
	      else PollKeyboard();
              }
           }
        }

#undef malloc
#undef free

static FILE *Output = NULL;   // By default we write to stdout

FILE *
Select(FILE *fd)
	{
	Output = fd;
	return Output;
	}

static char *PTR = NULL;     // Buffer to vsnprintf() into
static int PTR_SIZE = 0;     // Currently allocated size
static int PTR_INC  = 256;   // How much to grow when we need more space

// Code taken from man page for vsprintf()
int Print(char *fmt, ...)
        {
        int n;
        va_list ap;
        static int start_of_line = 1;
	static int first_print = 1;
        FILE *fd;

	if (! Output) Output = stdout;
	fd = Output;

	if (first_print) {
	   first_print=0;
	   // Delay this message to be sure it goes out the correct stream
	   ShowCopyright();
	   }

	// Initial allocate of buffer (first time through only)
        if (! PTR_SIZE) {
	   PTR_SIZE += PTR_INC;
	   if ((PTR = (char *)malloc (PTR_SIZE)) == NULL) {
           	fprintf(fd,"Print: Out of memory!\n");
           	fflush(fd);
		PTR_SIZE=0;
           	return 0;
           	}
	   }

        while (1) {

            /* Try to print in the allocated space. */
            va_start(ap, fmt);
            n = vsnprintf (PTR, PTR_SIZE, fmt, ap);
            va_end(ap);

            /* If that worked, display the string. */
            if (n > -1 && n < PTR_SIZE) {
                for(n=0; n<PTR_SIZE && PTR[n]; ++n) {
                   if (start_of_line && CurrentFile) {fprintf(fd,"%s: ",CurrentFile); if (PrintFlush) fflush(fd);}
                   if (putc(PTR[n],fd) == EOF) break;
                   if (PTR[n] == '\n') start_of_line = 1;
                   else start_of_line = 0;
                   }

		PTR[0]=0;
		if (feof(fd)) exit(1);
                if (PrintFlush) fflush(fd);
                return 1;
                }

	    PTR_SIZE += PTR_INC;
            if ((PTR = realloc (PTR, PTR_SIZE)) == NULL) {
                fprintf(fd,"Print: Out of memory!\n"); if (PrintFlush) fflush(fd);
                return 0;
                }
             }

        // notreached
        if (PrintFlush) fflush(fd);
        return 0;
        }

// Mimic printf() to our selected output stream
// this is hooked via #define macro at top of ninox.h
// to replace printf()
int
Printf(char *fmt, ...)
	{
        va_list ap;
	int n;

	if (! Output) Output = stdout;

        va_start(ap, fmt);
        n = vfprintf (Output, fmt, ap);
        va_end(ap);
	if (! feof(Output) && PrintFlush) fflush(Output);
	return n;
	}

#define XSTR(foo) STR(foo)
#define STR(foo) #foo

static void
ShowCopyright()
	{
	Print("ninox %s (%s)\n",VERSION,BUILDDATE);
        Print("(C)Copyright 2005-2015 Anthony Wesley, All Rights Reserved\n",VERSION);
        Print("This program can be freely distributed. Source code is available\n");
        Print("upon request from http://www.acquerra.com.au/astro/software/\n");
	}

int
ApplyAntiGhost(struct Image *img)
	{

	if (AntiGhostEnable==0 || (AG_X==0 && AG_Y==0) || AG_MAG<1) {
		Print("ApplyAntiGhost: params indicate no change\n");
		return 0;
		}

	Print("Applying antighosting: (%d,%d) @ %d\n",AG_X,AG_Y,AG_MAG);

	// Calculate and subtract a translated copy of the image to counteract 
	// ghosting
	switch(img->depth) {
		case 8:
			return ApplyAntiGhost_8(img);
			break;
		case 16:
			return ApplyAntiGhost_16(img);
			break;
		case 24:
			return ApplyAntiGhost_24(img);
			return;
		default:
			Print("ApplyAntiGhost not supported on depth %d\n",img->depth);
			return;
		}

	return 0;
	}

int
ApplyAntiGhost_8(struct Image *img)
	{
	Print("ApplyAntiGhost_8: not implemented\n");
	return 0;
	}

int
ApplyAntiGhost_16(struct Image *img)
	{
	unsigned short *data = (unsigned short *)img->data;
	int w = img->width;
	int h = img->height;
	int o;
	double g = (double)AG_MAG / 256.0;  // magnitude of ghost

	int x,y,x1,x2,y1,y2,dx,dy,val;

	// We are given AG_X and AG_Y which is the vector to the ghost from the original image.
	// We want to invert this to get the offset from the ghost back to its source
	int offset = -(AG_Y * w + AG_X);

	// depending on the direction of the ghosting we have to start at the right place
	// and scan in the correct direction to subtract it. The key is that the pixels we
	// read to subtract must always be original (un-ghosted) values

	if (AG_X < 0) {x1=w-1+AG_X; x2=0; dx=-1; }
		else  {x1=AG_X; x2=w-1; dx=1; }

	if (AG_Y < 0) {y1=h-1+AG_Y; y2=0; dy=-1; }
		else  {y1=AG_Y; y2=h-1; dy=1; }

	// scan the image, starting at the corner where the ghost appears. scan into the ghost and
	// recover original values

	for(y=y1; y!=y2; y+=dy) {
	   int o = y * w + x1;
	   for(x=x1; x!=x2 ; x+=dx,o+=dx) {
		   int val = data[o];

		   // ghosts are always additive, so if the src pixel is already zero then
		   // we don't need to do anything
		   if (val) {
		      int v1 = data[o+offset];

		      if (v1 > 0 ) {
		         //Print("(%d,%d): src=%d offset=%d g=%f orig=%d\n",x,y,val,offset,g,v1);

		         val -= v1 * g;
		         if (val<0) val=0;
		         if (val>65535) val=65535;
		         data[o] = val;
		         }
		      }

		   }
	   	}

	return 1;
	}

int
ApplyAntiGhost_24(struct Image *img)
	{
	Print("ApplyAntiGhost_8: not implemented\n");
	return 0;
	}

void
PrintInt64(long long v)
	{
#ifdef MSWIN32
              printf("%I64d",v);
#else
              printf("%lld",v);
#endif
	}

void
PrintUInt64(unsigned long long v)
	{
#ifdef MSWIN32
              printf("%I64u",v);
#else
              printf("%llu",v);
#endif
	}

unsigned long long
FileSize(char *fname)
	{
	FILE *z;
	int fd;
	off64_t size;

#ifdef MSWIN32
        z = fopen(fname,"rb");
#else   
        extern FILE * fopen64();
        z = fopen64(fname,"rb");
#endif

	fd = fileno(z);
	size = lseek64(fd,(off64_t)0,SEEK_END);
	fclose(z);

	return size;
	}

// Given a UNIX timestamp in ms, return a char string representation and fill in params
// if they are not NULL
char *
UnixTime_ms_utc(time_t tm, int *year, int *month, int *day, int *hr, int *min, double *sec)
	{
	static char str[256];
	time_t s = tm/1000;
	int ms = tm % 1000;
	struct tm *t;

	t = gmtime(&s);

	// Any params not NULL should be set
	if (year)  *year = t->tm_year + 1900;
	if (month) *month = t->tm_mon + 1;
	if (day)   *day = t->tm_mday;

	if (hr)  *hr = t->tm_hour;
	if (min) *min = t->tm_min;
	if (sec) *sec = t->tm_sec + (double)ms/1000;

	sprintf(str,"%04d-%02d-%02d %02d:%02d:%02d.%03d UTC",
		t->tm_year + 1900,
		t->tm_mon + 1,
		t->tm_mday,
		t->tm_hour, t->tm_min, t->tm_sec,
		ms
		);

	return str;
	}

// Given an SER timestamp, return a char string representation
char *
SerTime_utc(unsigned long long stm)
	{
	char *str = Malloc(64);
	unsigned long long t1970_ms = SerTimeStamp_Unix_ms(stm);
	time_t secs = t1970_ms / 1000;
	int ms = t1970_ms % 1000;
	struct tm *t;

	t = gmtime(&secs);

	sprintf(str,"%04d-%02d-%02d %02d:%02d:%02d.%03d UTC",
		t->tm_year + 1900,
		t->tm_mon + 1,
		t->tm_mday,
		t->tm_hour, t->tm_min, t->tm_sec,
		ms
		);

	return str;
	}

// Given a UNIX time in ms, return the equivalent SER timestamp
unsigned long long
SerTimeStamp_FromUnix_ms(unsigned long long t)
	{
	unsigned long long st = t*(unsigned long long)10000 + (unsigned long long)621355968000000000;

	return st;
	}

// Given an SER timestamp, return the equivalent UNIX time in ms
unsigned long long
SerTimeStamp_Unix_ms(unsigned long long t)
	{
	unsigned long long t1970_ms = (t - (unsigned long long)621355968000000000)/10000;

	return t1970_ms;
	}

// Given the datetime components, return the UNIX time in ms
unsigned long long
DateTime_to_Unix_ms(int year,int month,int day,int hour,int min,double sec)
	{
	struct tm t;
	time_t gm;

	t.tm_year = year - 1900;
	t.tm_mon  = month - 1;
	t.tm_mday = day;
	t.tm_hour = hour;
	t.tm_min  = min;
	t.tm_sec  = sec;

	// Hopefully these are not needed
	t.tm_wday = 0;
	t.tm_yday = 0;
	t.tm_isdst = -1;

#ifdef MSWIN32
        gm = my_timegm(&t);
#else   
        gm = timegm(&t);
#endif

	return (gm*1000) + ((int)(sec*1000)%1000);
	}

// MinGW doesn't have timegm so we have to roll our own.
time_t
my_timegm (struct tm *tm) 
	{
        time_t ret;
        char *tz;

        tz = getenv("TZ");
        mysetenv("TZ", "UTC");
        tzset();
        ret = mktime(tm);
        if (tz)
            mysetenv("TZ", tz);
        tzset();
        return ret;
        }

static void
mysetenv(const char *name, const char *value) 
	{
#ifdef MSWIN32
    	int len = strlen(name)+1+strlen(value)+1;
    	char *str = Malloc(len);
    	sprintf(str, "%s=%s", name, value);
    	putenv(str);
#else
    	setenv(name, value, 1);
#endif
	} 
