/*
 * $Id: streamts.c,v 1.18 2005/07/22 17:32:34 digi_casi Exp $
 *
 * inetd style daemon for streaming avpes, ps and ts
 *
 * Copyright (C) 2002 Andreas Oberritter <obi@tuxbox.org>
 *
 * based on code which is
 * Copyright (C) 2001 TripleDES
 * Copyright (C) 2000, 2001 Marcus Metzler <marcus@convergence.de>
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
 *
 *	http://cuberevo:<port>
 */

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include "stpti.h"

// network
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>

/* conversion buffer sizes */
#define IN_SIZE		(TS_SIZE * 256)

/* tcp packet data size */
#define PACKET_SIZE	1448
#define LISTENPORT	4000

unsigned char *buf;
unsigned char *pmt_buf;
unsigned int writebuf_size = 0;
unsigned char writebuf[PACKET_SIZE];

static int stpti4fd;
static void *st_buffer;
st_slothandles_t st_slots;
st_slothandles_t st_slots_old;

unsigned char slots_freed = 1;
unsigned char exit_flag = 0;


void packet_stdout (int socket, unsigned char * buf, int count, void * p) {

	unsigned int size;
	unsigned char *bp;
	ssize_t written;

	/*
	 * ensure, that there is always at least one complete
	 * packet inside of the send buffer
	 */
	while (writebuf_size + count >= PACKET_SIZE) {

		/*
		 * how many bytes are to be sent from the input buffer?
		 */
		size = PACKET_SIZE - writebuf_size;

		/*
		 * send buffer is not empty, so copy from
		 * input buffer to get a complete packet
		 */
		if (writebuf_size) {
			memcpy(writebuf + writebuf_size, buf, size);
			bp = writebuf;
		}

		/*
		 * if send buffer is empty, then do not memcopy,
		 * but send directly from input buffer
		 */
		else {
			bp = buf;
		}

		/*
		 * write the packet, count the amount of really
		 * written bytes
		 */
		written = write(socket, bp, PACKET_SIZE);

		/*
		 * exit on error, also means client closed connection
		 */
		if (written == -1) {
			exit_flag = 1;
			return;
		}

		/*
		 * if the packet could not be written completely, then
		 * how many bytes must be stored in the send buffer
		 * until the next packet is to be sent?
		 */
		writebuf_size = PACKET_SIZE - written;

		/*
		 * move all bytes of the packet which were not sent
		 * to the beginning of the send buffer
		 */
		if (writebuf_size)
			memmove(writebuf, bp + written, writebuf_size);

		/*
		 * advance in the input buffer
		 */
		buf += size;

		/*
		 * decrease the todo size
		 */
		count -= size;
	}

	/*
	 * if there are still some bytes left in the input buffer,
	 * then store them in the send buffer and increase send
	 * buffer size
	 */
	if (count > 0) {
		memcpy(writebuf + writebuf_size, buf, count);
		writebuf_size += count;
	}
}

static int read_data(void *ts_data, ssize_t size) {
	bytesready_t br;
	br.buffer = st_buffer;

	if (ioctl(stpti4fd, IOCTL_BYTESREADY, &br) != 0 || br.errorcode != 0) 
	{
		printf("Failed to check for bytes ready\n");
		return -2;
	} 
	else 
	{
		if (br.bytesready > 0)
		{
			if(br.bytesready > size) {
				//printf("Overflow?\n");
				br.bytesready = size;
			}

			bufferread_t bread;
			bread.buffer = st_buffer;
			bread.dst = ts_data;
			bread.dst_size = size;
			bread.bytes2read = br.bytesready;
			bread.magic[0] = 0;
			bread.magic[1] = 0;
			bread.unknown = 0;
			if (ioctl(stpti4fd, IOCTL_BUFFERREAD, &bread) != 0 || bread.errorcode != 0) 
			{
				printf("Read failed %d\n", bread.errorcode);
				exit_flag = 1;
				return -1;
			} 
			else 
			{
				return bread.bytes2read;
			}
		} 
		else
		{
			// No data ready
			return 0;
		}
	}
}

// Clean exit of program
void free_slot_buffers()
{
	if (!slots_freed)
	{
		slots_freed = 1;
		unset_buffer(stpti4fd, st_slots.vidhandle);
		unset_buffer(stpti4fd, st_slots.audhandle);
		unset_buffer(stpti4fd, st_slots.pcrhandle);
		free_buffer(stpti4fd, st_buffer);
	}
}

void clean_exit(int signal)
{
	free_slot_buffers();
	close(stpti4fd);
	exit(0);
}

void stop_stream(int signal)
{
	free_slot_buffers();
	exit_flag = 1;
	return;
}

