/*
 * handle dvb devices
 * import vdr channels.conf files
 */

#include <features.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/time.h>
#include <sys/types.h>

#include "dvb_tune.h"
#include "dvb_stream.h"
#include "dvb_debug.h"

#define TIMEOUT_SECS	3
#define MAX_PID			0x1fff

// ISO 13818-1
#define SYNC_BYTE		0x47
#define TS_PACKET_LEN	188

// create a buffer that is a number of packets long
// (this is approx 4k)
#define BUFFSIZE		(22 * TS_PACKET_LEN)


// If large file support is not included, then make the value do nothing
#ifndef O_LARGEFILE
#define O_LARGEFILE	0
#endif

/* ----------------------------------------------------------------------- */
int write_stream(struct dvb_state *h, char *filename, int sec)
{
time_t start, end, now, prev;
char buffer[BUFFSIZE];
int file;
int count;
int rc;
unsigned done ;

    if (sec <= 0)
    {
		fprintf(stderr, "Invalid duration (%d)\n", sec);
		return(1);
    }

    if (-1 == h->dvro)
    {
		fprintf(stderr,"dvr device not open\n");
		return(2);
    }
    
    file = open(filename, O_WRONLY | O_TRUNC | O_CREAT | O_LARGEFILE, 0666);
    if (-1 == file) {
		fprintf(stderr,"open %s: %s\n",filename,strerror(errno));
		return(3);
    }

    count = 0;
    start = time(NULL);
    end = sec + time(NULL);
	for (done=0; !done;)
	{
		rc = read(h->dvro, buffer, sizeof(buffer));
		switch (rc) {
		case -1:
			perror("read");
			return(100);
		case 0:
			fprintf(stderr,"EOF\n");
			return(101);
		default:
			write(file, buffer, rc);
			count += rc;
			break;
		}
		now = time(NULL);

		if (dvb_debug)
		{		
			if (prev != now)
			{
				fprintf(stderr, "%d / %d : %d bytes\n", now-start, end-start, rc) ;
				prev = now ;
			}
		}

		if (now >= end)
		{
			++done ;
			break;
		}	
	}
    
    close(file);

    return 0;
}


