#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <getopt.h>
#include <iconv.h>
#include <time.h>

#include "ts.h"
#include "psi.h"
#include "sdt.h"
#include "sdtt.h"
#include "eit.h"
#include "tot.h"
#include "dsmcc.h"
#include "ts_ctl.h"
#include "util.h"

typedef		struct	_ContentTYPE{
	char	*japanese ;
	char	*english ;
}CONTENT_TYPE;

#define		CAT_COUNT		16
static  CONTENT_TYPE	ContentCatList[CAT_COUNT] = {
	{ "ニュース・報道", "news" },
	{ "スポーツ", "sports" },
	{ "情報", "information" },
	{ "ドラマ", "drama" },
	{ "音楽", "music" },
	{ "バラエティ", "variety" },
	{ "映画", "cinema" },
	{ "アニメ・特撮", "anime" },
	{ "ドキュメンタリー・教養", "documentary" },
	{ "演劇", "stage" },
	{ "趣味・実用", "hobby" },
	{ "福祉", "welfare" },
	{ "予備", "etc" },
	{ "予備", "etc" },
	{ "予備", "etc" },
	{ "その他", "etc" }
};


SVT_CONTROL	*svttop = NULL;
DSM_CONTROL	dsmctl[1024];
#define		SECCOUNT	64
static unsigned char *base64 = (unsigned char *)"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static void base64_char(unsigned long bb, int srclen, unsigned char *dest, int j)
{
	int x, i, base;

	/* 最終位置の計算 */
	for ( i = srclen; i < 2; i++ ) 
		bb <<= 8;

	/* BASE64変換 */
	for ( base = 18, x = 0; x < srclen + 2; x++, base -= 6) {
		dest[j++] = base64[ (unsigned long)((bb>>base) & 0x3F) ];
	}

	/* 端数の判断 */
	for ( i = x; i < 4; i++ ) {
		dest[j++] = (unsigned char)'=';		/* 端数 */
	}
	
}

static void base64_encode(unsigned char *dest, const unsigned char *src, int len)
{
	unsigned char *p = src;
	unsigned long bb = (unsigned long)0;
	int i = 0, j = 0;

	while (len--)
	{
		bb <<= 8;
		bb |= (unsigned long)*p;

		/* 24bit単位に編集 */
		if (i == 2) {
			base64_char(bb, i, dest, j);

			j = j + 4;
			i = 0;
			bb = 0;
		} else
			i++;

		p++;
	}

	/* 24bitに満たない場合 */
	if (i) base64_char(bb, i - 1, dest, j);

}

void	xmlspecialchars(char *str)
{
	strrep(str, "&", "&amp;");
	strrep(str, "'", "&apos;");
	strrep(str, "\"", "&quot;");
	strrep(str, "<", "&lt;");
	strrep(str, ">", "&gt;");
}



void	GetSDT(FILE *infile, SVT_CONTROL *svttop, SECcache *secs, 
	int count, STATION **station, int * station_count, char *header, int is_logo)
{
	SECcache  *bsecs;
	int pmtpids[SECCOUNT];
	memset(pmtpids, 0, sizeof(pmtpids));
	int dsmccpids[SECCOUNT];
	memset(dsmccpids, 0, sizeof(dsmccpids));
	int i = 0 , downloadDataId = 0;

	while((bsecs = readTS(infile, secs, count)) != NULL) {
		/* SDT */
		if((bsecs->pid & 0xFF) == 0x11) {
			dumpSDT(bsecs->buf, svttop, station, station_count, header);
		}
		/* TOT */
		else if((bsecs->pid & 0xFF) == 0x14) {
			dumpTOT(bsecs->buf);
		}
		/* SDTT */
		//else if((bsecs->pid & 0xFF) == 0x23) {
		//	dumpSDTT(bsecs->buf, *station, *station_count);
		//}
		/* BIT */
		else if((bsecs->pid & 0xFF) == 0x24) {
			dumpBIT(bsecs->buf);
		}
		/* CDT */
		else if((bsecs->pid & 0xFF) == 0x29) {
			dumpCDT(bsecs->buf, *station, *station_count);
		}
		else if ( is_logo ) {
			/* PAT */
			if((bsecs->pid & 0xFF) == 0x00) {
				dumpPAT(bsecs->buf, secs, count, pmtpids);
			}
			/* PMT */
			for ( i = 1; i < SECCOUNT; i++ ) {
				if ( pmtpids[i] == 0 ) {
					break;
				}
				/* PMT specified by PAT */
				if ( bsecs->pid == pmtpids[i] ) {
					dumpPMT(bsecs->buf, secs, count, dsmccpids);
				}
			}
			/* DSM-CC */
			for ( i = 0; i < SECCOUNT; i++ ) {
				if ( dsmccpids[i] == 0 ) {
					break;
				}
				/* DSM-CC specified by PMT */
				if ( bsecs->pid == dsmccpids[i] ) {
					dumpDSMCC(bsecs->buf, &downloadDataId, &dsmctl);
				}
			}
		}
	}
}

