/*
 *	webcam.c
 *	Streaming webcam using jpeg images over a multipart/x-mixed-replace stream
 *	Copyright (C) 2002 Jeroen Vreeken (pe1rxq@amsat.org)
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <sys/fcntl.h>
#include <sys/stat.h>

#include "picture.h"
#include "webcam.h"
#include "motion.h"

/* This function sets up a TCP/IP socket for incomming requests. It is used by two features.
 * - webcam - called only during initiatization of Motion from the function webcam_init
 * - xmlrpc - called from the function xmlrpc_httpd_run()
 * The function sets up a a socket on the port number given by _port_.
 * If the parameter _local_ is not zero the socket is setup to only accept connects from localhost.
 * Otherwise any client IP address is accepted. The function returns an integer representing the socket.
 */
int http_bindsock(int port, int local, struct context *cnt)
{
	int sl, optval=1;
	struct sockaddr_in sin;

	if ((sl=socket(PF_INET, SOCK_STREAM, 0))<0) {
		printf("[%d] socket(): %m\n",cnt->threadnr);
		syslog(LOG_ERR, "[%d] socket(): %m",cnt->threadnr);
		return -1;
	}

	memset(&sin, 0, sizeof(struct sockaddr_in));
	sin.sin_family=AF_INET;
	sin.sin_port=htons(port);
	if (local)
		sin.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
	else
		sin.sin_addr.s_addr=htonl(INADDR_ANY);

	setsockopt(sl, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

	if (bind(sl, (struct sockaddr *)&sin, sizeof(struct sockaddr_in))==-1) {
		printf("[%d] bind(): %m\n",cnt->threadnr);
		syslog(LOG_ERR, "[%d] bind(): %m",cnt->threadnr);
		close(sl);
		return -1;
	}

	if (listen(sl, DEF_MAXWEBQUEUE)==-1) {
		printf("[%d] listen(): %m\n",cnt->threadnr);
		syslog(LOG_ERR, "[%d] listen(): %m",cnt->threadnr);
		close(sl);
		return -1;
	}

	return sl;
}

int http_acceptsock(int sl, struct context *cnt)
{
	int sc, i;
	struct sockaddr_in sin;
	int addrlen=sizeof(struct sockaddr_in);

	if ((sc=accept(sl, (struct sockaddr *)&sin, &addrlen))>=0) {
		i=1;
		ioctl(sc, FIONBIO, &i);
		return sc;
	}
	printf("[%d] accept(): %m\n",cnt->threadnr);
	syslog(LOG_ERR, "[%d] accept(): %m",cnt->threadnr);

	return -1;
}

void webcam_close(struct webcam *list)
{
	struct webcam *next=list->next;
	
	while (next) {
		list=next;
		next=list->next;
		if (list->tmpfile) {
			fclose(list->tmpfile->fd);
			free(list->tmpfile);
		}
		fclose(list->fwrite);
		close(list->socket);
		free(list);
	}
}

void webcam_flush(struct webcam *list, int *stream_count, int lim)
{
	char buffer[2048];
	int readcount=2048;
	void *tmp;

	while (list->next) {
		list=list->next;
		if (!list->tmpfile)
			continue;
		fseek(list->tmpfile->fd, list->filepos, SEEK_SET);
		readcount=2048;
		while (readcount==2048 && list->tmpfile) {
			readcount=fread(buffer, 1, 2048, list->tmpfile->fd);
			if (readcount>0) {
				readcount=write(list->socket, buffer, readcount);
				if (readcount>0)
					list->filepos+=readcount;
				if (list->filepos>=list->tmpfile->size || 
				    (readcount<0 && errno!=EAGAIN)) {
					list->tmpfile->ref--;
					if (list->tmpfile->ref<=0) {
						fclose(list->tmpfile->fd);
						free(list->tmpfile);
					}
					list->tmpfile=NULL;
					list->nr++;
				}
				if ( (readcount<0 && errno!=EAGAIN) ||
				     (lim && !list->tmpfile && list->nr>lim) )
				{
					fclose(list->fwrite);
					close(list->socket);
					if (list->next)
						list->next->prev=list->prev;
					list->prev->next=list->next;
					tmp=list;
					list=list->prev;
					free(tmp);
					(*stream_count)--;
				}
			}
		}
	}
}

struct webcam_tmpfile *webcam_tmpfile(void)
{
	struct webcam_tmpfile *file=mymalloc(sizeof(struct webcam_tmpfile));
	file->ref=0;
	file->fd=tmpfile();
	if (!file->fd) {
		free(file);
		return NULL;
	}
	return file;
}

void webcam_add_client(struct webcam *list, int sc)
{
	struct webcam *new=mymalloc(sizeof(struct webcam));
	new->socket=sc;
	new->fwrite=fdopen(sc, "a");
	new->tmpfile=NULL;
	new->filepos=0;
	new->nr=0;
	new->prev=list;
	new->next=list->next;
	if (new->next)
		new->next->prev=new;
	list->next=new;
	new->last=0;

    
	fprintf(new->fwrite, "HTTP/1.1 200 OK\r\n");
	fprintf(new->fwrite, "Server: Motion/"VERSION"\r\n");
	fprintf(new->fwrite, "Connection: close\r\n");
	fprintf(new->fwrite, "Max-Age: 0\r\n");
	fprintf(new->fwrite, "Expires: 0\r\n");
	fprintf(new->fwrite, "Cache-Control: no-cache, private\r\n");
	fprintf(new->fwrite, "Pragma: no-cache\r\n");
	fprintf(new->fwrite, "Content-type: multipart/x-mixed-replace; boundary=BoundaryString\r\n");
	fflush(new->fwrite);
}

void webcam_add_write(struct webcam *list, struct webcam_tmpfile *tmpfile, int fps)
{
	struct timeval curtimeval;
	unsigned long int curtime;

	gettimeofday(&curtimeval, NULL);
	curtime=curtimeval.tv_usec+1000000L*curtimeval.tv_sec;
	while (list->next) {
		list=list->next;
		if (list->tmpfile==NULL && (curtime-list->last)>=1000000L/fps) {
			list->last=curtime;
			list->tmpfile=tmpfile;
			tmpfile->ref++;
			list->filepos=0;
			fflush(list->fwrite);
			fprintf(list->fwrite, "\r\n--BoundaryString\r\n");
			fprintf(list->fwrite, "Content-type: image/jpeg\r\n");
			fprintf(list->fwrite, "Content-Length: %ld", list->tmpfile->size);
			fprintf(list->fwrite, "\r\n\r\n");
			fflush(list->fwrite);
		}
	}
	if (tmpfile->ref<=0) {
		fclose(tmpfile->fd);
		free(tmpfile);
	}
}

int webcam_check_write(struct webcam *list)
{
	int write=0;
	while (list->next) {
		list=list->next;
		if (list->tmpfile==NULL)
			write=1;
	}
	return write;
}

/* This function is called from motion.c for each motion thread starting up.
 * The function setup the incomming tcp socket that the clients connect to
 * The function returns an integer representing the socket.
 */
int webcam_init(struct context *cnt)
{
	cnt->webcam.socket=http_bindsock(cnt->conf.webcam_port, cnt->conf.webcam_localhost, cnt);
	cnt->webcam.next=NULL;
	cnt->webcam.prev=NULL;
	signal(SIGPIPE, SIG_IGN);
	return cnt->webcam.socket;
}

void webcam_stop(struct context *cnt)
{	
	if (cnt->conf.setup_mode)
		printf("[%d] Closing webcam listen socket\n", cnt->threadnr);
	close(cnt->webcam.socket);
	if (cnt->conf.setup_mode)
		printf("[%d] Closing active webcam sockets\n", cnt->threadnr);
	webcam_close(&cnt->webcam);
}

int webcam_put(struct context *cnt, char *image)
{
	struct timeval timeout;
	struct webcam_tmpfile *tmpfile;
	fd_set fdread;
	int sl=cnt->webcam.socket;
	int sc;

	timeout.tv_sec=0;
	timeout.tv_usec=0;
	FD_ZERO(&fdread);
	FD_SET(cnt->webcam.socket, &fdread);
	if ((cnt->stream_count < DEF_MAXSTREAMS) &&
	    (select(sl+1, &fdread, NULL, NULL, &timeout)>0)) {
		sc=http_acceptsock(sl, cnt);
		webcam_add_client(&cnt->webcam, sc);
		cnt->stream_count++;
	}
	webcam_flush(&cnt->webcam, &cnt->stream_count, cnt->conf.webcam_limit);
	if (webcam_check_write(&cnt->webcam)) {
		tmpfile=webcam_tmpfile();
		if (tmpfile) {
			put_picture_fd(cnt, tmpfile->fd, image, cnt->conf.webcam_quality);
			fseek(tmpfile->fd, 0, SEEK_END);
			tmpfile->size=ftell(tmpfile->fd);
			webcam_add_write(&cnt->webcam, tmpfile, cnt->conf.webcam_maxrate);
		} else {
			const char msg[]="Error creating tmpfile: ";
			printf("[%d] %s%m\n", cnt->threadnr, msg);
			syslog(LOG_ERR, "[%d] %s%m", cnt->threadnr, msg);
		}
	}
	webcam_flush(&cnt->webcam, &cnt->stream_count, cnt->conf.webcam_limit);
	return 0;
}