/* ----------------------------------------------------------------------- */
int write_stream_demux(struct dvb_state *h, struct multiplex_pid_struct *pid_list, unsigned num_entries)
{
time_t now, prev;
char buffer[BUFFSIZE];
char *bptr ;
int status;
int rc;
unsigned sync ;
unsigned ts_pid ;
unsigned pid_index ;
int running ;
unsigned byte_num ;
int buffer_len ;

    if (-1 == h->dvro)
    {
		fprintf(stderr,"dvr device not open\n");
		return(2);
    }
    
    running = num_entries ;
    sync = 1 ;
    while (running > 0)
    {
		if (dvb_debug)
			fprintf(stderr, "waiting for sync...\n") ;

    	// wait for sync byte
    	bptr = buffer ;
    	status = getbuff(h, buffer, 1) ;
    	if (status) return (status) ;
    	
    	// wait for sync byte, but abort if we've waited for at least 4 packets and not found it
    	byte_num=0;
    	while ( (buffer[0] != SYNC_BYTE) && (byte_num < (4*TS_PACKET_LEN)) )
    	{
	    	status = getbuff(h, buffer, 1) ;
	    	if (status) return (status) ;
	    	
	    	++byte_num ;
    	}
    	sync = 0 ;
    	
    	// did we find it?
    	if (buffer[0] != SYNC_BYTE)
    	{
    		return(4) ;
    	}

		if (dvb_debug >= 10)
			fprintf(stderr, "handling TS packets...(buffer @ %p)\n", buffer) ;

		// get rest of TS packet
    	status = getbuff(h, &buffer[1], (BUFFSIZE-1)) ;
    	buffer_len = BUFFSIZE ;
    	bptr = buffer ;
    	while ( (running>0) && !sync)
    	{
	    	if (status) return (status) ;

			if (dvb_debug >= 10)
				fprintf(stderr, "Start of loop : 0x%02x (bptr @ %p) %d bytes left\n", bptr[0], bptr, buffer_len) ;

    		// start of each packet
			now = time(NULL);

			// check sync byte
			if (bptr[0] != SYNC_BYTE)
			{
				// re-sync
				++sync ; 

				if (dvb_debug >= 10)
					fprintf(stderr, "! Resync required : 0x%02x (bptr @ %p)\n", bptr[0], bptr) ;
			}
			else
			{
				/* decode header
				#	sync_byte 8 bslbf
				#
				#	transport_error_indicator 1 bslbf
				#	payload_unit_start_indicator 1 bslbf
				#	transport_priority 1 bslbf
				#	PID 13 uimsbf
				#
				#	transport_scrambling_control 2 bslbf
				#	adaptation_field_control 2 bslbf
				#	continuity_counter 4 uimsbf
				#
				#	if(adaptation_field_control = = '10' || adaptation_field_control = = '11'){
				#		adaptation_field()
				#	}
				#	if(adaptation_field_control = = '01' || adaptation_field_control = = '11') {
				#		for (i = 0; i < N; i++){
				#		data_byte 8 bslbf
				#		}
				#	}
				*/
				ts_pid = ((bptr[1] & 0x1f) << 8) | (bptr[2] & 0xff) & MAX_PID ;
				if (dvb_debug >= 10)
				{
					if (prev != now)
					{
						fprintf(stderr, "-> TS PID 0x%x (%u)\n", ts_pid, ts_pid) ;
					}
				}
				
				// search the pid list for a match (also keep done flags up to date - in case there are no packets for this pid!)
				for (pid_index=0; pid_index < num_entries; ++pid_index)
				{
					// debug display
					if (dvb_debug)
					{
						if (prev != now)
						{
							fprintf(stderr, " + + PID %d : %d pkts : ", pid_list[pid_index].pid, pid_list[pid_index].pkts) ;
							if (pid_list[pid_index].done)
							{
								fprintf(stderr, "complete") ;
							}
							else
							{
								if (now >= pid_list[pid_index].file_info->start)
								{
									if (now <= pid_list[pid_index].file_info->end)
									{
										fprintf(stderr, "recording (%d secs remaining)", 
											pid_list[pid_index].file_info->end - now) ;
									}
								}
								else
								{
									fprintf(stderr, "starting in %d secs ...", 
										pid_list[pid_index].file_info->start - now) ;
								}
							}
							fprintf(stderr, "\n") ;
						}
					}

					// skip if done 
					if (!pid_list[pid_index].done)
					{
						// matching pid?
						if (ts_pid == pid_list[pid_index].pid)
						{
							// check start time
							if (now >= pid_list[pid_index].file_info->start)
							{
								// write this packet to the corresponding file
								write(pid_list[pid_index].file_info->file, bptr, TS_PACKET_LEN);

								// debug
								pid_list[pid_index].pkts++;

								if (dvb_debug >= 10)
									fprintf(stderr, " + + Written PID %d : total %d pkts : ", pid_list[pid_index].pid, pid_list[pid_index].pkts) ;
								
							}
						}

						// check end time - mark as done if elapsed
						if (now > pid_list[pid_index].file_info->end)
						{
							pid_list[pid_index].done = 1 ;
							--running ;
						}
					}
				} // for each pid 
				
				// update buffer
				buffer_len -= TS_PACKET_LEN ;
				bptr += TS_PACKET_LEN ;
				if (buffer_len < TS_PACKET_LEN)
				{
					// next packets
			    	status = getbuff(h, buffer, BUFFSIZE) ;
			    	buffer_len = BUFFSIZE ;
			    	bptr = buffer ;

					if (dvb_debug >= 10)
						fprintf(stderr, "Reload buffer : 0x%02x (bptr @ %p) %d bytes left\n", bptr[0], bptr, buffer_len) ;

				}

			} // if sync

			prev = now ;

			if (dvb_debug >= 10)
				fprintf(stderr, "End of loop : 0x%02x (bptr @ %p) %d bytes left\n", bptr[0], bptr, buffer_len) ;


    	} // while in sync

    } // while running
    
    return 0;
}

/* ----------------------------------------------------------------------- */
int getbuff(struct dvb_state *h, char *buffer, int count)
{
int rc ;
int status ;
int data_ready ;

	status = 0 ;
	
	// wait for data (or time out)
	data_ready = input_timeout(h->dvro, TIMEOUT_SECS) ;
	if (data_ready != 1)
	{
		fprintf(stderr,"reading %d bytes\n", count);
		if (data_ready < 0)
		{
			perror("read");
			return(200);
		}
		else
		{
			fprintf(stderr,"timed out\n");
			return(201);
		}
	} 
	
	// got to here so data is available
	rc = read(h->dvro, buffer, count);
	switch (rc) {
	case -1:
		fprintf(stderr,"reading %d bytes\n", count);
		perror("read");
		return(100);
	case 0:
		fprintf(stderr,"EOF\n");
		return(101);
	default:
		// check length
		if (rc != count)
			return(102);
		break;
	}
	return(status) ;
}

/* ----------------------------------------------------------------------- */
// Wait for data ready or timeout     
int
input_timeout (int filedes, unsigned int seconds)
{
   fd_set set;
   struct timeval timeout;
 
   /* Initialize the file descriptor set. */
   FD_ZERO (&set);
   FD_SET (filedes, &set);
 
   /* Initialize the timeout data structure. */
   timeout.tv_sec = seconds;
   timeout.tv_usec = 0;
 
   /* select returns 0 if timeout, 1 if input available, -1 if error. */
   return TEMP_FAILURE_RETRY (select (FD_SETSIZE,
                                      &set, NULL, NULL,
                                      &timeout));
}


// void * memcpy ( void * destination, const void * source, size_t num );
