
/* 
 * Argyll Color Correction System
 * ChromeCast Display target patch window
 *
 * Author: Graeme W. Gill
 * Date:   8/9/14
 *
 * Copyright 2013, 2014 Graeme W. Gill
 * All rights reserved.
 *
 * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
 * see the License.txt file for licencing details.
 */

#include <stdio.h>
#include <string.h>
#ifdef NT
# include <winsock2.h>
#endif
#ifdef UNIX
# include <sys/types.h>
# include <ifaddrs.h>
# include <netinet/in.h> 
# include <arpa/inet.h>
# ifdef __FreeBSD__
#  include <sys/socket.h>
# endif /* __FreeBSD__ */
#endif
#include "copyright.h"
#include "aconfig.h"
#include "icc.h"
#include "numsup.h"
#include "cgats.h"
#include "conv.h"
#include "dispwin.h"
#include "conv.h"
#include "mongoose.h"
#include "ccast.h"
#include "ccwin.h"
#include "render.h"

#undef WRITE_PNG		/* [und] Write each test patch to "ccwin.png" */
#undef CCTEST_PATTERN	/* [und] Create dev. spatial test pattern */
#undef SEND_TEST_FILE 	/* [und] Send this file name to ChromeCast instead of pattern */
#undef DO_TIMING       	/* [und] Print rendering timing */
#undef DEBUG			/* [und] */

#define DDITHER 1		/*       0 = no dither - quantize to PNG 8 bit RGB value */
						/* [def] 1 = use error diffusion dithering with ccast quant model */
						/*       2 = use crafted 4x4 dither cell */

#define VWIDTH  1920.0	/* Video stream and display size */
#define VHEIGHT 1080.0

#define IWIDTH  1280.0	/* This is the native still image framebuffer size for ChromeCasts */
#define IHEIGHT 720.0

//#define STANDALONE_TEST

#ifdef DEBUG
#define errout stderr
# define debug(xx)	fprintf(errout, xx )
# define debug2(xx)	fprintf xx
# define debugr(xx)	fprintf(errout, xx )
# define debugr2(xx)	fprintf xx
# define debugrr(xx)	fprintf(errout, xx )
# define debugrr2(xx)	fprintf xx
# define debugrr2l(lev, xx)	fprintf xx
#else
#define errout stderr
# define debug(xx) 
# define debug2(xx)
# define debugr(xx) if (p->ddebug) fprintf(errout, xx ) 
# define debugr2(xx) if (p->ddebug) fprintf xx
# define debugrr(xx) if (callback_ddebug) fprintf(errout, xx ) 
# define debugrr2(xx) if (callback_ddebug) fprintf xx
# define debugrr2l(lev, xx) if (callback_ddebug >= lev) fprintf xx
#endif

/* ================================================================== */

/* Chromwin context and (possible) web server  */
typedef struct _chws {
	int verb;
	int ddebug;
	
	int direct;					/* End PNG directly, rather than using web server */

	struct mg_context *mg;		/* Mongoose context (if needed) */
	char *ws_url;				/* Web server URL for accessing server */

//	double hoff, voff;			/* Input position of test square */
	double x, y;				/* position of test square in pixels */
	double w, h;				/* size of test square in pixels */
	int pno;					/* Index to generate a sequence of URLs */
	unsigned char *ibuf;		/* Memory image of .png file */
	size_t ilen;

	ccast *cc;					/* ChromeCast */

	/* Update the png image */
	int (*update)(struct _chws *p, unsigned char *ibuf, size_t ilen, double *bg);

	/* Destroy ourselves */
	void (*del)(struct _chws *p);

} chws;

static void chws_del(chws *p) {

	if (p->mg != NULL)
		mg_stop(p->mg);

	if (p->cc != NULL)
		p->cc->del(p->cc);

	if (p->ibuf != NULL)
		free(p->ibuf);

	if (p->ws_url != NULL)
		free(p->ws_url);

	free(p);
}

