/*
 * watchme.c - v1.0
 * ianw@[sydney.sgi.com|ieee.org]
 *
 * 20th Feb 2001
 *
 * a program to sit and listen on a port for a http connection,
 * and then return an image from an indycam
 * 
 * mostly created to learn about SGI video library programming,
 * brush up on pthreads programming, and for voyeuristic fun :)
 *
 */

#include "webcamd.h"

#define VERSION "1.0\0"

/* error message printout */
#define ERR_OUT(msg)  syslog(LOG_ERR,"webcamd[%d]: %s, %s ", getpid(), strerror(errno) , msg )
/* warning printout */
#define WARN_OUT(msg) syslog(LOG_INFO,"webcamd[%d]: %s ", getpid() , msg)
/* quit equal zero */
#define QEZ(a, msg)  if ( (a) == 0 )    { ERR_OUT(msg) ; clean_up(1) ; }
/* quit not equal zero */
#define QNZ(a, msg)  if ( (a) != 0 )    { ERR_OUT(msg) ; clean_up(1) ; }
/* quit less than zero */
#define QLZ(a, msg)  if ( (a)  < 0 )    { ERR_OUT(msg) ; clean_up(1) ; }
/* quit eq -1 */
#define QEM(a, msg)  if ( (a) == -1)    { ERR_OUT(msg) ; clean_up(1) ; }
/* quit equal null */
#define QEN(a, msg)  if ( (a) == NULL ) { ERR_OUT(msg) ; clean_up(1) ; }

/* threads */
void *server_thread(void*);
void *harvest_thread(void*);

/* globals */
static pthread_mutex_t mutex;
static pthread_cond_t condition;
static int sock;

/* pointer to the history, see header */
History* global_history;

void vlerror_exit(void) 
{
	vlPerror("webcamd");
	syslog(LOG_ERR, "webcamd vl error");
	clean_up(1);
}

/* primarily close the socket so other things can use
 * the port right away
 */
void clean_up(int signo) 
{
	pthread_mutex_init(&mutex, NULL);
	pthread_cond_init(&condition, NULL);
		
	close(sock);
	
	WARN_OUT("exit");
	
	exit(0);
}

/*
 * fill an image i with a snapshot from the first available 
 * input device.  you should make sure you select the the
 * indycam as the input for the VINO board and it will
 * work great on a Indy. 
 *
 * I am not 100% sure on the semantics of where the VL calls
 * work and do not work. the man page is a bit vague and 
 * so is the web ; but I know we can't use DMBuffers with
 * older indy VINO equipment; which is a shame because
 * they are a really nice concept.  
 * however how these older calls work with newer equipment 
 * is left as an excercise for the reader.
 * 
 * anyway, we just make two nodes; the first video device and
 * a memory drain.  setup the path between them and snap an image
 * into memory ; i do realise creating this every time we want 
 * an image is probably a waste ...
 *
 * then pass this image data (in RGBX format from the indycam) to
 * the compression library to be converted into a jpeg, and 
 * store the jpeg information
 * 
 * that's all!
 */
