#include "ccextractor.h"
using namespace std;

const byte cCCExtractor::startUserDataHeader[]	= {0x00, 0x00, 0x01, 0xB2};
const byte cCCExtractor::endUserDataHeader[]	= {0x00, 0x00, 0x00, 0x01};

const byte cCCExtractor::dishDVBHeader[]		= {0x05, 0x02};
const byte cCCExtractor::r4000DVBHeader[]	= {0xBB, 0x02};
const byte cCCExtractor::r5000DVBHeader[]	= {0x99, 0x02};
const byte cCCExtractor::dvdHeader[]		= {0x43, 0x43};
  
cCCExtractor::cCCExtractor(void){
	qPos = 0;
	
	ccCnt = 0;
	rawBuffer1 = NULL;
	rawBuffer2 = NULL;

	nExtractField = 1; // field 1 only
	for(int i=0; i<CC_QUEUE_SIZE; i++){
		queue[i].data = NULL;
	}
	
	InitParser();
	Reset();
	
#ifdef CC_DEBUG	
	logFile = fopen("raw_cc.bin", "wb");
	logCCFile = fopen("cc.bin", "wb");
	sccFile = fopen("scc.txt", "wt");
		
	byte head[] = {0xFF, 0xFF, 0xFF, 0xFF};
	fwrite(head, sizeof(byte), 4, logFile);
#endif	
}

cCCExtractor::~cCCExtractor(void){
	Reset();	
#ifdef CC_DEBUG
	fclose(logFile);
	fclose(sccFile);
	fclose(logCCFile);
#endif	
}

bool cCCExtractor::Init(int bufferSize){
	nChannel = 0; //CC1
	//Extraction
	nBufferSize = bufferSize;
	
	rawBuffer1 = new byte[bufferSize];
	memset (rawBuffer1, 0, sizeof (rawBuffer1));

	rawBuffer2 = new byte[bufferSize];
	memset (rawBuffer2, 0, sizeof (rawBuffer2));

	nRaw1CCSize = 0;
	nRaw2CCSize = 0;
	framePos = 0;
	
	//CC Raw Filtering
	lineLength = 0;
	
	return true;
}

bool cCCExtractor::Reset(void){
	//cleaning the CC queue
	for(int i=0; i<qPos; i++)
		delete [] queue[i].data;
	nBufferSize = 0;

	nRaw1CCSize = 0;
	if(rawBuffer1!=NULL){
		delete [] rawBuffer1;
		rawBuffer1 = NULL;
	}

	nRaw2CCSize = 0;
	if(rawBuffer2!=NULL){
		delete [] rawBuffer2;
		rawBuffer2 = NULL;
	}

	header[0]=0xFF;
	header[1]=0xFF;
	header[2]=0xFF;
	header[3]=0xFF;

	bInUserData = false;

	guideBuffer[0]=startUserDataHeader[0];
	guideBuffer[1]=startUserDataHeader[1];
	guideBuffer[2]=startUserDataHeader[2];
	guideBuffer[3]=startUserDataHeader[3];
	
	nUDPos = 0;

//raw data stuff
 rawPos = 0;
 bEOL = false;
 bInline = false;
 bSkipHeader = false; //not yet used
 bCR = false;
 nColumns = 0;
 
 for(int i=0; i<ccCnt; i++)
	if(ccContent[i].data!=NULL){
 		delete [] ccContent[i].data;
		ccContent[i].data = NULL;
	}
 
 ccCnt = 0;
 

	return true;
}

bool cCCExtractor::EqualBytes(byte *s1, const byte *s2, int len){
	for(int i=0; i<len; i++)
		if(s1[i]!=s2[i])
			return false;
			
	return true;
}

void cCCExtractor::InitParser(){
	parserPos = 0;	
}