/* Change the .png being served */
/* Return nz on error */
static int chws_update(chws *p, unsigned char *ibuf, size_t ilen, double *bg) {
	char url[200];

	debug("\nUpdate png\n");

	if (p->ibuf != NULL)
		free(p->ibuf);

	p->ibuf = ibuf;
	p->ilen = ilen;

	/* Send the PNG swatch direct */
	if (p->direct) {
		double x, y, w, h;
		/* Convert x,y,w,h to relative rather than pixel size */

		debugr2((errout,"Got x %f y %f w %f h %f\n", p->x, p->y, p->w, p->h));

		// Convert from quantized to direct loader parameters
		if (p->w < IWIDTH)
			x = p->x/(IWIDTH - p->w);
		else	
			x = 0.0;
		if (p->h < IHEIGHT)
			y = p->y/(IHEIGHT - p->h);
		else
			y = 0.0;
		w = p->w/(0.1 * IWIDTH);
		h = p->h/(0.1 * IWIDTH);

		debugr2((errout,"Sending direct x %f y %f w %f h %f\n", x, y, w, h));

		if (p->cc->load(p->cc, NULL, p->ibuf, p->ilen, bg, x, y, w, h)) {
			debugr2((errout,"ccwin_update direct load failed\n"));
			return 1;
		}

	/* Using web server */
	} else {

#ifdef SEND_TEST_FILE
		sprintf(url, "%s%s",p->ws_url, SEND_TEST_FILE);
#else
		sprintf(url, "%stpatch_%d.png",p->ws_url, ++p->pno); 
#endif
		if (p->cc->load(p->cc, url, NULL, 0, NULL,  0.0, 0.0, 0.0, 0.0)) {
			debugr2((errout,"ccwin_update server load failed\n"));
			return 1;
		}
	}
	return 0;
}

/* Web server event handler - return the current .png image */
static void *ccwin_ehandler(enum mg_event event,
                           struct mg_connection *conn) {
	const struct mg_request_info *request_info = mg_get_request_info(conn);
	chws *p = (chws *)mg_get_user_data(conn);
	char *cp;
	char sbuf[200];

	debugr2((errout,"ccwin_ehandler()\n"));

	if (event != MG_NEW_REQUEST) {
		return NULL;
	}

	debugr2((errout,"Event: uri = '%s'\n",request_info->uri));

#ifdef SEND_TEST_FILE
#pragma message("############################# ccwin.c SEND_TEST_FILE defined ! ##")
	return NULL;
#endif

	if (p->ibuf != NULL && p->ilen > 0
     && (cp = strrchr(request_info->uri, '.')) != NULL
	 && strcmp(cp, ".png") == 0) { 

		debugr2((errout,"Event: Loading %s\n",request_info->uri));

		debugr2((errout,"Returning current png size %d bytes\n",(int)p->ilen));
		sprintf(sbuf,
			"HTTP/1.1 200 OK\r\n"
			"Content-Type: image/png\r\n"
		    "Content-Length: %d\r\n"
		    "\r\n"
		    ,(int)p->ilen);
		
	    mg_write(conn, sbuf, strlen(sbuf));
	    mg_write(conn, p->ibuf, p->ilen);

	} else {
		debugr2((errout,"Bad request or png - returning 404\n"));
		sprintf(sbuf,
			"HTTP/1.0 404 Not Found\r\n"
		    "\r\n"
			"<html><body><h1>Error 404 - Not Found</h1></body></html>");
		
	    mg_write(conn, sbuf, strlen(sbuf));
	}

	return "yes";
}

