/**
 * This listens to the DVB stream for the PAT/PMT and time,
 * which is unfortunately needed as input for the CA.
 *
 * You call either ca_start_in_new_thread() or
 * (if you create the thread yourself) ca_start() and later
 * ca_stop_thread() / ca_stop().
 *
 * Uses open()/close(), poll() and ioctl() system calls.
 *
 * Based on zap_dvb.c and zap_ca.c
 *
 * Copyright (C) 2007 Ben Bucksch
 * 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 Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 */

#include "poll.h"
#include "zap_ca.h"
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <fcntl.h>
#include <linux/dvb/frontend.h>
#include <linux/dvb/dmx.h>
#include <libucsi/mpeg/section.h>
#include <libucsi/dvb/section.h>
#include <iostream>
#include <string>


static void process_pat(int pat_fd, const char* demuxDeviceFilename, int channel_service_id, int *pmt_fd, struct pollfd *pollfd);
static void process_tdt(int tdt_fd);
static void process_pmt(int pmt_fd, int channel_service_id);
static int create_filter(const char* demuxDeviceFilename, uint16_t pid, uint8_t table_id);

// TODO move to local vars in ca_start and pass as pointers to process_*
static int pat_version = -1;
static int ca_pmt_version = -1;

/////////////////////////////////////////////////////////////
// ca_start(), incl. setup and poll()
/////////////////////////////////////////////////////////////

void ca_stop(bool* stopSignal)
{
    std::cout << "ca_stop()" << std::endl;	
	(*stopSignal) = true;
}

#define ERROR_IN_START(test, message) \
{ \
  if (test) \
  { \
  	if (pat_fd > 0) close(pat_fd); \
  	if (pmt_fd > 0) close(pmt_fd); \
  	if (tdt_fd > 0) close(tdt_fd); \
  	throw message; \
  } \
}

/**
 * Sets up the CA for the tuned transponder
 * Call XXX before/after tuning
 *
 * You must call this in a thread, because it will block,
 * in fact it will loop infinitely until you call ca_stop().
 *
 * @param adapterNo   E.g. 0 for "/dev/dvb/adapter0/". Must be between 0 and 9.
 * @param channel_service_id   ID for channel
 */
void ca_start(int adapterNo, int channelServiceID, bool* stopSignal) throw(const char*)
{
    std::string adapter = "/dev/dvb/adapter0/";
    adapter[adapter.size() - 2] = '0' + adapterNo;
    std::string demuxDeviceFilenameStr = adapter + "demux0";
    const char* demuxDeviceFilename = demuxDeviceFilenameStr.c_str();
    std::cout << "Using adapter " << adapterNo << " " << adapter << " with " << demuxDeviceFilename
              << " . Service ID " << channelServiceID << std::endl;

	struct pollfd pollfds[3];
	int pat_fd = -1;
	int pmt_fd = -1;
	int tdt_fd = -1;

	// Create PAT filter
	pat_fd = create_filter(demuxDeviceFilename, TRANSPORT_PAT_PID, 0x00); // stag_mpeg_program_association
	ERROR_IN_START(pat_fd < 0, "Failed to create PAT section filter");
	pollfds[0].fd = pat_fd;
	pollfds[0].events = POLLIN|POLLPRI|POLLERR;

	// Create TDT filter
	tdt_fd = create_filter(demuxDeviceFilename, TRANSPORT_TDT_PID, 0x70); // stag_dvb_time_date
	ERROR_IN_START(tdt_fd < 0, "Failed to create TDT section filter");
	pollfds[1].fd = tdt_fd;
	pollfds[1].events = POLLIN|POLLPRI|POLLERR;

	// Zero PMT filter for now, will be filled in later by process_pat()
	pollfds[2].fd = 0;
	pollfds[2].events = 0;

	struct zap_ca_params zap_ca_params1;
	zap_ca_params1.adapter_id = adapterNo;
	zap_ca_params1.caslot_num = 0; // TODO support several CAMs per DVB card
	zap_ca_params1.moveca = 0;
	zap_ca_start(&zap_ca_params1); // TODO creates yet another pthread :-(
    std::cout << "zap_ca_start() done" << std::endl;

	// Infinite loop until ca_stop is called
	// to get PMT/PAT for CA

	while(!(*stopSignal))
	{
		// is there SI data?
		int count = poll(pollfds, 3, 100); // system call to efficiently wait for new data 
		ERROR_IN_START(count < 0, "Poll error");
		if (count == 0)
			continue;

		if (pollfds[0].revents & (POLLIN|POLLPRI)) // PAT
			process_pat(pat_fd, demuxDeviceFilename, channelServiceID, &pmt_fd, &pollfds[2]);
		if (pollfds[1].revents & (POLLIN|POLLPRI)) // TDT
			process_tdt(tdt_fd);
		if (pollfds[2].revents & (POLLIN|POLLPRI)) // PMT 
			process_pmt(pmt_fd, channelServiceID);
	}

	close(pat_fd);
	close(pmt_fd);
	close(tdt_fd);

    std::cout << "zap_ca_stop()..." << std::endl;	
    zap_ca_stop();
    std::cout << "zap_ca_stop() returned" << std::endl;	
}