void fillImage(Image* i)
{
	int file, xsize, ysize, framesize;
	VLServer svr;
        VLPath path;
        VLNode src, drn;
        VLControlValue val;
        VLBuffer buffer;
        VLInfoPtr info;
        char *data,*imgPtr,*dmbufferPtr;

	
	/* Compression Library vars */
	CLhandle compressorHdl;
	int compressedBufferSize;
	void *compressedBuffer;
	int k, numberOfFrames;

	/* require a transport descriptor */
        VLTransferDescriptor xferDesc;

	/* connect to video daemon */
	if (!(svr = vlOpenVideo("")))
		vlerror_exit();
	
	/* setup drain node as a memory node */
	drn = vlGetNode(svr, VL_DRN, VL_MEM, VL_ANY);
	/* setup source node as any video device */
	src = vlGetNode(svr, VL_SRC, VL_VIDEO, VL_ANY);
	
	/* create a path, using any device that will support it */
	path = vlCreatePath(svr, VL_ANY, src, drn);
	
	/* setup the path, define it's usage, etc */
	if ((vlSetupPaths(svr, (VLPathList)&path, 1, VL_SHARE, VL_SHARE)) < 0)
		vlerror_exit();
	
	/* set packing to RGB */
	val.intVal = VL_PACKING_RGB_8;
	vlSetControl(svr, path, drn, VL_PACKING, &val);
	
	/* non-interleved so we get full frames */
	val.intVal = VL_CAPTURE_INTERLEAVED;
	vlSetControl(svr, path, drn, VL_CAP_TYPE, &val);
	
	/* set the zoom */
	val.fractVal.numerator   = 1;
        val.fractVal.denominator = global_history->zoom;
        vlSetControl(svr, path, drn, VL_ZOOM, &val);
	
	/* get the frame size */
	vlGetControl(svr, path, drn, VL_SIZE, &val);
	xsize = val.xyVal.x;
	ysize = val.xyVal.y;

	/* create & register a single buffer */
	
	/* XXX vino workaround ahead.  
	 * vino has a bug that causes the first image returned by
	 * vl_transfer_mode_discrete captures to (randomly -- 
	 * the best kind of bug :) have black
	 * lines through them (ie. it captures one field, but 
	 * not another, and so mixes the fields to end up with 
	 * black lines.  we take two images, and throw the 
	 * first away, since this way we negate the first-call 
	 * bug
	 *
	 * of course we just doubled our memory requirements for
	 * no gain.  
	 */
	
	buffer = vlCreateBuffer(svr, path, drn, 2);
	if (buffer == NULL )
		vlerror_exit();
	vlRegisterBuffer(svr, path, drn, buffer);
	
	/* begin transfer of a single image times 2 */
	xferDesc.delay   = 0;
        xferDesc.trigger = VLTriggerImmediate;
        xferDesc.count   = 2;
        xferDesc.mode    = VL_TRANSFER_MODE_DISCRETE;
        if (vlBeginTransfer(svr, path, 2, &xferDesc ) < 0 ) {
                vlPerror("vlBeginTransfer");
                exit(-1);
        }
	
	/* wait for first frame */
	do 
	{
		info = vlGetNextValid(svr, buffer);
	} while (!info);
	
	/* go for the second frame effectivley disregarding the first*/
	do 
	{
		info = vlGetNextValid(svr, buffer);
	} while (!info);
	
	/* get a pointer to the frame */
	data   = vlGetActiveRegion(svr, buffer, info);
	imgPtr = data;
	
	/* get the size of the frame (since it's rgb_8 packing it would be
	 * with X height X 4 (rgb and alpha channel) X 8
	 * .. but let vl library do the work for us 
	 */
       	framesize = vlGetTransferSize(svr, path);

	/* Create and configure a software jpeg compressor 
	 * on an octane or o2 we could use DMBuffers for the whole thing, from
	 * video to converter to output.  but it's not supported for indy and
	 * the poor little indycam 
	 * the CL doesn't work on o2 anyway (since it has hardware jpeg compression;
	 * this is all software.
	 */
	clOpenCompressor(CL_JPEG_SOFTWARE, &compressorHdl);

	/* set paramaters */
	clSetParam(compressorHdl, CL_IMAGE_WIDTH,  xsize);
	clSetParam(compressorHdl, CL_IMAGE_HEIGHT, ysize);
	clSetParam(compressorHdl, CL_FORMAT, CL_FORMAT_XBGR);
	clSetParam(compressorHdl, CL_JPEG_QUALITY_FACTOR, global_history->quality);
	
	/* Allocate space for compressed buffer */
	compressedBufferSize = clGetParam(compressorHdl,CL_COMPRESSED_BUFFER_SIZE);
	compressedBuffer = malloc(compressedBufferSize);
	QEN( compressedBuffer , "out of memory" );

	/* Compress and copy */
	clCompress(compressorHdl, 1, imgPtr, &compressedBufferSize, compressedBuffer);
	
	i->size = compressedBufferSize;
	i->data = (void *)malloc( compressedBufferSize );
	QEN( i->data , "out of memory" );

	bcopy( compressedBuffer , i->data , compressedBufferSize );
	
	/* free the compression buffer */
	free( compressedBuffer );
	
	/* Destroy the object */
	clCloseCompressor(compressorHdl);
	
	/* unlock buffer */
	vlPutFree(svr, buffer);
	
	/* end data transfer */
	vlEndTransfer(svr, path);
	
	/* cleanup */
	vlDeregisterBuffer(svr, path, drn, buffer);
	vlDestroyBuffer(svr, buffer);
	vlDestroyPath(svr, path);
	vlCloseVideo(svr);

}