chws *new_chws(
ccast_id *cc_id,				/* ChromeCast to open */
double width, double height,	/* Width and height as % */
double hoff, double voff,		/* Offset from center in fraction of screen, range -1.0 .. 1.0 */
int verb, int ddebug) {
	chws *p;
	const char *options[3];
	char port[50];
	int portno = 0;		/* Port number allocated */
	int forcedef = 0;	/* Force default reciever app. */

	if ((p = (chws *)calloc(sizeof(chws), 1)) == NULL) {
		error("new_chws: calloc failed");
		return NULL;
	}

	p->verb = verb;
	p->ddebug = ddebug;

	p->update = chws_update;
	p->del = chws_del;

	/* We make sure we round the test patch size and */
	/* location to integer resolution so that we can know */
	/* it's exact relationship to the upsampled pixel locations. */

	/* Setup window size and position */
	/* The default size is 10% of the width */
	p->w = floor(width/100.0 * 0.1 * IWIDTH + 0.5); 
	if (p->w > IWIDTH)
		p->w = IWIDTH;
	p->h = floor(height/100.0 * 0.1 * IWIDTH + 0.5); 
	if (p->h > IHEIGHT)
		p->h = IHEIGHT;

	// Make offset be on an even pixel boundary, so that we know
	// the up-filter phase.
	p->x = floor((hoff * 0.5 + 0.5) * (IWIDTH - p->w) + 0.5);
	p->y = floor((voff * 0.5 + 0.5) * (IHEIGHT - p->h) + 0.5);
	if (((int)p->x) & 1)
		p->x++;
	if (((int)p->y) & 1)
		p->y++;

	if (verb) printf("Opening ChromeCast '%s'\n",cc_id->name);

#ifdef SEND_TEST_FILE
	forcedef = 1;
#endif

	/* Connect to the chrome cast */
	if ((p->cc = new_ccast(cc_id, forcedef)) == NULL) {
		error("new_ccast: failed");
		chws_del(p);
		return NULL;
	}

	p->direct = p->cc->get_direct_send(p->cc);

	if (!p->direct) {
		/* Create a web server */
		options[0] = "listening_ports";
//		sprintf(port,"%d", 0);			/* Use any available */
		sprintf(port,"%d", 8081);		/* Use fixed port for Linux firewall rule */
		options[1] = port;
		options[2] = NULL;

		p->mg = mg_start(&ccwin_ehandler, (void *)p, options);
	
		if ((p->ws_url = mg_get_url(p->mg)) == NULL) {
			debugr2((errout, "mg_get_url() failed\n"));
			chws_del(p);
			return NULL;
		}
		if (p->ddebug)
			printf("Created .png server at '%s'\n",p->ws_url);
	}

	return p;
}


/* ================================================================== */

/* Get RAMDAC values. ->del() when finished. */
/* Return NULL if not possible */
static ramdac *ccwin_get_ramdac(dispwin *p) {
	debugr("webdisp doesn't have a RAMDAC\n"); 
	return NULL;
}

/* Set the RAMDAC values. */
/* Return nz if not possible */
static int ccwin_set_ramdac(dispwin *p, ramdac *r, int persist) {
	debugr("webdisp doesn't have a RAMDAC\n"); 
	return 1;
}

/* ----------------------------------------------- */
/* Install a display profile and make */
/* it the default for this display. */
/* Return nz if failed */
int ccwin_install_profile(dispwin *p, char *fname, ramdac *r, p_scope scope) {
	debugr("webdisp doesn't support installing profiles\n"); 
	return 1;
}

/* Un-Install a display profile */
/* Return nz if failed, */
int ccwin_uninstall_profile(dispwin *p, char *fname, p_scope scope) {
	debugr("webdisp doesn't support uninstalling profiles\n"); 
	return 1;
}

/* Get the currently installed display profile. */
/* Return NULL if failed. */
icmFile *ccwin_get_profile(dispwin *p, char *name, int mxlen) {
	debugr("webdisp doesn't support getting the current profile\n"); 
	return NULL;
}

/* ----------------------------------------------- */