void	GetEIT(FILE *infile, FILE *outfile, STATION *psta, SECcache *secs)
{
	SECcache  *bsecs;
	EIT_CONTROL	*eitcur ;
	EIT_CONTROL	*eitnext ;
	EIT_CONTROL	*eittop = NULL;
	time_t	l_time ;
	time_t	end_time ;
	struct	tm	tl ;
	struct	tm	*endtl ;
	char	cendtime[32];
	char	cstarttime[32];

	char	title[1024];
	char	subtitle[1024];
	char	desc[102400] = {0};
	char	Category[1024];
	char	VideoType[1024];
	char	AudioType[1024];

	memset(secs, 0,  sizeof(SECcache) * SECCOUNT);
	secs[0].pid = 0x12; /* EIT  */
	secs[1].pid = 0x26; /* EIT  */
	secs[2].pid = 0x27; /* EIT  */

	eittop = calloc(1, sizeof(EIT_CONTROL));
	eitcur = eittop ;
	fseek(infile, 0, SEEK_SET);
	while((bsecs = readTS(infile, secs, SECCOUNT)) != NULL) {
		/* EIT */
		if((bsecs->pid & 0xFF) == 0x12) {
			dumpEIT(bsecs->buf, psta->svId, psta->onId, psta->tsId, eittop);
		}else if((bsecs->pid & 0xFF) == 0x26) {
			dumpEIT(bsecs->buf, psta->svId, psta->onId, psta->tsId, eittop);
		}else if((bsecs->pid & 0xFF) == 0x27) {
			dumpEIT(bsecs->buf, psta->svId, psta->onId, psta->tsId, eittop);
		}
	}
	eitcur = eittop ;
	while(eitcur != NULL){
		if(!eitcur->servid){
			eitcur = eitcur->next ;
			continue ;
		}
		if(eitcur->content_type > CAT_COUNT){
			eitcur->content_type = CAT_COUNT -1 ;
		}
		memset(title, '\0', sizeof(title));
		strcpy(title, eitcur->title);
		xmlspecialchars(title);

		memset(subtitle, '\0', sizeof(subtitle));
		strcpy(subtitle, eitcur->subtitle);
		xmlspecialchars(subtitle);

		memset(desc, '\0', sizeof(desc));
		if ( eitcur->desc ) {
			strcpy(desc, eitcur->desc);
			xmlspecialchars(desc);
		}

		memset(Category, '\0', sizeof(Category));
		strcpy(Category, ContentCatList[eitcur->content_type].japanese);
		xmlspecialchars(Category);

		memset(VideoType, '\0', sizeof(VideoType));
		strcpy(VideoType, parseComponentDescType(eitcur->video_type));
		xmlspecialchars(VideoType);

		memset(AudioType, '\0', sizeof(AudioType));
		strcpy(AudioType, parseAudioComponentDescType(eitcur->audio_type));
		xmlspecialchars(AudioType);

		tl.tm_sec = eitcur->ss ;
		tl.tm_min = eitcur->hm ;
		tl.tm_hour = eitcur->hh ;
		tl.tm_mday = eitcur->dd ;
		tl.tm_mon = (eitcur->mm - 1);
		tl.tm_year = (eitcur->yy - 1900);
		tl.tm_wday = 0;
		tl.tm_isdst = 0;
		tl.tm_yday = 0;
		l_time = mktime(&tl);
		if((eitcur->ehh == 0) && (eitcur->emm == 0) && (eitcur->ess == 0)){
			(void)time(&l_time);
			end_time = l_time + (60 * 5);		// ５分後に設定
		endtl = localtime(&end_time);
		}else{
			end_time = l_time + eitcur->ehh * 3600 + eitcur->emm * 60 + eitcur->ess;
			endtl = localtime(&end_time);
		}
		memset(cendtime, '\0', sizeof(cendtime));
		memset(cstarttime, '\0', sizeof(cstarttime));
		strftime(cendtime, (sizeof(cendtime) - 1), "%Y%m%d%H%M%S", endtl);
		strftime(cstarttime, (sizeof(cstarttime) - 1), "%Y%m%d%H%M%S", &tl);

		fprintf(outfile, "  <programme start=\"%s +0900\" stop=\"%s +0900\" channel=\"%s\" event=\"%d\">\n",	
				cstarttime, cendtime, psta->ontv, eitcur->event_id);
		fprintf(outfile, "    <title lang=\"ja_JP\">%s</title>\n", title);
		fprintf(outfile, "    <desc lang=\"ja_JP\">%s</desc>\n", subtitle);
		fprintf(outfile, "    <longdesc lang=\"ja_JP\">%s</longdesc>\n", desc);
		fprintf(outfile, "    <category lang=\"ja_JP\">%s</category>\n", Category);
		fprintf(outfile, "    <video type=\"%d\">%s</video>\n", eitcur->video_type, VideoType);
		fprintf(outfile, "    <audio type=\"%d\" multi=\"%d\">%s</audio>\n", eitcur->audio_type, eitcur->multi_type, AudioType);
		//fprintf(outfile, "    <category lang=\"en\">%s</category>\n", ContentCatList[eitcur->content_type].english);
		fprintf(outfile, "  </programme>\n");
#if 0
		fprintf(outfile, "(%x:%x:%x)%s,%s,%s,%s,%s,%s\n",
					eitcur->servid, eitcur->table_id, eitcur->event_id,
					cstarttime, cendtime,
					title, subtitle,
					Category,
					ContentCatList[eitcur->content_type].english);

		fprintf(outfile, "(%x:%x)%04d/%02d/%02d,%02d:%02d:%02d,%02d:%02d:%02d,%s,%s,%s,%s\n",
					eitcur->table_id, eitcur->event_id,
					eitcur->yy, eitcur->mm, eitcur->dd,
					eitcur->hh, eitcur->hm, eitcur->ss,
					eitcur->ehh, eitcur->emm, eitcur->ess,
					eitcur->title, eitcur->subtitle,
					ContentCatList[eitcur->content_type].japanese,
					ContentCatList[eitcur->content_type].english);
#endif
		eitnext = eitcur->next ;
		free(eitcur->title);
		free(eitcur->subtitle);
		free(eitcur);
		eitcur = eitnext ;
	}
	free(eittop);
	eittop = NULL;
}