//returns true if it a parsed a complete user data block
bool cCCExtractor::ParseBuffer(byte *buffer, int len){	
	dprint(4, "Starting from %d...", parserPos);
	nRaw1CCSize=0;
	nRaw2CCSize=0;
	
	if(!bInUserData)
	while(!EqualBytes(header, startUserDataHeader, 4)){
		if(parserPos>=len)
			return false;
		
		header[0] = header[1];
		header[1] = header[2];
		header[2] = header[3];
		header[3] = buffer[parserPos++];
	}

	bInUserData = true;
  bool bRecord = true;
	while(!EqualBytes(header, endUserDataHeader, 4)){
		if(parserPos>=len)
			return false;
		
		header[0] = header[1];
		header[1] = header[2];
		header[2] = header[3];
		header[3] = buffer[parserPos++];
		
		if(bRecord && nUDPos < CC_BUFFER_SIZE){			
			guideBuffer[nUDPos++] = header[3];
			if(nUDPos==2)
				//stop recording if dont have the right CC header(s)
				//currently supporting only Dish DVB format
				if(guideBuffer[0]!=0x05 || guideBuffer[1]!=0x02){
					bRecord = false;
					nUDPos = 0;
				}		
		}		
	}
	
	if(bRecord){	
		bInUserData = false;
					
		header[0] = guideBuffer[0];
		header[1] = guideBuffer[1];
	
		bool hasMore = true;
		
		if(EqualBytes(header, dishDVBHeader, 2)){
			hasMore = ExtractCC4Dish(guideBuffer+2, nUDPos-6);
		}
		else
			if(EqualBytes(header, r4000DVBHeader, 2)){
				hasMore = ExtractCC4R4K(buffer, len);
			}
			else
				if(EqualBytes(header, r5000DVBHeader, 2)){
					hasMore = ExtractCC4R5K(buffer, len);
				}
				else
					if(EqualBytes(header, dvdHeader, 2)){
						hasMore = ExtractCC4DVD(buffer, len);
					}		
					else{
						dprint(4, "Unknown CC type!");
					  dprint(4, "Found: {%02x, %02x}.\n", header[0], header[1]);
					}
		
		if(!hasMore)
			return false;
			
	} //end record
	
	bInUserData = false;	
	
#ifdef CC_DEBUG	
	byte hhead[] = {0x00, 0x00, 0x01, 0xb2};
	fwrite(hhead, sizeof(byte), 4, logCCFile);
	fwrite(guideBuffer, sizeof(byte), nUDPos, logCCFile);

	if(nRaw1CCSize!=0 && !bRecord){
		dprint(4, "Strange!\n");
		exit(1);
	}
	
// save in file

	fwrite(rawBuffer1, sizeof(byte), nRaw1CCSize, logFile);

	for(int k=0; k<nRaw1CCSize; k+=2)
		if(SplitCC(rawBuffer1+k))
			if(lineLength>0){
				for(int p=0; p<lineLength; p+=2)
					fprintf(sccFile, "%02X%02X ", newLine[p], (byte)newLine[p+1]);
				fprintf(sccFile, "\n");
			}
			
	dprint(4, "CC Stream:");
	for(int i=0; i< (nExtractField==1) ? nRaw1CCSize : nRaw2CCSize; i++)
		dprint(4, "%02X ", ((nExtractField==1) ? rawBuffer1 : rawBuffer2)+i);
#endif	

//**************************************************************************************************

	for(int k=0; k<nRaw1CCSize; k+=2)
		if(SplitCC(rawBuffer1+k))
			if(lineLength>0)
				GetCCData(newLine, lineLength);//sending CC to the queue;
					
//**************************************************************************************************						

	nRaw1CCSize=0;
	nUDPos = 0;
	
	return true;
}