/* Change the window color. */
/* Return 1 on error, 2 on window being closed */
static int ccwin_set_color(
dispwin *p,
double r, double g, double b	/* Color values 0.0 - 1.0 */
) {
	chws *ws = (chws *)p->pcntx;
	int j;
	double orgb[3];		/* Previous RGB value */
	double kr, kf;
	int update_delay = 0;

	debugr("ccwin_set_color called\n");

	if (p->nowin) {
		debugr("ccwin_set_color: nowin - give up\n");
		return 1;
	}

	orgb[0] = p->rgb[0]; p->rgb[0] = r;
	orgb[1] = p->rgb[1]; p->rgb[1] = g;
	orgb[2] = p->rgb[2]; p->rgb[2] = b;

	for (j = 0; j < 3; j++) {
		if (p->rgb[j] < 0.0)
			p->rgb[j] = 0.0;
		else if (p->rgb[j] > 1.0)
			p->rgb[j] = 1.0;
		p->r_rgb[j] = p->s_rgb[j] = p->rgb[j];
		if (p->out_tvenc) {
			p->r_rgb[j] = p->s_rgb[j] = ((235.0 - 16.0) * p->s_rgb[j] + 16.0)/255.0;

			/* For video encoding the extra bits of precision are created by bit shifting */
			/* rather than scaling, so we need to scale the fp value to account for this. */
			if (p->pdepth > 8)
				p->r_rgb[j] = (p->s_rgb[j] * 255 * (1 << (p->pdepth - 8)))
				            /((1 << p->pdepth) - 1.0); 	
		}
	}

	/* This is probably not actually thread safe... */
	p->ncix++;

#if DDITHER != 1
# pragma message("############################# ccwin.c DDITHER != 1 ##")
#endif

	/* Turn the color into a png file */
	{
		/* We want a raster of IWIDTH x IHEIGHT pixels for web server, */
		/* or p->w x p->h for PNG direct. */
		render2d *r;
		prim2d *rct;
		depth2d depth = bpc8_2d;
#if DDITHER == 1
		int dither = 0x8002;		/* 0x8002 = error diffuse FG only */
#elif DDITHER == 2
		int dither = 0x4000;		/* 0x4000 = no dither but don't average pixels */
									/* so as to allow pattern to come through. */
#else
		int dither = 0;				/* Don't dither in renderer */
#endif
		double hres = 1.0;					/* Resoltion in pix/mm */
		double vres = 1.0;					/* Resoltion in pix/mm */
		double iw, ih;						/* Size of page in mm (pixels) */
		double bg[3];						/* Background color */
		color2d c;
		unsigned char *ibuf;		/* Memory image of .png file */
		size_t ilen;
#ifdef DO_TIMING
		int stime;
#endif

		if (ws->direct) {
			iw = ws->w;		/* Requested size */
			ih = ws->h;
		} else {
			iw = IWIDTH;
			ih = IHEIGHT;	/* Size of page in mm */
		}

		if (p->blackbg) {
			bg[0] = 0.0;
			bg[1] = 0.0;
			bg[2] = 0.0;
		} else {
			bg[0] = 0.5;
			bg[1] = 0.5;
			bg[2] = 0.5;
		}

		debugr2((errout, "ccwin_set_color iw %f ih %f\n",iw,ih));

		if ((r = new_render2d(iw, ih, NULL, hres, vres, rgb_2d,
		     0, depth, dither,
#if DDITHER == 1
			 ccastQuant,
#else
			 NULL,
#endif
			 NULL)) == NULL) {
			error("ccwin: new_render2d() failed");
		}
	
		/* Set the background color */
		c[0] = bg[0];
		c[1] = bg[1];
		c[2] = bg[2];
		r->set_defc(r, c);
	
		c[0] = p->r_rgb[0];
		c[1] = p->r_rgb[1];
		c[2] = p->r_rgb[2];
		if (ws->direct)
			r->add(r, rct = new_rect2d(r, 0.0, 0.0, ws->w, ws->h, c)) ;
		else
			r->add(r, rct = new_rect2d(r, ws->x, ws->y, ws->w, ws->h, c)) ;

#if DDITHER == 2			/* Use dither pattern */
		{
			double rgb[3];
			double dpat[CCDITHSIZE][CCDITHSIZE][3];
			double (*cpat)[MXPATSIZE][MXPATSIZE][TOTC2D];
			int i, j;

			/* Get a chrome cast dither pattern to match target color */
			for (i = 0; i < 3; i++)
				rgb[i] = p->r_rgb[i] * 255.0;
			get_ccast_dith(dpat, rgb);

			if ((cpat = malloc(sizeof(double) * MXPATSIZE * MXPATSIZE * TOTC2D)) == NULL)
				error("ccwin: malloc of dither pattern failed");
			
			for (i = 0; i < CCDITHSIZE; i++) {
				for (j = 0; j < CCDITHSIZE; j++) {
					int k = (((int)IHEIGHT-2) - j) % CCDITHSIZE;	/* Flip to origin bot left */
					(*cpat)[i][k][0] = dpat[i][j][0]/255.0;			/* (HEIGHT-2 is correct!) */
					(*cpat)[i][k][1] = dpat[i][j][1]/255.0;
					(*cpat)[i][k][2] = dpat[i][j][2]/255.0;
				}
			}
			
			set_rect2d_dpat((rect2d *)rct, cpat, CCDITHSIZE, CCDITHSIZE);
		}
#endif /* DDITHER == 2 */

#ifdef CCTEST_PATTERN
#pragma message("############################# ccwin.c TEST_PATTERN defined ! ##")
		if (getenv("ARGYLL_CCAST_TEST_PATTERN") != NULL) {
			verbose(0, "Writing test pattern to '%s'\n","testpattern.png");
			if (r->write(r, "testpattern.png", 1, NULL, NULL, png_file))
				error("ccwin: render->write failed");
		}
#else	/* !CCTEST_PATTERN */
# ifdef WRITE_PNG		/* Write it to a file so that we can look at it */
#  pragma message("############################# spectro/ccwin.c WRITE_PNG is enabled ######")
		if (r->write(r, "ccwin.png", 1, NULL, NULL, png_file))
			error("ccwin: render->write failed");
# endif	/* WRITE_PNG */
#endif	/* !CCTEST_PATTERN */

#ifdef DO_TIMING
		stime = msec_time();
#endif
		if (r->write(r, "MemoryBuf", 1, &ibuf, &ilen, png_mem))
			error("ccwin: render->write failed");
#ifdef DO_TIMING
		stime = msec_time() - stime;
		printf("render->write took %d msec\n",stime);
#endif
		if (ws->update(ws, ibuf, ilen, bg))
			error("ccwin: color update failed");
		p->ccix = p->ncix;
	}

	/* If update is notified asyncronously ... */
//	while(p->ncix != p->ccix) {
//		msec_sleep(50);
//	}
//printf("#################################################################\n");
//printf("#################     RGB update notified        ################\n");
//printf("#################################################################\n");

	/* Allow for display update & instrument delays */
	update_delay = dispwin_compute_delay(p, orgb);
	debugr2((errout, "ccwin_set_color delaying %d msec\n",update_delay));
	msec_sleep(update_delay);

	return 0;
}