void checkSta(STATION **station,int *stalength){
	STATION *statmp;
	int chl[90];
	int chlt = 0;
	int stal = 0;
	STATION * statin = *station;
	statmp = malloc( sizeof(STATION) * 2 );
	for (int i = 0 ; i < *stalength ; i++){
		int noidinchl = 1;
		for (int j = 0 ; j < chlt ; j++){
			if ( chl[j] == statin[i].svId ) {
				noidinchl = 0;
			}
		}
		if ( noidinchl == 1 ) {
			statmp = realloc(statmp, (stal+1) * sizeof(STATION));
			statmp[stal] = statin[i];
			chl[chlt] = statin[i].svId;
			chlt++;
			stal++;
		}
	}
	*station = statmp;
	*stalength = stal;//ここいらが怪しい
	//memcpy(statin,statmp,chlt*sizeof(STATION));
	//free(statmp);
	return;
}

int main(int argc, char *argv[])
{

	FILE *infile = stdin;
	FILE *outfile = stdout;
	char	*arg_onTV ;
	int		staCount ;
	int   inclose = 0;
	int   outclose = 0;
	SVT_CONTROL	*svtcur ;
	SVT_CONTROL	*svtsave ;
	SECcache   secs[SECCOUNT];
	int		lp ;
	STATION	*pStas ;
	int		act = 0;
	int		i , j, k ;
	int		is_logo = 0;
	SDTTdata  sdtd;
	SDTTdataLoop *loop;
	SDTTdataService *service;

	memset(dsmctl, 0,  sizeof(dsmctl));

	if(argc == 5 && strcmp(argv[1], "/LOGO") == 0){
		argc--;
		argv[1] = argv[2];
		argv[2] = argv[3];
		argv[3] = argv[4];
		is_logo = 1;
	}
	if(argc == 4){
		arg_onTV = argv[1];
		if(strcmp(argv[2], "-")) {
			infile = fopen(argv[2], "r");
			if ( !infile) {
			  printf( "tsFile not found (Can't open file: %s)\n", argv[2] );
			  exit( -1 );
			}
			inclose = 1;
		}
		else {
			infile = stdin;
		}
		if(strcmp(argv[3], "-")) {
			outfile = fopen(argv[3], "w+");
			if ( !outfile) {
			  printf( "xmlFile not found (Can't open file: %s)\n", argv[3] );
			  exit( -1 );
			}
			outclose = 1;
		}
		else {
			outfile = stdout;
		}
	}else{
		fprintf(stdout, "Usage : %s (/LOGO) {/BS|/CS|<id>} <tsFile> <outfile>\n", argv[0]);
		fprintf(stdout, "\n");
		fprintf(stdout, "/LOGO    ロゴ取得モード。独立して指定し、番組表の出力を行ないません。\n");
		fprintf(stdout, "         必要なTSの長さ 地上波は10分 BS/CSは20分です。\n");
		fprintf(stdout, "id       チャンネル識別子。地上波の物理チャンネルを与えます。\n");
		fprintf(stdout, "/BS      BSモード。一つのTSからBS全局のデータを読み込みます。\n");
		fprintf(stdout, "/CS      CSモード。一つのTSから複数局のデータを読み込みます。\n");
		fprintf(stdout, "/TIME    時刻合わせモード。TSからTOT(Time Offset Table)を読み込みます。\n");
		fprintf(stdout, "         recpt1 <任意> 10(秒以上) - | epgdump /TIME - <任意>の形で使用してください。\n");
		fprintf(stdout, "         TOTは5秒に1回しか来ないため、recpt1に与える時間をある程度長くしてください。\n");
/*
		fprintf(stdout, "  ontvcode   Channel identifier (ex. ****.ontvjapan.com)\n");
		fprintf(stdout, "  /BS        BS mode\n");
		fprintf(stdout, "               This mode reads the data of all BS TV stations\n");
		fprintf(stdout, "               from one TS data.\n");
		fprintf(stdout, "  /CS        CS mode\n");
		fprintf(stdout, "               This mode reads the data of two or more CS TV stations\n");
		fprintf(stdout, "               from one TS data.\n");
*/
		return 0;
	}

	pStas = NULL;
	staCount = 0;
	svttop = calloc(1, sizeof(SVT_CONTROL));
	act = 0 ;

	/* 興味のあるpidを指定 */
	if ( is_logo ) {
		memset(secs, 0,  sizeof(SECcache) * SECCOUNT);
		secs[0].pid = 0x00; /* PAT  */
		secs[1].pid = 0x11; /* SDT  */
		secs[2].pid = 0x29; /* CDT  */
	}
	else {
		memset(secs, 0,  sizeof(SECcache) * SECCOUNT);
		secs[0].pid = 0x00; /* PAT  */
		secs[1].pid = 0x11; /* SDT  */
		secs[2].pid = 0x12; /* EIT  */
		secs[3].pid = 0x23; /* SDTT */
		secs[4].pid = 0x26; /* EIT  */
		secs[5].pid = 0x27; /* EIT  */
		secs[6].pid = 0x29; /* CDT  */
	}

	if(strcmp(arg_onTV, "/TIME") == 0){
		printf("TSに載っている時刻データは2秒ほど早めてあるのかもしれません。\n");
		memset(secs, 0,  sizeof(SECcache) * SECCOUNT);
		secs[0].pid = 0x14; /* TOT  */

		GetSDT(infile, NULL, secs, SECCOUNT,NULL, NULL,NULL, 0);

		goto cleanup;
	}else if(strcmp(arg_onTV, "/BS") == 0){
		char *head = "BS";
		GetSDT(infile, svttop, secs, SECCOUNT, &pStas, &staCount, head, is_logo);
	}else if(strcmp(arg_onTV, "/CS") == 0){
		char *head = "CS";
		GetSDT(infile, svttop, secs, SECCOUNT, &pStas, &staCount, head, is_logo);
	}else if(strcmp(arg_onTV, "/TEST") == 0){
		memset(secs, 0,  sizeof(SECcache) * SECCOUNT);
		secs[0].pid = 0x24; /* BIT  */

		char *head = "TEST";
		GetSDT(infile, svttop, secs, SECCOUNT, &pStas, &staCount, head, 0);
		//if (sta_count) 
		//	printf("Station count: %d\n1st ontv=%s,name=%s\n",staCount, pStas[0].ontv, pStas[0].name);
	}else{
		GetSDT(infile, svttop, secs, SECCOUNT, &pStas, &staCount, arg_onTV, 0);

		// 地上波のマルチチャンネル対応のためコメントアウト
		/*
		act = 1 ;
		svttop = calloc(1, sizeof(SVT_CONTROL));
		GetSDT(infile, svttop, secs, SECCOUNT);
		svtcur = svttop->next ;	//先頭
		if(svtcur == NULL){
			free(svttop);
			return 1;
		}

		pStas = calloc(1, sizeof(STATION));
		pStas->tsId = svtcur->transport_stream_id ;
		pStas->onId = svtcur->original_network_id ;
		pStas->svId = svtcur->event_id ;
		pStas->ontv = arg_onTV ;
		pStas->name = svtcur->servicename ;
		staCount = 1;
		*/
	}
	checkSta(&pStas, &staCount);

	fprintf(outfile, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
	fprintf(outfile, "<!DOCTYPE tv SYSTEM \"xmltv.dtd\">\n\n");
	fprintf(outfile, "<tv generator-info-name=\"tsEPG2xml\" generator-info-url=\"http://localhost/\">\n");

	char	ServiceName[1024];
	char	Logo[8192];

	if ( is_logo ) {
		memset(Logo, '\0', sizeof(Logo));
		for(lp = 0 ; lp < staCount ; lp++){
			for ( i = 0 ; i < 6 ; i++) {
				if (pStas[lp].logo_array[i].logo) {
					base64_encode(Logo, pStas[lp].logo_array[i].logo, pStas[lp].logo_array[i].logo_size);
					xmlspecialchars(Logo);
					fprintf(outfile, "    <logo ts=\"%d\" on=\"%d\" sv=\"%d\" type=\"%d\">%s</logo>\n", 
						pStas[lp].tsId, 
						pStas[lp].onId, 
						pStas[lp].svId, 
						i, 
						Logo);
				}
			}
		}

		for ( i = 0; i < 1024; i++) {
			if ( dsmctl[i].isUsed == 0 ) break;
			parseSDTTdata(dsmctl[i].blockData, &sdtd);

			for (j = 0; j < sdtd.number_of_loop; j++) {
				loop = sdtd.loop + sizeof(SDTTdataLoop) * j;

				for ( k = 0; k < loop->number_of_services; k++) {
					service = loop->services + sizeof(SDTTdataService) * k;

					/*
					for(lp = 0 ; lp < staCount ; lp++){
						if ( 
							pStas[lp].tsId == service->transport_stream_id && 
							pStas[lp].onId == service->original_network_id && 
							pStas[lp].svId == service->service_id
						) {
							clt2png(loop->data, 
								&pStas[lp].logo_array[sdtd.logo_type].logo, 
								&pStas[lp].logo_array[sdtd.logo_type].logo_size);
						}
					}
					*/

					#if 0
					printf( "SDTTdataLoop (%d:%d) %d:%d[%d:%d:%d]%d\n", 
						i, j, 
						sdtd.logo_type, 
						loop->logo_id, 
						service->transport_stream_id, 
						service->original_network_id, 
						service->service_id, 
						loop->data_size
					);
					#endif

				
					void* logo = NULL;
					int logo_size = 0;

					clt2png(loop->data, &logo, &logo_size);
					memset(Logo, '\0', sizeof(Logo));
					base64_encode(Logo, logo, logo_size);
					xmlspecialchars(Logo);

					fprintf(outfile, "  <logo ts=\"%d\" on=\"%d\" sv=\"%d\" type=\"%d\" dlid=\"%d\">%s</logo>\n", 
						service->transport_stream_id, 
						service->original_network_id, 
						service->service_id, 
						sdtd.logo_type, 
						loop->logo_id, 
						Logo);
				}

			}
		}
	}
	for(lp = 0 ; lp < staCount ; lp++){
		memset(ServiceName, '\0', sizeof(ServiceName));
		strcpy(ServiceName, pStas[lp].name);
		xmlspecialchars(ServiceName);

		fprintf(outfile, "  <channel id=\"%s\">\n", pStas[lp].ontv);
		fprintf(outfile, "    <display-name lang=\"ja_JP\">%s</display-name>\n", ServiceName);
		fprintf(outfile, "    <id ts=\"%d\" on=\"%d\" sv=\"%d\"/>\n", pStas[lp].tsId, pStas[lp].onId, pStas[lp].svId);

		fprintf(outfile, "  </channel>\n");
	}
	if ( is_logo ) {
		fprintf(outfile, "</tv>\n");
		goto cleanup;
	}
	for(lp = 0 ; lp < staCount ; lp++){
		GetEIT(infile, outfile, &pStas[lp], secs);
	}
	fprintf(outfile, "</tv>\n");
cleanup: 
	if(inclose) {
		fclose(infile);
	}
	if(outclose) {
		fclose(outfile);
	}
	if(act){
		free(pStas);
		svtcur = svttop ;	//先頭
		while(svtcur != NULL){
			svtsave = svtcur->next ;
			free(svtcur);
			svtcur = svtsave ;
		}
	}

	return 0;
}