bool cCCExtractor::ExtractCC4Dish(byte *buffer, int len){
	byte hi, type, repeater;
	if(len<=0)
		return false;

	type = buffer[5];
#ifdef CC_DEBUG	
	cout << "Type: " << (int)type << endl;			
#endif	
	switch(type){
		case 0x02:
			PrintData1(buffer[7]);
			PrintData1(buffer[8]);
			
			PrintData2(0x80);
			PrintData2(0x80);

			repeater = buffer[9];

			if((repeater==0x04) && ((buffer[7] & 0x7F)<0x20)){
				PrintData1(buffer[7]);
				PrintData1(buffer[8]);
				
				PrintData2(0x80);
				PrintData2(0x80);
			}
			
			break;
		case 0x04:
			//skip 1 byte					
			//capture next 4 bytes
			//writing
			PrintData1(buffer[7]);
			PrintData1(buffer[8]);

			PrintData2(0x80);
			PrintData2(0x80);

			//writing
			PrintData1(buffer[9]);
			PrintData1(buffer[10]);
				
			PrintData2(0x80);
			PrintData2(0x80);
			break;
		case 0x05:
			//dump data from previous frames
			if(framePos>0){
				dprint(4, "Dumping P buffer:");

				for(int i=0; i<framePos; i++){
					dprint(4, "%02X ", frameBuffer[i]);
					PrintData1(frameBuffer[i]);
				}				
				dprint(4, "\n");
			}
				
			framePos = 0;
			
			//skip 6 next bytes
			
			//read type (1 byte)
			type = buffer[12];
				
			//skip one byte
			//read 2 bytes and
			//add them to the frame buffer

			frameBuffer[framePos++] = buffer[14];
			frameBuffer[framePos++] = buffer[15];
			if(buffer[14]==0x8b && buffer[15]==0x45)
					hi=0;
			//if type==0x02
			if(type==0x02){
				repeater = buffer[16];
					
				//if repeater==0x04
				if((repeater==0x04) && ((buffer[14] & 0x7F) < 0x20)){
					//add them to the frame buffer
					frameBuffer[framePos++] = buffer[14];
					frameBuffer[framePos++] = buffer[15];
				}
			}
			else{
				//else read two more bytes	and
				//add them to the frame buffer			
				frameBuffer[framePos++] = buffer[16];
				frameBuffer[framePos++] = buffer[17];				
			}
#ifdef CC_DEBUG			
			if(framePos>0){
				dprint("4, New P buffer:");
				for(int i=0; i<framePos; i++){
					dprint(4, "%02X ", frameBuffer[i]);
					//PrintData1(frameBuffer[i]);
				}
				dprint(4, "\n");
			}
#endif			
			break;
	}
	
	return true;
}

bool cCCExtractor::ExtractCC4R4K(byte *buffer, int len){
#ifdef CC_DEBUG	
	dprint(4,"Extracting CC for 4K: NOT SUPPORTED");
#endif	
	return true;
}

bool cCCExtractor::ExtractCC4R5K(byte *buffer, int len){
#ifdef CC_DEBUG	
	dprint(4,"Extracting CC for 5K: NOT SUPPORTED");
#endif	
	return true;
}

bool cCCExtractor::ExtractCC4DVD(byte *buffer, int len){
#ifdef CC_DEBUG	
	dprint(4,"Extraction of CC for DVD: NOT SUPPORTED");
#endif	
	return false;
}

bool cCCExtractor::PrintData1(byte c1){
	if(nRaw1CCSize>nBufferSize){
		dprint(4,"CC: No more memory for raw data!\n");
		return false;
	}
	rawBuffer1[nRaw1CCSize++] = c1;
	return true;
}

bool cCCExtractor::PrintData2(byte c1){
		rawBuffer2[nRaw2CCSize++] = c1;
	return true;
}