static int create_filter(const char* demuxDeviceFilename, uint16_t pid, uint8_t table_id)
{
	int demux_fd = open(demuxDeviceFilename, O_RDWR);
	if (demux_fd < 0)
	{
		return -1;
	}

	struct dmx_sct_filter_params sctfilter;
	memset(&sctfilter, 0, sizeof(sctfilter));
	sctfilter.pid = pid;
	sctfilter.filter.filter[0] = table_id;
	sctfilter.filter.mask[0] = 0xFF;
	sctfilter.flags = DMX_IMMEDIATE_START | DMX_CHECK_CRC;

	int rv = ioctl(demux_fd, DMX_SET_FILTER, &sctfilter);
	if (rv < 0)
	{
		close(demux_fd);
		return -1;
	}

	return demux_fd;
}


/////////////////////////////////////////////////////////////
// Process
/////////////////////////////////////////////////////////////

static void process_pat(int pat_fd, const char* demuxDeviceFilename, int channel_service_id, int *pmt_fd, struct pollfd *pollfd)
{
	int size;
	uint8_t sibuf[4096];

	// read the section
	if ((size = read(pat_fd, sibuf, sizeof(sibuf))) < 0) {
		return;
	}

	// parse section
	struct section *section = section_codec(sibuf, size);
	if (section == NULL) {
		return;
	}

	// parse section_ext
	struct section_ext *section_ext = section_ext_decode(section, 0);
	if (section_ext == NULL) {
		return;
	}
	if (pat_version == section_ext->version_number) {
		return;
	}

	// parse PAT
	struct mpeg_pat_section *pat = mpeg_pat_section_codec(section_ext);
	if (pat == NULL) {
		return;
	}

	// try and find the requested program
	struct mpeg_pat_program *cur_program;
	mpeg_pat_section_programs_for_each(pat, cur_program) {
		if (cur_program->program_number == channel_service_id) {
			// close old PMT fd
			if (*pmt_fd != -1)
				close(*pmt_fd);

			// create PMT filter
			if ((*pmt_fd = create_filter(demuxDeviceFilename,
							     cur_program->pid, stag_mpeg_program_map)) < 0) {
				return;
			}
			pollfd->fd = *pmt_fd;
			pollfd->events = POLLIN|POLLPRI|POLLERR;

			// we have a new PMT pid
			ca_pmt_version = -1;
			break;
		}
	}

	// remember the PAT version
	pat_version = section_ext->version_number;
}

static void process_tdt(int tdt_fd)
{
	int size;
	uint8_t sibuf[4096];

	// read the section
	if ((size = read(tdt_fd, sibuf, sizeof(sibuf))) < 0) {
		return;
	}

	// parse section
	struct section *section = section_codec(sibuf, size);
	if (section == NULL) {
		return;
	}

	// parse TDT
	struct dvb_tdt_section *tdt = dvb_tdt_section_codec(section);
	if (tdt == NULL) {
		return;
	}

	// done
	zap_ca_new_dvbtime(dvbdate_to_unixtime(tdt->utc_time));
}

static void process_pmt(int pmt_fd, int channel_service_id)
{
	int size;
	uint8_t sibuf[4096];

	// read the section
	if ((size = read(pmt_fd, sibuf, sizeof(sibuf))) < 0) {
		return;
	}

	// parse section
	struct section *section = section_codec(sibuf, size);
	if (section == NULL) {
		return;
	}

	// parse section_ext
	struct section_ext *section_ext = section_ext_decode(section, 0);
	if (section_ext == NULL) {
		return;
	}
	if ((section_ext->table_id_ext != channel_service_id) ||
	    (section_ext->version_number == ca_pmt_version)) {
		return;
	}

	// parse PMT
	struct mpeg_pmt_section *pmt = mpeg_pmt_section_codec(section_ext);
	if (pmt == NULL) {
		return;
	}

	//std::cout << "Send new PMT to zap_ca" << std::endl;

	// do ca handling
	if (zap_ca_new_pmt(pmt) == 1)
		ca_pmt_version = pmt->head.version_number;
}