void init_stream(int socket)
{
		int i;

		/* On channel switches, wait for new pids */
		for (i=0; i<=4; i++)
		{
			get_slot_handles(&st_slots);
			if ((st_slots.vpid != st_slots_old.vpid) && (st_slots.apid != st_slots_old.apid))
				break;

			sleep(1);
		}
		/* Getting new pids successfull?
         * If not: either box is in standby, or we timed out because channel change took too long
         */
		if (i==5)
		{
			printf("Getting new PIDS failed!\n");
			exit_flag = 1;
			slots_freed = 1; // when in standby, slots cannot be freed
			return;
		}

		if (st_slots.vpid >= 0xe000)
		{
			printf("[streamts.c] Streaming Radio Channels not supported!\n");
			exit_flag = 1;
			slots_freed = 1;
			return;
		}

		if (allocate_buffer(stpti4fd, st_slots.sthandle, &st_buffer, IN_SIZE))
			exit(1);

		if (set_buffer(stpti4fd, st_slots.vidhandle, st_buffer)) {
			exit_flag = 1; //exit(1);
			return;
		}

		if (set_buffer(stpti4fd, st_slots.audhandle, st_buffer)) {
			exit_flag = 1; //exit(1);
			return;
		}

		// important for channels, where pcr_pid != video_pid
		if (set_buffer(stpti4fd, st_slots.pcrhandle, st_buffer)) {
			exit_flag = 1; //exit(1);
			return;
		}
		slots_freed = 0;
		printf("[streamts.c] Slots initialized\n");		

		/* VLC needs PAT and PMT to play the stream.
		 * We also send it after channel switching, so the stream contiunes to play.
         */
		create_pat_pmt(pmt_buf, st_slots.pcrpid, st_slots.vpid, st_slots.apid);
		packet_stdout(socket, pmt_buf, TS_SIZE*2, NULL);

		st_slots_old = st_slots;
}

int main (int argc, char ** argv)
{
	int i = 0;
	int rc;

	signal(SIGTERM, clean_exit);
	signal(SIGINT, clean_exit);
	signal(SIGPIPE, stop_stream);

	// Network
	struct sockaddr_in  servaddr;
	int ListenSocket, ConnectionSocket;
	u_short Port = LISTENPORT;
	struct sockaddr_in cliaddr;
	socklen_t clilen = sizeof(cliaddr);

	//network-setup
	ListenSocket = socket(AF_INET, SOCK_STREAM, 0);
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(Port);

	// Ready stapi
	stpti4fd = open_stapi();
   	if (stpti4fd < 0) 
		return EXIT_FAILURE;

	buf = (unsigned char *) malloc(IN_SIZE);
	pmt_buf = (unsigned char *) malloc(TS_SIZE*2);

	if (buf == NULL || pmt_buf == NULL) {
		perror("malloc");
		return EXIT_FAILURE;
	}

	fclose(stderr);

	while ((rc = bind(ListenSocket, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_in))))
	{
		fprintf(stderr, "[streamts.c] bind to port %d failed, RC=%d...\n",Port, rc);
		if (i == 10) {
			fprintf(stderr, "[streamts.c] Giving up\n");
			exit(EXIT_FAILURE);
		}
		fprintf(stderr, "[streamts.c] %d. try, wait for 2 s\n",i++);
		sleep(2);
	}

	if (listen(ListenSocket, 5))
	{
		fprintf(stderr,"[streamts.c] listen failed\n");
		exit(EXIT_FAILURE);
	}
	printf("[streamts.c] streamts-ng v0.1 successfully started\n");

	do // listen for connection and stream, main loop
	{
		memset(&cliaddr, 0, sizeof(cliaddr));
		if((ConnectionSocket = accept(ListenSocket, (struct sockaddr *) &cliaddr, (socklen_t *) &clilen)) == -1){
			fprintf(stderr,"[streamts.c] accept failed\n");
			exit(EXIT_FAILURE);
		}

		/* send response to http client */
		write(ConnectionSocket, "HTTP/1.1 200 OK\r\nServer: streamts\r\n\r\n", 37);
		printf("[streamts.c] Client connected\n");

		st_slots_old.vpid = st_slots_old.apid = 0;
		reset_pat_counter();
		init_stream(ConnectionSocket);

		ssize_t r = 0;
		while (!exit_flag)
		{
			r = read_data(buf, IN_SIZE);

			if (r > 0)
			{
				packet_stdout(ConnectionSocket, buf, r, NULL);
				usleep(STPTI_TIMEOUT); // very important, else stpti gets overloaded with requests, and slows down
			}
			else if (r == -2) // channel changed
			{
				// buffers cannot be unset, stpti invalidates them anyway on a channel switch
				printf("[streamts.c] Channel changed\n");
				init_stream(ConnectionSocket);
			}
		}

		printf("[streamts.c] Closing connection\n");
		close(ConnectionSocket); // send EOF to video client
		free_slot_buffers();
		// reset flag and accept new connections
		exit_flag = 0;
	} while(1);

	return EXIT_SUCCESS;
}

