/*
 * dvbcam.cpp
 *
 * Copyright (C) 2008 Christophe Thommeret <hftom@free.fr>
 * Copyright (C) 2006 Christoph Pfister <christophpfister@gmail.com>
 *
 * code based on ca_zap (LGPL)
 * Copyright (C) 2004, 2005 Manu Abraham (manu@kromtek.com)
 * Copyright (C) 2006 Andrew de Quincey (adq_dvb@lidskialf.net)
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <linux/dvb/ca.h>

#include <tqapplication.h>
#include <tqthread.h>
#include <tqstring.h>
#include <tqspinbox.h>
#include <tqlabel.h>
#include <tqtextbrowser.h>
#include <tqlineedit.h>
#include <tqpushbutton.h>

#include "dvbcam.h"

#define TIMER_EVENT_MMI_OPEN 500
#define TIMER_EVENT_MMI_CLOSE 501
#define TIMER_EVENT_CAM_READY 502



DvbCam::DvbCam(int adapter, int ca_device, int demux_device, int ci_type, int maxService)
{
	Adapter     = adapter;
	CaDevice    = ca_device;
	DemuxDevice = demux_device;
	ciType = ci_type;
	CamMaxService = maxService;
	fprintf(stderr, "DvbCam: CamMaxService = %d\n", CamMaxService);

	stdcam = NULL;
	menuDialog = NULL;

	sidList.setAutoDelete( true );

	isRunning = true;
	start();
}

DvbCam::~DvbCam()
{
	isRunning = false;
	wait();
	sidMutex.lock();
	sidList.clear();
	sidMutex.unlock();
}

int DvbCam::probe( int adapter, int ca_device )
{
	int fdCa;
	ca_caps_t cap;
	ca_slot_info_t info;
	TQString s = TQString("/dev/dvb/adapter%1/ca%2").arg( adapter ).arg( ca_device );

	if ( (fdCa = open( s.ascii(), O_RDWR )) < 0) {
		fprintf(stderr, "DvbCam::probe(): %s:", s.ascii());
		perror(" ");
		return -1;
	}

	if ( ioctl(fdCa, CA_GET_CAP, &cap) == 0) {
		if ( cap.slot_num>0 ) {
			info.num = 0;
			if ( ioctl(fdCa, CA_GET_SLOT_INFO, &info) == 0) {
				if ( (info.type & CA_CI_LINK)!=0) {
					fprintf(stderr, "DvbCam::probe(): LLCI slot found on %s\n", s.ascii());
					if ( (info.flags & CA_CI_MODULE_PRESENT)!=0 ) {
						fprintf(stderr, "DvbCam::probe(): CA module present on %s\n", s.ascii());
						close (fdCa);
						return CA_CI_LINK;
					}
					else
						fprintf(stderr, "DvbCam::probe(): no CA module present on %s\n", s.ascii());
				}
				else if ( (info.type & CA_CI)!=0 ) {
					fprintf(stderr, "DvbCam::probe(): HLCI slot found on %s\n", s.ascii());
					if ( (info.flags & CA_CI_MODULE_PRESENT)!=0 ) {
						fprintf(stderr, "DvbCam::probe(): CA module present on %s\n", s.ascii());
						close (fdCa);
						return CA_CI;
					}
					else
						fprintf(stderr, "DvbCam::probe(): no CA module present on %s\n", s.ascii());
				}
				else
					fprintf(stderr, "DvbCam::probe(): unsupported CI interface on %s\n", s.ascii());
			}
			else
				fprintf(stderr, "DvbCam::Probe(): ioctl failed on %s\n", s.ascii());
		}
		else
			fprintf(stderr, "DvbCam::Probe(): no CI slot found on %s\n", s.ascii());
	}
	else
		fprintf(stderr, "DvbCam::Probe(): ioctl failed on %s\n", s.ascii());

	close (fdCa);
	return -1;
}

bool DvbCam::canPlay( ChannelDesc *chan )
{
	int i, n=0;

	TQMutexLocker locker( &sidMutex );
	for ( i=0; i<sidList.count(); ++i ) {
		if ( sidList.at(i)->getState()<CamService::Remove )
			n++;
		if ( sidList.at(i)->getChannel().name==chan->name )
			return true;
	}
	return ( n<CamMaxService );
}

void DvbCam::startService( ChannelDesc *chan )
{
	int i;
	TQMutexLocker locker( &sidMutex );
	for ( i=0; i<sidList.count(); ++i ) {
		if ( sidList.at(i)->getChannel().name==chan->name ) {
			sidList.at(i)->restart();
			return;
		}
	}
	sidList.append( new CamService( Adapter, DemuxDevice, chan, CamMaxService ) );
}

void DvbCam::stopService( ChannelDesc *chan )
{
	int i;
	TQMutexLocker locker( &sidMutex );
	for ( i=0; i<sidList.count(); ++i ) {
		if ( sidList.at(i)->getChannel().name==chan->name ) {
			sidList.at(i)->setState( CamService::Remove );
			return;
		}
	}
}

void DvbCam::resendPmts()
{
	int i;
	TQMutexLocker locker( &sidMutex );
	for ( i=0; i<sidList.count(); ++i ) {
		if ( sidList.at(i)->getState()==CamService::Added ) {
			sidList.at(i)->setState( CamService::Ready );
		}
	}
}

void DvbCam::run()
{
	int i, reset_loop, query_loop, state_loop;
	bool cam_ready = false;

	fprintf(stderr, "CamThread: started\n");

	int ca_fd = dvbca_open(Adapter, CaDevice);
	if ( ca_fd<0 ) {
		fprintf( stderr, "CamThread: [error] opening ca device failed\n" );
		return;
	}

	//fprintf(stderr, "CamThread: just using the first cam slot\n");

	reset_loop=0;
	while ( isRunning && reset_loop++<6 && !cam_ready ) {
		if ( ciType!=CA_CI ) { // do not reset HLCI
			if ( dvbca_reset(ca_fd, 0) ) {
				fprintf( stderr, "CamThread: [error] resetting cam slot failed\n" );
				//close( ca_fd );
				//ca_fd = -1;
				//return;
				usleep(1000000);
				continue;
			}
			fprintf( stderr, "CamThread: reset cam slot\n" );
		}

		state_loop=0;
		query_loop=0;
		while ( isRunning && state_loop++<30 ) {
			switch( dvbca_get_cam_state(ca_fd, 0) ) {
				case DVBCA_CAMSTATE_MISSING: {
					//fprintf(stderr, "CamThread: [error] no cam detected\n");
					//close(ca_fd);
					//return;  // FIXME: find a more reliable solution
					break;
				}
				case DVBCA_CAMSTATE_READY: {
					fprintf( stderr, "CamThread: cam 0 is ready\n" );
					cam_ready = true;
					break;
				}
				case DVBCA_CAMSTATE_INITIALISING: {
					fprintf( stderr, "CamThread: cam is initialising\n" );
					if ( ciType==CA_CI ) { // workaround needed for hlci
						fprintf(stderr, "CamThread: cam 0 is ready [hlci workaround]\n");
						cam_ready = true;
					}
					break;
				}
				default: {
					if ( ++query_loop>3 ) {
						fprintf(stderr, "CamThread: [error] querying the cam state failed\n");
						close(ca_fd);
						ca_fd = -1;
						return;
					}
				}
			}
			if(cam_ready) {
				break;
			}
			usleep(100000); // 100 ms
		}
	}

	if ( isRunning ) {
		switch(dvbca_get_interface_type(ca_fd, 0)) {
			case DVBCA_INTERFACE_LINK: {
				fprintf(stderr, "CamThread: LLCI cam slot detected\n");
				break;
			}
			case DVBCA_INTERFACE_HLCI: {
				fprintf(stderr, "CamThread: HLCI cam slot detected\n");
				break;
			}
			default: {
				fprintf(stderr, "CamThread: [error] unknown cam slot type\n");
				close(ca_fd);
				ca_fd = -1;
				return;
			}
		}
	}

	close(ca_fd);
	ca_fd = -1;

	if ( isRunning ) {
		if ( !init() ) {
			fprintf(stderr, "CamThread: [error] cam slot initialisation failed\n");
			return;
		}
	}
	fprintf(stderr, "CamThread: cam slot initialised\n");

	CamService *cs;
	while ( isRunning ) {
		if ( stdcam->stdcam->poll( stdcam->stdcam )!=EN50221_STDCAM_CAM_OK ) {
			usleep( 100000 );
			continue;
		}
		sidMutex.lock();
		for ( i=0; i<sidList.count(); ++i ) {
			cs = sidList.at(i);
			if ( cs->getState()==CamService::Remove ) {
				if ( sendPmt( cs->caPmt, cs->caPmtSize ) ) {
					fprintf( stderr, "CamThread: %s removed from camlist\n", cs->getChannel().name.ascii() );
					sidList.remove( cs );
					--i;
				}
				else
					fprintf( stderr, "CamThread: %s failed removing from camlist\n", cs->getChannel().name.ascii() );
				stdcam->stdcam->poll( stdcam->stdcam );
				usleep(100000);
			}
			else if ( cs->getState()==CamService::Destroy ) {
				fprintf( stderr, "CamThread: %s service deleted\n", cs->getChannel().name.ascii() );
				sidList.remove( cs );
				--i;
			}
		}
		for ( i=0; i<sidList.count(); ++i ) {
			cs = sidList.at(i);
			if ( cs->getState()==CamService::Ready ) {
				if ( sendPmt( cs->caPmt, cs->caPmtSize ) ) {
					cs->setState( CamService::Added );
					fprintf( stderr, "CamThread: %s pmt sent to cam\n", cs->getChannel().name.ascii() );
				}
				else
					fprintf( stderr, "CamThread: %s pmt failed sending to cam\n", cs->getChannel().name.ascii() );
				stdcam->stdcam->poll( stdcam->stdcam );
				usleep(100000);
			}
		}
		sidMutex.unlock();
		usleep( 10000 ); //sleep a bit
	}

	fprintf(stderr, "CamThread: stopping requested\n");

	if ( stdcam ) {
		if (stdcam->stdcam->destroy)
			stdcam->stdcam->destroy(stdcam->stdcam, 1);
		en50221_sl_destroy( SessionLayer );
		en50221_tl_destroy( TransportLayer );
		delete stdcam;
	}

	fprintf(stderr, "CamThread: stopped\n");
	return;
}

bool DvbCam::init()
{
	TransportLayer = en50221_tl_create(1, 16);
	if ( TransportLayer==NULL ) {
		fprintf(stderr, "Failed to create transport layer\n");
		return false;
	}
	SessionLayer = en50221_sl_create(TransportLayer, 16);
	if ( SessionLayer==NULL ) {
		fprintf(stderr, "Failed to create session layer\n");
		en50221_tl_destroy( TransportLayer );
		return false;
	}
	en50221_stdcam *sc = en50221_stdcam_create( Adapter, 0, TransportLayer, SessionLayer );
	if ( sc==NULL ) {
		en50221_sl_destroy( SessionLayer );
		en50221_tl_destroy( TransportLayer );
		fprintf(stderr, "Failed to create stdcam\n");
		return false;
	}

	stdcam = new StandardCam( sc, this );

	// hook up the AI callbacks
	if ( stdcam->stdcam->ai_resource ) {
		en50221_app_ai_register_callback( stdcam->stdcam->ai_resource, aiCallback, stdcam );
	}

	// hook up the CA callbacks
	if ( stdcam->stdcam->ca_resource ) {
		en50221_app_ca_register_info_callback( stdcam->stdcam->ca_resource, infoCallback, stdcam );
	}

	// hook up the MMI callbacks
	if ( stdcam->stdcam->mmi_resource ) {
		en50221_app_mmi_register_close_callback( stdcam->stdcam->mmi_resource, mmi_close_callback, stdcam );
		en50221_app_mmi_register_display_control_callback( stdcam->stdcam->mmi_resource, mmi_display_control_callback, stdcam );
		en50221_app_mmi_register_enq_callback( stdcam->stdcam->mmi_resource, mmi_enq_callback, stdcam );
		en50221_app_mmi_register_menu_callback( stdcam->stdcam->mmi_resource, mmi_menu_callback, stdcam );
		en50221_app_mmi_register_list_callback( stdcam->stdcam->mmi_resource, mmi_menu_callback, stdcam );
	} else {
		fprintf(stderr, "CAM Menus are not supported by this interface hardware\n");
	}

	return true;
}

bool DvbCam::sendPmt( unsigned char *pmt_buffer, int size )
{
	if ( !stdcam )
		return false;
	if ( en50221_app_ca_pmt( stdcam->stdcam->ca_resource, stdcam->stdcam->ca_session_number, pmt_buffer, size) )
		return false;

	return true;
}

int DvbCam::infoCallback(void *arg, uint8_t slot_id, uint16_t session_number, uint32_t ca_id_count, uint16_t *ca_ids)
{
	StandardCam *std =  (StandardCam*)arg;
	(void)slot_id;
	(void)session_number;

	fprintf(stderr, "CAM supports the following ca system ids:\n");
	uint32_t i;
	for ( i=0; i<ca_id_count; i++ ) {
		fprintf( stderr, "  0x%04x\n", ca_ids[i]);
	}

	TQApplication::postEvent( std->dvbcam, new TQTimerEvent(TIMER_EVENT_CAM_READY ) );

	return 0;
}

int DvbCam::aiCallback(void *arg, uint8_t slot_id, uint16_t session_number, uint8_t application_type, uint16_t application_manufacturer, uint16_t manufacturer_code, uint8_t menu_string_length, uint8_t *menu_string)
{
	StandardCam *std =  (StandardCam*)arg;
	(void)slot_id;
	(void)session_number;

	fprintf(stderr, "CAM Application type: %02x\n", application_type);
	std->dvbcam->setAppType( TQString("0x%1").arg(application_type, 0, 16 ) );
	fprintf(stderr, "CAM Application manufacturer: %04x\n", application_manufacturer);
	std->dvbcam->setAppManu( TQString("0x%1").arg(application_manufacturer, 0, 16 ) );
	fprintf(stderr, "CAM Manufacturer code: %04x\n", manufacturer_code);
	std->dvbcam->setManuCode( TQString("0x%1").arg(manufacturer_code, 0, 16 ) );
	fprintf(stderr, "CAM Menu string: %.*s\n", menu_string_length, menu_string);
	TQString s = (const char*)menu_string;
	s.truncate( menu_string_length );
	std->dvbcam->setMenuString( s );
	return 0;
}

int DvbCam::mmi_close_callback(void *arg, uint8_t slot_id, uint16_t session_number, uint8_t cmd_id, uint8_t delay)
{
	fprintf( stderr,"mmi_close_callback, delay=%d\n",delay);
	StandardCam *std = (StandardCam*)arg;
	(void) slot_id;
	(void) session_number;
	(void) cmd_id;
	(void) delay;

	// note: not entirely correct as its supposed to delay if asked
	std->mmi_state = MMI_STATE_CLOSED;
	TQApplication::postEvent( std->dvbcam, new TQTimerEvent(TIMER_EVENT_MMI_CLOSE ) );
	return 0;
}

int DvbCam::mmi_display_control_callback(void *arg, uint8_t slot_id, uint16_t session_number, uint8_t cmd_id, uint8_t mmi_mode)
{
	fprintf( stderr,"mmi_display_control_callback\n");
	struct en50221_app_mmi_display_reply_details reply;
	StandardCam *std = (StandardCam*)arg;
	(void) slot_id;

	// don't support any commands but set mode
	if ( cmd_id!=MMI_DISPLAY_CONTROL_CMD_ID_SET_MMI_MODE ) {
		en50221_app_mmi_display_reply( std->stdcam->mmi_resource, session_number, MMI_DISPLAY_REPLY_ID_UNKNOWN_CMD_ID, &reply );
		return 0;
	}

	// we only support high level mode
	if ( mmi_mode!=MMI_MODE_HIGH_LEVEL ) {
		en50221_app_mmi_display_reply( std->stdcam->mmi_resource, session_number, MMI_DISPLAY_REPLY_ID_UNKNOWN_MMI_MODE, &reply );
		return 0;
	}

	// ack the high level open
	reply.u.mode_ack.mmi_mode = mmi_mode;
	en50221_app_mmi_display_reply( std->stdcam->mmi_resource, session_number, MMI_DISPLAY_REPLY_ID_MMI_MODE_ACK, &reply );
	std->mmi_state = MMI_STATE_OPEN;
	return 0;
}

int DvbCam::mmi_enq_callback(void *arg, uint8_t slot_id, uint16_t session_number, uint8_t blind_answer, uint8_t expected_answer_length,
		uint8_t *text, uint32_t text_size)
{
	fprintf( stderr,"mmi_enq_callback\n");
	StandardCam *std = (StandardCam*)arg;
	(void) slot_id;
	(void) session_number;

	TQString s;

	TQMutexLocker locker( &std->mutex );

	std->menuList.clear();
	fprintf(stderr, "%.*s: ", text_size, text);
	s = (const char*)text;
	s.truncate( text_size );
	std->menuList.append( s );
	fflush(stdout);

	std->mmi_enq_blind = blind_answer;
	std->mmi_enq_length = expected_answer_length;
	std->mmi_state = MMI_STATE_ENQ;
	std->menuType = MMI_MENU;

	return 0;
}

int DvbCam::mmi_menu_callback(void *arg, uint8_t slot_id, uint16_t session_number, struct en50221_app_mmi_text *title,
		struct en50221_app_mmi_text *sub_title, struct en50221_app_mmi_text *bottom, uint32_t item_count,
		struct en50221_app_mmi_text *items, uint32_t item_raw_length, uint8_t *items_raw)
{
	fprintf( stderr,"mmi_menu_callback, session=%d\n",session_number);
	StandardCam *std = (StandardCam*)arg;
	(void) slot_id;
	(void) session_number;
	(void) item_raw_length;
	(void) items_raw;

	TQString s;

	TQMutexLocker locker( &std->mutex );
	std->menuList.clear();

	fprintf(stderr, "------------------------------\n");

	if (title->text_length) {
		fprintf(stderr, "%.*s\n", title->text_length, title->text);
		s = (const char*)title->text;
		s.truncate( title->text_length );
		std->menuList.append( s );
	}
	if (sub_title->text_length) {
		fprintf(stderr, "%.*s\n", sub_title->text_length, sub_title->text);
		s = (const char*)sub_title->text;
		s.truncate( sub_title->text_length );
		std->menuList.append( s );
	}

	uint32_t i;
	fprintf(stderr, "0. Quit menu\n");
	std->menuList.append( "0. Quit menu" );
	for(i=0; i< item_count; i++) {
		fprintf(stderr, "%i. %.*s\n", i+1, items[i].text_length, items[i].text);
		s = (const char*)items[i].text;
		s.truncate( items[i].text_length );
		std->menuList.append( TQString("%1. %2").arg(i+1).arg(s) );

	}

	if (bottom->text_length) {
		fprintf(stderr, "%.*s\n", bottom->text_length, bottom->text);
		s = (const char*)bottom->text;
		s.truncate( bottom->text_length );
		std->menuList.append( s );
	}
	fflush(stdout);

	std->mmi_state = MMI_STATE_MENU;
	std->menuType = MMI_MENU;

	TQApplication::postEvent( std->dvbcam, new TQTimerEvent(TIMER_EVENT_MMI_OPEN ) );

	return 0;
}

void DvbCam::timerEvent( TQTimerEvent *e )
{
	switch ( e->timerId() ) {
		case TIMER_EVENT_MMI_OPEN:
			showMMI();
			break;
		case TIMER_EVENT_MMI_CLOSE:
			closeMMI();
			break;
		case TIMER_EVENT_CAM_READY:
			resendPmts();
			break;
	}
}

void DvbCam::showMMI()
{
	if ( !menuDialog && stdcam ) {
		menuDialog = new MCamMenuDialog( stdcam );
		connect( menuDialog, TQT_SIGNAL(enteredResponse(TQString)), this, TQT_SLOT(mmiResponse(TQString)) );
		menuDialog->exec();
		closeMMI();
	}
}

void DvbCam::closeMMI()
{
	if ( menuDialog ) {
		delete menuDialog;
		menuDialog = 0;
		if ( stdcam && stdcam->stdcam->mmi_resource ) {
			en50221_app_mmi_close( stdcam->stdcam->mmi_resource, stdcam->stdcam->mmi_session_number, MMI_CLOSE_MMI_CMD_ID_IMMEDIATE, 0 );
			stdcam->mmi_state = MMI_STATE_CLOSED;
		}
	}
}

int DvbCam::showCamDialog()
{

	CamDialog dlg;
	dlg.maxServiceSpin->setValue( getCamMaxService() );
	dlg.appTypeLab->setText( getAppType() );
	dlg.appManuLab->setText( getAppManu() );
	dlg.manuCodeLab->setText( getManuCode() );
	dlg.menuStringLab->setText( getMenuString() );
	connect( dlg.camMenuBtn, TQT_SIGNAL(clicked()), this, TQT_SLOT(enterMenu()) );
	dlg.exec();
	CamMaxService = dlg.maxServiceSpin->value();
	return CamMaxService;
}

void DvbCam::enterMenu()
{
	if ( stdcam && stdcam->stdcam->ai_resource )
		en50221_app_ai_entermenu( stdcam->stdcam->ai_resource, stdcam->stdcam->ai_session_number );
}

void DvbCam::mmiResponse( TQString s )
{
	TQString res = s.stripWhiteSpace();

	switch( stdcam->mmi_state ) {
		case MMI_STATE_CLOSED:
		case MMI_STATE_OPEN: {
			en50221_app_ai_entermenu(stdcam->stdcam->ai_resource, stdcam->stdcam->ai_session_number);
			fprintf(stderr,"en50221_app_ai_entermenu 2\n");
			break;
		}
		case MMI_STATE_ENQ: {
			if ( res.isEmpty() ) {
				en50221_app_mmi_answ( stdcam->stdcam->mmi_resource, stdcam->stdcam->mmi_session_number, MMI_ANSW_ID_CANCEL, NULL, 0);
			}
			else {
				en50221_app_mmi_answ( stdcam->stdcam->mmi_resource, stdcam->stdcam->mmi_session_number, MMI_ANSW_ID_ANSWER, (uint8_t*)res.ascii(), res.length() );
			}
			stdcam->mmi_state = MMI_STATE_OPEN;
			break;
		}
		case MMI_STATE_MENU: {
			en50221_app_mmi_menu_answ( stdcam->stdcam->mmi_resource, stdcam->stdcam->mmi_session_number, res.toInt() );
			stdcam->mmi_state = MMI_STATE_OPEN;
			break;
		}
	}
}




MCamMenuDialog::MCamMenuDialog( StandardCam *sc )
{
	stdcam = sc;
	connect( inputLine, TQT_SIGNAL(returnPressed()), this, TQT_SLOT(validateClicked()) );
	connect( &readTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(setMenu()) );
	readTimer.start( 500 );
}

void MCamMenuDialog::setMenu()
{
	if ( !stdcam )
		return;
	TQMutexLocker locker( &stdcam->mutex );
	if ( stdcam->menuType==MMI_MENU )
		menuText->setText( stdcam->menuList.join("\n") );
}

void MCamMenuDialog::validateClicked()
{
	emit enteredResponse( inputLine->text() );
	inputLine->clear();
}





// class CamService
CamService::CamService( int adapter, int demux_device, ChannelDesc *chan, int maxService )
{
	Adapter     = adapter;
	DemuxDevice = demux_device;
	CamMaxService = maxService;
	channel = *chan;
	parsedPmt = NULL;
	state = NotReady;
	isRunning = true;
	start();
}

CamService::~CamService()
{
	stop();
}

void CamService::restart()
{
	setState( NotReady );
}

void CamService::stop()
{
	isRunning = false;
	wait();
}

int CamService::createSectionFilter(uint16_t pid, uint8_t table_id)
{
	// open the demuxer
	int demux_fd = dvbdemux_open_demux(Adapter, DemuxDevice, 0);
	if(demux_fd < 0) {
		return -1;
	}

	// create a section filter
	uint8_t filter[18] = {table_id};
	uint8_t mask[18] = {0xff};
	if(dvbdemux_set_section_filter(demux_fd, pid, filter, mask, 1, 1)) { // crc check on
		close(demux_fd);
		return -1;
	}

	return demux_fd;
}

int CamService::processPat(int demux_fd)
{
	// read section
	unsigned char si_buf[4096];
	int size = read( demux_fd, si_buf, sizeof(si_buf) );
	if ( size<0 )
		return -1;

	// parse section
	section *parsed_section = section_codec( si_buf, size );
	if ( parsed_section==NULL )
		return -1;

	// parse section_ext
	section_ext *parsed_section_ext = section_ext_decode( parsed_section, 1 ); // crc check on
	if ( parsed_section_ext==NULL )
		return -1;

	// parse pat
	mpeg_pat_section *parsed_pat = mpeg_pat_section_codec( parsed_section_ext );
	if ( parsed_pat==NULL )
		return -1;

	// try and find the requested program
	mpeg_pat_program *cur_program;
	mpeg_pat_section_programs_for_each( parsed_pat, cur_program ) {
		if ( cur_program->program_number==channel.sid )
			return cur_program->pid;
	}

	fprintf( stderr, "CamService (%s): [warning] channel couldn't be found in PAT\n", channel.name.ascii() );

	return -1;
}

int CamService::processPmt( int demux_fd )
{
	// read section
	memset( pmtBuffer, 0, sizeof(pmtBuffer) );
	int size = read( demux_fd, pmtBuffer, sizeof(pmtBuffer) );
	if ( size<=0 )
		return -1;

	// parse section
	section *parsed_section = section_codec( pmtBuffer, size );
	if ( parsed_section==NULL )
		return -1;

	// parse section_ext
	section_ext *parsed_section_ext = section_ext_decode( parsed_section, 1 ); // crc check on
	if ( parsed_section_ext==NULL )
		return -1;

	// parse pmt
	struct mpeg_pmt_section *pmt = mpeg_pmt_section_codec( parsed_section_ext );
	if ( pmt==NULL )
		return -1;

	parsedPmt = pmt;
	return parsedPmt->head.version_number;
}

void CamService::setState( PmtState st )
{
	TQMutexLocker locker( &mutex );
	switch ( st ) {
		case Ready: {
			int listmgnt;
			if ( CamMaxService==1 )
				listmgnt = CA_LIST_MANAGEMENT_ONLY;
			else
				listmgnt = CA_LIST_MANAGEMENT_ADD;
			if ( state>Ready )
				listmgnt = CA_LIST_MANAGEMENT_UPDATE;
			if ( setCaPmt( listmgnt, CA_PMT_CMD_ID_OK_DESCRAMBLING ) )
				state = st;
			break;
		}
		case Remove: {
			if ( state!=Added )
				state = Destroy;
			else if ( setCaPmt( CA_LIST_MANAGEMENT_UPDATE, CA_PMT_CMD_ID_NOT_SELECTED ) )
				state = st;
			break;
		}
		default: {
			state = st;
		}
	}
}

int CamService::getState()
{
	TQMutexLocker locker( &mutex );
	return state;
}

bool CamService::setCaPmt( int list_management, int cmd_id )
{
	if ( !parsedPmt )
		return false;

	caPmtSize = en50221_ca_format_pmt( parsedPmt, caPmt, sizeof(caPmt), 0, list_management, cmd_id );
	if ( caPmtSize<=0 )
		return false;

	return true;
}

void CamService::run()
{
	int i, pmtVersion=-1;
	int demux_fd=-1;

	fprintf( stderr, "CamService (%s): started\n", channel.name.ascii() );

	while ( isRunning ) {
loopLabel:
		demux_fd = createSectionFilter( TRANSPORT_PAT_PID, stag_mpeg_program_association );
		if ( demux_fd<0 ) {
			fprintf( stderr, "CamService (%s): [error] opening demux device failed\n", channel.name.ascii() );
			demux_fd = -1;
			usleep( 100000 );
			goto loopLabel;
		}

		pollfd poll_desc;
		poll_desc.fd = demux_fd;
		poll_desc.events = POLLIN | POLLPRI;
		while ( isRunning ) {
			int ret = poll( &poll_desc, 1, 1000 );
			if ( ret<0 ) {
				fprintf( stderr, "CamService (%s): [error] polling demux device failed\n", channel.name.ascii() );
				close( demux_fd );
				demux_fd = -1;
				usleep( 100000 );
				goto loopLabel;
			}
			if ( ret>0 ) {
				int processed_pat = processPat( demux_fd );
				if ( processed_pat>=0 ) {
					close( demux_fd );
					demux_fd = -1;
					demux_fd = createSectionFilter( processed_pat, stag_mpeg_program_map );
					if ( demux_fd<0 ) {
						fprintf( stderr, "CamService (%s): [error] opening demux device failed\n", channel.name.ascii() );
						demux_fd = -1;
						usleep( 100000 );
						goto loopLabel;
					}
					poll_desc.fd = demux_fd;
					break;
				}
				else
					usleep( 10000 );
			}
			else
				usleep( 10000 );
		}

		while ( isRunning ) {
			int ret = poll( &poll_desc, 1, 1000 );
			if ( ret<0 ) {
				fprintf( stderr, "CamService (%s): [error] polling demux device failed\n", channel.name.ascii() );
				close( demux_fd );
				demux_fd = -1;
				usleep( 100000 );
				goto loopLabel;
			}
			if ( ret>0 ) {
				//fprintf( stderr, "CamService (%s): parsing pmt\n", channel.name.ascii() );
				i = processPmt( demux_fd );
				if ( i==-1 )
					usleep( 10000 );
				else {
					if ( i!=pmtVersion || getState()<Ready ) {
						fprintf( stderr, "CamService (%s): new pmt received\n", channel.name.ascii() );
						setState( Ready );
						pmtVersion = i;
					}
					i = 200;
					while ( i-- && isRunning )
						usleep( 10000 );
				}
			}
			else
				usleep( 10000 );
		}
	}

	if ( demux_fd != -1 )
		close( demux_fd );
	fprintf( stderr, "CamService (%s): stopped\n", channel.name.ascii() );
}

#include "dvbcam.moc"