//returns true if there is something to process 
bool cCCExtractor::SplitCC(byte* input){
	byte hi = input[0];
	byte lo = input[1];
	byte even[2];
	bool bReady = false;
	if((hi==0x94) && (lo==0x2F)){
		hi+=2;
		hi-=2;
	}

	if(rawPos >= RAWBUFFER){
		dprint(4,"CC: Buffer Overflow: %d bytes", rawPos);
		lineLength = RAWBUFFER;
		for(int i=0; i<lineLength; i++)
			newLine[i] = ccRaw[i];
			
		rawPos = 0;
		bReady = true;
	}
		
	if(bInline)
		nColumns++;
			
	even[0] = hi & 0x7F;
	even[1] = lo & 0x7F;
	
	if(even[0]==0x00 && even[1]==0x00 && bEOL){
	 bEOL = false;
	 bInline = false;	 
	}
	/*
	# check for carriage return code (142d, 1c2d, 152d or 1c2d)
    if (($evenword ne "142d") and ($evenword ne "1c2d") and
       ($evenword ne "152d") and ($evenword ne "1d2d") and ($crflag)) {
      $endofline = 0;
      $inline = 0;
    }
  */
  if(
  	even[0]!=0x14 && even[0]!=0x1C && even[0]!=0x15 && even[0]!=0x1D && even[1]!=0x2D && bCR
  	){
  	bEOL = false;
  	bInline = false;
  }
  
  if(even[0]==0 && even[1]==0 && !bInline)
		return false;  
  
  if(hi==0x0f){
	bEOL = false;
	bInline = false;
  }
   /*
    # start new line if switching modes (and haven't started a new line already):
    #  High byte = 14, 1c, 15 or 1d for Channel 1 - 4
    #  Pop-on: low byte 20
    #  Roll-up: low bytes 25, 26, 27
    #  Paint-on: low byte 29
    #  Text: low byte 2a, 2b
    #  XDS: high byte 01 - 0e, any low byte
  */    
  if(
  		(
  			(
  				(even[0]==0x14 || even[0]==0x15 || even[0]==0x1C || even[0]==0x1D) &&
  				(even[1]==0x20 || even[1]==0x25 || even[1]==0x26 || even[1]==0x27 || even[1]==0x29 || even[1]==0x2A || even[1]==0x2B)
  			) ||
  			(even[0]>0x00 && even[0]<0x0F)
  		) && (nColumns > 2)
  	)
  	bInline = false;
  
  if(even[0]!=0	 && even[1]!=0 && !bInline){
  	bInline = true;
  	nColumns = 1;
  	
	if(rawPos>0){
		lineLength = rawPos;
		for(int i=0; i<lineLength; i++){
			newLine[i] = ccRaw[i];
		}
		rawPos = 0;
		bReady = true;
	}
	
  }
  
  bCR = ((even[0]==0x14 || even[0]==0x15 || even[0]==0x1C || even[0]==0x1D ) && even[1]==0x2D ) ? true : false;
  bEOL = ((even[0]==0x14 || even[0]==0x15 || even[0]==0x1C || even[0]==0x1D ) && (even[1]==0x2C || even[1]==0x2F) ) ? true : bEOL;
  
  ccRaw[rawPos++] = hi;
  ccRaw[rawPos++] = lo;
  

  return bReady;
}

bool cCCExtractor::GetCCData(byte *line, int len){
  	byte channel_types[] = {0x14, 0x1C, 0x15, 0x1D};
	byte hi,lo;
	byte buffer[CC_BUFFER_SIZE * 5];
	
	//!!!!!
	ccCnt = 0;
	
	int line_pos = 0;

	hi = line[0] & 0x7F;

	if(hi>=0x01 && hi<=0x0F && nChannel!=2) //if expected channel is 3 but current channel is not 3
		return false; // then exit

	if(channel_types[nChannel]!=hi)
		return false; //not matching the expected channel	
	
	
	for(int i=0; i<len; i+=2){
		hi = line[i] & 0x7F;
		lo = line[i+1] & 0x7F;

		if(hi>=0x20){//is it text?
			if(i>0)
				if(!bWasText){
				//save the commands
					QueueData(0x01, buffer, line_pos);
					line_pos = 0;
					ccCnt++;
				}
				
			buffer[line_pos++] = GetByte(hi, false);
			buffer[line_pos++] = GetByte(lo, false);
			//printf("%c%c", hi, lo);
			
			bWasText = true;
		}
		else{
			if(i>0)
				if(bWasText){
					//save the text
					QueueData(0x00, buffer, line_pos);
					line_pos = 0;
					ccCnt++;
				}
			
			buffer[line_pos++] = line[i];
			buffer[line_pos++] = line[i+1];
			
			bWasText = false;
		}
	}
	
	QueueData(bWasText ? 0x00 : 0x01, buffer, line_pos);
		
	ccCnt++;
  
	return true;
}