/* Set/unset the blackground color flag */
/* Return nz on error */
static int ccwin_set_bg(dispwin *p, int blackbg) {
	p->blackbg = blackbg;

	return 0;
}


/* ----------------------------------------------- */
/* Set the shell set color callout */
void ccwin_set_callout(
dispwin *p,
char *callout
) {
	debugr2((errout,"ccwin_set_callout called with '%s'\n",callout));

	p->callout = strdup(callout);
}

/* ----------------------------------------------- */
/* Destroy ourselves */
static void ccwin_del(
dispwin *p
) {
	chws *ws;

	debugr("ccwin_del called\n");

	if (p == NULL)
		return;

	ws = (chws *)p->pcntx;

	if (ws != NULL)
		ws->del(ws);

	if (p->name != NULL)
		free(p->name);
	if (p->description != NULL)
		free(p->description);
	if (p->callout != NULL)
		free(p->callout);

	free(p);
}

/* ----------------------------------------------- */

/* Create a web display test window, default grey */
dispwin *new_ccwin(
ccast_id *cc_id,				/* ChromeCast to open */
double width, double height,	/* Width and height in mm. (TV width assumed to b 1000mm) */
double hoff, double voff,		/* Offset from center in fraction of screen, range -1.0 .. 1.0 */
int nowin,						/* NZ if no window should be created - RAMDAC access only */
int native,						/* X0 = use current per channel calibration curve */
								/* X1 = set native linear output and use ramdac high precn. */
								/* 0X = use current color management cLut (MadVR) */
								/* 1X = disable color management cLUT (MadVR) */
int *noramdac,					/* Return nz if no ramdac access. native is set to X0 */
int *nocm,						/* Return nz if no CM cLUT access. native is set to 0X */
int out_tvenc,					/* 1 = use RGB Video Level encoding */
int blackbg,					/* NZ if whole screen should be filled with black */
int verb,						/* NZ for verbose prompts */
int ddebug						/* >0 to print debug statements to stderr */
) {
	dispwin *p = NULL;
	char *cp;
	chws *ws = NULL;
	const char *options[3];

	debug("new_ccwin called\n");

	if ((p = (dispwin *)calloc(sizeof(dispwin), 1)) == NULL) {
		if (ddebug) fprintf(stderr,"new_ccwin failed because malloc failed\n");
		return NULL;
	}

	/* !!!! Make changes in dispwin.c & madvrwin.c as well !!!! */
	p->name = strdup("Web Window");
	p->width = width;
	p->height = height;
	p->nowin = nowin;
	p->native = native;
	p->out_tvenc = out_tvenc;
	p->blackbg = blackbg;
	p->ddebug = ddebug;
	p->get_ramdac          = ccwin_get_ramdac;
	p->set_ramdac          = ccwin_set_ramdac;
	p->install_profile     = ccwin_install_profile;
	p->uninstall_profile   = ccwin_uninstall_profile;
	p->get_profile         = ccwin_get_profile;
	p->set_color           = ccwin_set_color;
	p->set_bg              = ccwin_set_bg;
	p->set_update_delay    = dispwin_set_update_delay;
	p->set_settling_delay  = dispwin_set_settling_delay;
	p->enable_update_delay = dispwin_enable_update_delay;
	p->set_callout         = ccwin_set_callout;
	p->del                 = ccwin_del;

	if (noramdac != NULL)
		*noramdac = 1;
	p->native &= ~1;

	if (nocm != NULL)
		*nocm = 1;
	p->native &= ~2;

	p->rgb[0] = p->rgb[1] = p->rgb[2] = 0.5;	/* Set Grey as the initial test color */
	
	dispwin_set_default_delays(p);

	p->ncix = 1;

	p->pdepth = 8;		/* Assume this by API */
	p->edepth = 8;

	/* Basic object is initialised, so create connection to ChromeCast */
	if ((ws = new_chws(cc_id, width, height, hoff, voff, verb, ddebug)) == NULL) {
		if (ddebug) fprintf(stderr,"new_ccwin failed - new_chws() failed\n");
		return NULL;
	}

	/* Extra delay ccast adds after confirming load */
	p->extra_update_delay = ws->cc->get_load_delay(ws->cc) / 1000.0;

	p->pcntx = (void *)ws;

	/* Create a suitable description */
	{
		char buf[100];
		sprintf(buf,"ChromeCast '%s'",cc_id->name);
		p->description = strdup(buf);
	}

    // Set a default first color
	if (ccwin_set_color(p, 128.0, 128.0, 128.0)) {
		if (ddebug) fprintf(stderr,"new_ccwin set_color()\n");
		p->del(p);
		return NULL;
	}

	debugr("new_ccwin: return sucessfully\n");

	return p;
}