/*
 * send the image to the browser
 */
void *server_thread(void *arg) {
	
	char buf[MAXLEN], header[MAXLEN];
	/* 404 error message */
	char *fof = "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD><BODY>NOT FOUND</BODY></HTML>";
	char *get;
	
	/* our socket that we passed in */
	int msgsock = (int)arg;
	int image_no = 0;

	/* a simple server */
	bzero(buf, MAXLEN);
	if ( recv(msgsock, buf, MAXLEN,0) <= 0) 
	{
		/* don't report an error or die, it's probably a client problem
		 * just kill this socket & thread 
		 */
		close(msgsock);
		pthread_exit((void*)1);
	}

       /* find the image number we are after.  if it's > than what we already have filled then
	* go to the currently filled item 
	*
	* it would be funky to write a get_all function that returns a html page with 
	* each of the images, possibly the latest one as a big image and the others as a 
	* small image.
	*
	* we parse for standard http request
	*
	* GET / and an optional number after this. eg:
	* GET /0 image 0
	* GET /4 image 4
	*/
	if ( (get = strtok(buf, "/")) != NULL )
		image_no = atoi( strtok( NULL , " " ));
	else 
	{
	        /* got very strange request here -- it is a non-standard request
		* no web browser should get this
		* do the right thing and return a 404 error
		*/
		sprintf(header,
			"HTTP/1.1 404 File Not Found\r\n"
			"Connection: close\r\n"
			"Content-Type: text/html\r\n\r\n");
		send(msgsock, header, strlen(header) , 0 );
		send(msgsock, fof   , strlen(fof)    , 0 );
		close(msgsock);
		pthread_exit((void*)1);
	}
	
	/* if we select an image greater than those currently filled, just return the 
	 * current biggest
	 */
	if ( image_no > global_history->filled )		
		image_no = global_history->filled;
	
	/* we have cut out zero because humans start from 1; only C starts from zero */
	if ( image_no == 0 )
		image_no = 1;
	
       /* is this legal?  every compiler i've come across can handle "string1" "string2"
	* but i've never found any doco that actually says it's standard
	* if it doesn't compile then take it out
	*
	* it's only a bare-bones header, but some points to note
	*  - connection: close (since the default is to use keepalive for http 1.1
	*
	* you need to use carriage returns back to the web browser to get it to 
	* recognise the headers : this had me stumped for a while!
	*/
	sprintf(header,
		"HTTP/1.1 200 OK\r\n"
		"Server: webcam\r\n"
		"Accept-Ranges: bytes\r\n"
		"Connection: close\r\n"
		"Content-Length: %d\r\n"
		"Content-Type: image/jpeg\r\n\r\n", global_history->images[image_no].size ); 
		
	/* send it away! */
	send(msgsock, header , strlen(header) , 0 ) ;
	send(msgsock, global_history->images[image_no].data , global_history->images[image_no].size , 0 );
	close(msgsock);
	
	return (void*)1;
}

/*
 * harvest an image into the global_history 
 *
 * there is no need for any sort of concurrency control, as this runs
 * all by its self and the other threads only read from the 
 * structure
 *
 * very simple 
 *
 */