bool cCCExtractor::QueueData(byte type, byte* data, int len){
	cMutexLock lock(&m_mutex);
#ifdef CC_DEBUG	
	if(type==0x00){
		dprint(4, "\Text found(%d bytes): ", len);
		for(int i=0; i<len; i++)
			dprint(4, "%c", data[i]);
		fflush(stdout);
	}
	else{
		dprint(4, "\nCommand found(%d bytes): ", len);		
		for(int i=0; i<len; i++)
			dprint(4, "%2X%s", data[i], (i%2==0) ? "" : " ");
		dprint(4, "\n");
	}
#endif
	
	if(qPos>=CC_QUEUE_SIZE){
		dprint(2, "\nCC: Warning: The CC queue is overflown. Apparently OSD is not active.\nCC: Flushing the queue.\n");
		qPos=0;

		return false;
	}
	
//	dprint(4, "Queue cursor: %d\n", qPos);
	queue[qPos].type = type;
	queue[qPos].length = len;
	queue[qPos].data = new byte[len];
	for(int i=0; i<len; i++)
		queue[qPos].data[i] = data[i];
	qPos++;
	
	return true;
}

void cCCExtractor::SetChannel(int chn){
	if(chn>=0 && chn <4)
		nChannel = chn;
}

byte cCCExtractor::GetByte(byte in, bool itv){
	char itvc[] = "����|";
	//char itv[] = "aeiouc*Nn|";
	byte codes[] = {0x2A, 0x5C, 0x5E, 0x5F, 0x60, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F};
	/*if(in==0x00)
		return '_';*/
	
	if(!itv)
		for(int i=0; i<10; i++)
			if(codes[i]==in)
				return (byte) itvc[i];

	return in;	
}

CC_Data *cCCExtractor::PopCC(){
	cMutexLock lock(&m_mutex);
	CC_Data *r = NULL;	
	dprint(4, "Queue cursor: %d\n", qPos);fflush(stdout);
	if(qPos>0){
		dprint(4, "Creating the response item...\n");
		r = new CC_Data();
		r->type = queue[0].type;
		r->length = queue[0].length;
		r->data = queue[0].data;
		
		dprint(4, "Rotating the queue...\n");
		for(int i=1; i<qPos; i++){
			queue[i-1].type = queue [i].type;
			queue[i-1].length = queue [i].length;
			queue[i-1].data = queue [i].data;			
		}
		
		dprint(4, "Reset...\n");
		queue [qPos-1].data = NULL;
		queue [qPos-1].length = 0;
		
//	printf("Correcting the cursor.\n");fflush(stdout);
		qPos--;
/*
		if(r->type==0x00){
			printf("\Text found(%d bytes): ", r->length);fflush(stdout);
			for(int i=0; i<r->length; i++)
				printf("%c", r->data[i]);
			fflush(stdout);
		}
		else{
			printf("\nCommand found(%d bytes): ", r->length);fflush(stdout);
			for(int i=0; i<r->length; i++)
				printf("%2X ", r->data[i]);
			printf("\n");fflush(stdout);
		}
	*/	
	}
	//printf("Queue cursor: %d\n", qPos);fflush(stdout);
	//printf("Pop exit.\n");;fflush(stdout);
	return r;
}
