/***************************************************************************
                          dvbevents.cpp  -  description
                             -------------------
    begin                : Tue Dec 16 2003
    copyright            : (C) 2003-2005 by Christophe Thommeret
    email                : hftom@free.fr
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>

#include <qdatetime.h>

#include "dvbevents.h"



EventTsidList::EventTsidList( unsigned short t )
{
	tsid = t;
	events.setAutoDelete( true );
}



EventTsidList::~EventTsidList()
{
	events.clear();
}



EventSourceList::EventSourceList( QString src )
{
	source = src;
	tsidList.setAutoDelete( true );
}



EventSourceList::~EventSourceList()
{
	tsidList.clear();
}



DVBevents::DVBevents( bool *ok, int anum, int tnum, const QString &charset ) : DVBsection( ok, anum, tnum, charset )
{
	srcList.setAutoDelete( true );
	connect ( &cleanTimer, SIGNAL(timeout()), this, SLOT(setClean()) );
	clean = false;
	cleanTimer.start( 3000 );
	demuxOpened = false;
	isRunning = true;
	pf[0].fd = fdDemux;
	pf[0].events = POLLIN;
	start();
}



DVBevents::~DVBevents()
{
	isRunning = false;
	if ( !wait(2000) ) {
		terminate();
		wait();
	}
	srcList.clear();
	cleanTimer.stop();
}



void DVBevents::doClean( bool b )
{
	if ( b ) {
		if ( cleanTimer.isActive() )
			return;
		cleanTimer.start( 3000 );
	}
	else {
		cleanTimer.stop();
		clean = false;
	}
}



bool DVBevents::shortEventDesc( unsigned char *buf, EventDesc *desc )
{
	QString name, text;
	int len, len2;
	ShortEvent *ev;

	if ( !safeLen( buf+6 ) )
		return false;
	len = getBits(buf,40,8);
	if ( !safeLen( buf+6+len ) )
		return false;
	name = getText( buf+6, len );
	if ( !safeLen( buf+6+len+1 ) )
		return false;
	len2 = getBits(buf+6+len,0,8);
	if ( !safeLen( buf+7+len+len2 ) )
		return false;
	text = getText( buf+7+len, len2);
	if ( desc->title.isEmpty() ) {
		desc->title=name;
		desc->subtitle=text;
		return true;
	}
	desc->shortEvents.append( new ShortEvent() );
	ev = desc->shortEvents.getLast();
	ev->name = name;
	ev->text = text;
	return true;
}



bool DVBevents::extEventDesc( unsigned char *buf, EventDesc *desc )
{
	int loop, len1, len2;
	unsigned char *b = buf;
	QString s;

	if ( !safeLen( b+7 ) )
		return false;
	loop = getBits(b+6,0,8);
	b +=7;

	while ( loop>0 ) {
		if ( !safeLen( b+1 ) )
			return false;
		len1 = getBits(b,0,8);
		if ( !safeLen( b+1+len1 ) )
			return false;
		s = getText(b+1,len1);
		if ( !safeLen( b+1+len1+1 ) )
			return false;
		len2 = getBits(b+1+len1,0,8);
		if ( !safeLen( buf+2+len1+len2 ) )
			return false;
		if ( !s.isEmpty() )
			s = s+" : ";
		s = s+getText(b+2+len1,len2);
		desc->extEvents.append( new QString( s ) );
		b +=(2+len1+len2);
		loop -=(2+len1+len2);
	}
	if ( !safeLen( b+1 ) )
		return false;
	len1 = getBits(b,0,8);
	if ( !safeLen( b+1+len1 ) )
		return false;
	s = getText(b+1,len1);
	desc->extEvents.append( new QString( s ) );
	return true;
}



bool DVBevents::tableEIT( unsigned char* buffer )
{
	unsigned char* buf = buffer;
	unsigned int length, loop, sid, tid, eid, tsid, sn, lsn, nid;
	int i, sec;
	EventDesc *desc=0, *itdesc=0;
	EventTsidList *tlist;
	QPtrList<EventDesc> *currentEvents;
	bool nodesc, process;
	QDateTime start, cur, dt;

	tid = getBits(buf,0,8);
	length = getBits(buf,12,12);
	sid = getBits(buf,24,16);
	sn = getBits(buf,48,8);
	lsn = getBits(buf,56,8);
	tsid = getBits(buf,64,16);
	nid = getBits(buf,80,16);
	length -=11;
	buf +=14;

	QPtrListIterator<EventTsidList> tsiditer( currentSrcList->tsidList );
	tsiditer.toFirst();
	currentEvents = 0;
	while ( (tlist=tsiditer.current())!=0 ) {
		if ( tlist->tsid==tsid ) {
			currentEvents = &tlist->events;
			break;
		}
		++tsiditer;
	}
	if ( !currentEvents ) {
		currentSrcList->tsidList.append( new EventTsidList( tsid ) );
		currentEvents = &currentSrcList->tsidList.getLast()->events;
	}
	QPtrListIterator<EventDesc> it( *currentEvents );

	while ( length>4 ) {
		nodesc=process=false;
		if ( !safeLen( buf+2 ) )
			goto stop;
		eid = getBits(buf,0,16);
		if ( !safeLen( buf+2+5 ) )
			goto stop;
		start = getDateTime( buf+2 );
		nodesc=process=true;

		it.toFirst();
		while ( (desc=it.current())!=0 ) {
			if ( desc->sid==sid ) {
				if ( desc->startDateTime==start || desc->eid==eid ) {
					if ( desc->tid==0x4e && tid!=0x4e ) {
						process = false;
						nodesc = false;
						break;
					}
					else {
						nodesc = false;
						if ( desc->loop ) {
							process = false;
						}
						else {
							desc->extEvents.clear();
							desc->shortEvents.clear();
							desc->title=desc->subtitle="";
						}
						break;
					}
				}
			}
			++it;
		}

		if ( nodesc )
			desc = new EventDesc();
		if ( ++desc->loop>8 )
			desc->loop = 0;
		if ( process ) {
			if ( !safeLen( buf+10 ) )
				goto stop;
			desc->duration = getTime( buf+7 );
			if ( !safeLen( buf+11 ) )
				goto stop;
			desc->running = getBits(buf,80,3);
			desc->sid = sid;
			desc->tid = tid;
			desc->tsid = tsid;
			desc->nid = nid;
			desc->lsn = lsn;
			desc->sn = sn;
			desc->eid = eid;
		}

		if ( desc->sn != sn )
			return false;
		if ( !safeLen( buf+12 ) )
			goto stop;
		loop = getBits(buf,84,12);
		buf +=12;
		length -=(12+loop);
		while ( loop>0 ) {
			if ( process ) {
				if ( !safeLen( buf+1 ) )
					goto out;
				switch ( getBits(buf,0,8) ) {
					case 0x4D :
						if ( !shortEventDesc( buf, desc ) )
							goto out;
						break;
					case 0x4E :
						if ( !extEventDesc( buf, desc ) )
							goto out;
						break;
					default :
						break;
				}
			}
			if ( !safeLen( buf+2 ) )
				goto out;
			loop -=( getBits(buf,8,8)+2 );
			buf +=( getBits(buf,8,8)+2 );
		}
out:
		if ( process ) {
			if ( !nodesc ) {
				if ( start==desc->startDateTime )
					goto ifend;
				currentEvents->take( currentEvents->find( desc ) );
			}
			desc->startDateTime = start;
			for ( i=0; i<(int)currentEvents->count(); i++ ) {
				itdesc = currentEvents->at(i);
				if ( desc->startDateTime<itdesc->startDateTime ) {
					currentEvents->insert( i, desc );
					break;
				}
				itdesc = 0;
			}
			if ( !itdesc )
				currentEvents->append( desc );
		}
ifend:
		if ( process )
			++(desc->sn);
		if ( desc->title.length()<3 ) {
			currentEvents->remove( desc );
		}
		else if ( nodesc ) {
			cur = QDateTime::currentDateTime();
			dt = desc->startDateTime;
			sec = desc->duration.hour()*3600+desc->duration.minute()*60+desc->duration.second();
			if ( dt.addSecs( sec )<cur ) {
				currentEvents->remove( desc );
			}
			else
				desc->source = source;
		}

	}
	return true;
stop:
	fprintf( stderr, "Stop parsing EIT (%d:%d)\n", adapter, tuner );
	if ( nodesc )
		delete desc;
	return false;
}




bool DVBevents::safeLen( unsigned char* buf )
{
	if ( buf<(secbuf+readSize) )
		return true;
	fprintf( stderr, "EIT (%d:%d) : buffer overflow! Rejected\n", adapter, tuner );
	return false;
}



bool DVBevents::go( QString src, bool all )
{
	int tid;
	int i;

	if ( demuxOpened )
		return true;

	if ( all )
		tid = 0;
	else
		tid = 0x4e;

	source = src;
	for ( i=0; i<(int)srcList.count(); i++ ) {
		if ( srcList.at(i)->source==source ) {
			currentSrcList = srcList.at(i);
			break;
		}
	}
	if ( i==(int)srcList.count() ) {
		srcList.append( new EventSourceList( source ) );
		currentSrcList = srcList.getLast();
	}

	if ( !setFilter( 0x12, tid, 1000 ) )
		return false;
	demuxOpened = true;
	fprintf(stderr,"dvbEvents %d:%d started\n", adapter, tuner);
	return true;
}



void DVBevents::stop()
{
	if ( !demuxOpened )
		return;

	demuxOpened = false;
	isRunning = false;
	if ( !wait(2000) ) {
		terminate();
		wait();
		fprintf(stderr,"dvbEvents %d:%d terminated\n", adapter, tuner);
	}
	else
		fprintf(stderr,"dvbEvents %d:%d ended\n", adapter, tuner);
	stopFilter();
	isRunning = true;
	start();
}



void DVBevents::setClean()
{
	clean = true;
}



void DVBevents::run()
{
	int n=0, sec, tid;
	int skip=0;
	QDateTime dt, cur;
	EventDesc *itdesc;
	EventSourceList *slist;
	EventTsidList *tlist;
	QPtrListIterator<EventSourceList> srciter( srcList );

	setpriority(PRIO_PROCESS, 0, 19); // eit parsing is cpu eater on some astra multiplex.
	while ( isRunning ) {
		if ( clean ) {
			cur = QDateTime::currentDateTime();
			mutex.lock();
			srciter.toFirst();
			while ( (slist=srciter.current())!=0 ) {
				QPtrListIterator<EventTsidList> tsiditer( slist->tsidList );
				tsiditer.toFirst();
				while ( (tlist=tsiditer.current())!=0 ) {
					if ( !tlist->events.count() ) {
						slist->tsidList.remove( tlist );
						++tsiditer;
						continue;
					}
					QPtrListIterator<EventDesc> eventiter( tlist->events );
					eventiter.toFirst();
					while ( (itdesc=eventiter.current())!=0 ) {
						dt = itdesc->startDateTime;
						sec = itdesc->duration.hour()*3600+itdesc->duration.minute()*60+itdesc->duration.second();
						if ( dt.addSecs( sec )<cur ) {
							tlist->events.remove( itdesc );
						}
						++eventiter;
					}
					++tsiditer;
				}
				++srciter;
			}
			mutex.unlock();
			clean = false;
		}

		if ( !isRunning )
			break;

		if ( !demuxOpened ) {
			usleep( 50000 );
			continue;
		}

		if ( poll(pf,1,1000)>0 ){
			if ( pf[0].revents & POLLIN ){
				n = read( fdDemux, secbuf, 4096 );
				skip = 0;
			}
			else
				skip++;
		}
		else
			skip++;

		if (skip)
			continue;
		if ( n<16 )
			continue;
		else
			readSize = n;

		if ( !isRunning )
			break;

		tid = getBits(secbuf,0,8);
		if ( tid>0x4D && tid<0x70 ) {
			mutex.lock();
			tableEIT( secbuf );
			mutex.unlock();
		}
		usleep( 1000 ); // calm down parsing frequency
	}
}



void DVBevents::dumpEvents()
{
	EventDesc *itdesc;
	EventSourceList *slist;
	EventTsidList *tlist;
	QPtrListIterator<EventSourceList> srciter( srcList );
	QDateTime dt;

	stop();
	mutex.lock();

	srciter.toFirst();
	while ( (slist=srciter.current())!=0 ) {
		QPtrListIterator<EventTsidList> tsiditer( slist->tsidList );
		tsiditer.toFirst();
		while ( (tlist=tsiditer.current())!=0 ) {
			QPtrListIterator<EventDesc> eventiter( tlist->events );
			eventiter.toFirst();
			while ( (itdesc=eventiter.current())!=0 ) {
				if ( itdesc->startDateTime<dt )
					fprintf( stderr,"\nSORT ERROR!\n");
				else
					dt = itdesc->startDateTime;
				fprintf(stderr,"%d %d %d  %s %s\n", itdesc->tsid, itdesc->sid, itdesc->eid, itdesc->startDateTime.toString().latin1(), itdesc->title.latin1() );
				++eventiter;
			}
			++tsiditer;
		}
		++srciter;
	}

	mutex.unlock();
	go( source );
}

#include "dvbevents.moc"