void *harvest_thread(void *arg) {
	
	struct timespec ts;
	struct timeval tp;
	
	/* if people select 5 images, they expect to be able to access images
	 * 1-5, not 0-4.  we therefore start from 1
	 */
	int i=0;
	
	/* this is for our timed wait ... doesn't actually mutex anything */
	/* we never explicitly unlock it, since this thread goes for life */
	pthread_mutex_lock(&mutex);

	/* fill the first image */
	fillImage(&global_history->images[1]);
	
	while(1) 
	{
		/* free the last element if the queue is full */
		if ( global_history->filled == global_history->history_size ) 
			free( global_history->images[global_history->history_size].data );
		
		/* start at the second last element.  make it the last
		 * go to the third last, make it the second last
		 * etc.
		 */
		for (i=global_history->filled - 1 ; i >= 0 ; i-- ) {
			global_history->images[i+1].data = global_history->images[i].data;
			global_history->images[i+1].size = global_history->images[i].size;
		}
		
		/* grab a photo into the first slot */
		fillImage(&global_history->images[1]);
		
		/* irix, like linux, doesn't (currently) specify a pthread_sleep() function, so we 
		 * make a false condition (ie. never used) and do a timedwait on it.
		 * this piece of ugliness is to get a timespec structure
		 */
		gettimeofday(&tp, NULL);         
                ts.tv_sec  = tp.tv_sec;
                ts.tv_nsec = tp.tv_usec * 1000;
                ts.tv_sec += global_history->sleep;

		/* sleep */
		pthread_cond_timedwait(&condition, &mutex, &ts);
		
		/* filled increases until it reaches the global_history size-1,
		 * then we are full of data 
		 */
		if ( global_history->filled < global_history->history_size-1 ) 
			global_history->filled++;
	}
}

/*
 * MAIN
 */
void main(int argc, char *argv[]) 
{

	/* default port 1999 */
	int port          = 1999;
	/* default time between images is 5 minutes */
	int sleep_timer   = 300;
	/* default no of images to keep is 5 
	* we start from 1, not zero, since most humans
	* start from 1.  ie. if you put in an argument of 
	* 5 images, these go 1,2,3,4,5 -- we ignore 0
	*/
	int history_size  = 5+1;

	/*
	 * the (integer) denominator of the factor to zoom by.  
	 * read the video lurkers guide (use google)
	 * for caveats about zoom on VINO 
	 *
	 * ie. zoom_size = 2 means zoom out by 1/2
	 */
	int zoom_size = 1;

	/*
	 * we can set a quality between 0 - 100
	 * 100 being the best
	 */
	int jpeg_quality = 75;

	History history;
	Image *image;

	int msgsock, size;
	pthread_t pt,harvest;
	struct sockaddr_in addr;

	static struct sigaction act,death;

	int c,fd;
	char* err_out;
	pid_t pid;
	
	/* parse options */
	while ((c = getopt(argc, argv, "Vp:t:h:z:q:")) != -1)
		switch (c)
		{
			case 'p':
				port         = atoi(optarg);
				break;
			case 't':
				sleep_timer  = atoi(optarg);
				break;
			case 'h':
				/* it's the whole start from 1 thing again */
				history_size = atoi(optarg) + 1;
				break;
			case 'z':
				zoom_size    = atoi(optarg);
				if ( zoom_size < 1 || zoom_size > 4 ) 
				{
					fprintf(stderr, "zoom must be between 1 -- 4\n");
					exit(0);
				}
				break;
			case 'q':
				jpeg_quality = atoi(optarg);
				if ( jpeg_quality < 1 || jpeg_quality > 100 ) 
				{
					fprintf(stderr, "jpeg quality must be between 1 -- 100");
					exit(0);
				}
				break;
			case 'V':
				fprintf(stderr, "webcamd version %s\n", VERSION);
				exit(0);
			case '?':
				fprintf(stderr, "usage: webcamd [-p port] [-t secs] [-h images] [-z zoom (1-4)]\n");
				exit(0);
		}

	if ( fork() != 0 ) 
		exit(0);
	else {

		/* create a new session of which this process is the leader */
		pid=setsid();

		if ( (pid = fork()) != 0 )
		{
			exit(0);
		}
		/* after forking again we can never regain a controlling terminal */
		else 
		{

			pid = getpid();
			
			/* we are now a non-session group leader ... a daemon 
			 * close stdin, stdout, stderr ... we use the syslog
			 * anyway
			 */
			
			close(0);
			close(1);
			close(2);
			
			/* point stderr at console */
			fd = open("/dev/console", O_WRONLY);
			if ( fd != -1 )
				dup2( fd , 2 );
			/* i was reading about a bug in i think mountd 
			 * on linux that 
			 * assumed that fd 2 was stderr; however if it was
			 * run on a system without a console this was 
			 * not correct ; and it ended up writing it's 
			 * error messages all over it's config files
			 * i guess it is conceivable this could be run on a 
			 * machine with no /dev/console -- so pipe it to /dev/null
			 */
			else {
				fd = open("/dev/null", O_WRONLY);
				if ( fd == -1 )
					exit(1);
				
				dup2( fd , 2 );
			}
			
			/* we are very unimportant */
			nice(10);
			
			/* this is our major structure to keep all our images in.  
			* harvest_thread writes to the image array and our sever 
			* thread reads from it to return the images
			*/
			image = (Image*)malloc( history_size * sizeof(Image));
			QEN( (image) , "out of memory");

			history.images       = image;
			history.history_size = history_size;
			history.sleep        = sleep_timer;
			history.filled       = 1;
			history.zoom         = zoom_size;
			history.quality      = jpeg_quality;
			global_history       = &history;

		        /* ignore SIGPIPE.  if we get SIGPIPE it's because the client has
			* killed the pipe before we've finshed sending down it.  this
			* doesn't affect us in any way so it's safe to ignore
			*/
			act.sa_handler = SIG_IGN;
			sigfillset(&(act.sa_mask));
			sigaction(SIGPIPE, &act, NULL);

			death.sa_handler = clean_up;
			sigfillset(&(death.sa_mask));
			sigaction(SIGINT , &death, NULL);
			sigaction(SIGQUIT, &death, NULL);

			/* start a thread to 'harvest' images 
			* we don't use the mutex or conditions, since they are only to 
			* perform a timedwait on later in the code.  we don't 
			* de-register them either since we never leave the server loop
			*/
			pthread_mutex_init(&mutex, NULL);
			pthread_cond_init(&condition, NULL);
			pthread_create(&harvest, NULL, harvest_thread, (void*)NULL );

			/* open a new socket ... we're useless without so die if not available */
			
			sock = socket(AF_INET, SOCK_STREAM, 0);
			QLZ( sock , "socket failure");
			
			/* simple socket stuff ... */
			addr.sin_family = AF_INET;
			addr.sin_port   = htons(port);
			addr.sin_addr.s_addr = htonl(INADDR_ANY);

			QLZ( bind(sock, (struct sockaddr *)&addr, sizeof(addr)) , "socket error" );

			listen(sock,5);
			
			/* seems like we're ok to go */
			syslog(LOG_INFO,"webcamd[%d] listening on port %d, "
                                        "%d seconds between images, %d image history\n", 
                                        getpid(), port, sleep_timer, history_size-1); 

			/* accept connection , lauch a thread to handle it , repeat */
			while(1)
			{
				msgsock=accept(sock, (struct sockaddr *)NULL, (int *)NULL );
				QEM ( msgsock , "socket error" ); 
				
				/* launch a new server thread & join to it */
				QNZ( pthread_create(&pt, NULL, &server_thread, (void*)msgsock) , "can't create thread"); 
				QNZ( pthread_join(pt, NULL) , "can't join thread"); 

			}
		}
	}	
}
