/*
 * CC plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id: ccosd.c,v 1.2 2005/11/02 16:08:26 aegyssus Exp $
 */
 
#include <iostream>
#include <unistd.h>
#include <ctype.h>
#include "ccosd.h"

cCcOsd::cCcOsd(void)
:cThread("cc osd")
{
	m_Active = false;
	m_Enabled = false;
  dprint(2, "cCcOsd::cCcOsd()");
  //m_Mutex = new cMutex();
  m_Osd = NULL;
  m_Receiver = NULL;    
    
  SetChannel(CC1);
  
  m_Font = cFont::GetFont(fontFix);
  nRollup = 0;
  bTextMode = false;
  //bCR = false;    
  
  crtContext = new sCcContext();
  crtContext->nBufferPos = 0;  
  crtContext->nX = 0;
  crtContext->nY = 0;
  
  bufferContext = new sCcContext();
  bufferContext->nBufferPos = 0; 
  bufferContext->nX = 0;
  bufferContext->nY = 0;    
  OsdController.Subscribe(9000,this);
}

cCcOsd::~cCcOsd(void)
{
  cMutexLock lock(&m_Mutex);
  dprint(2, "cCcOsd::~cCcOsd()");
  if (m_Active) {
     m_Active = false;
     Cancel(5);
  }
  
  if (m_Receiver)
   delete m_Receiver;
   
  /*if (m_Osd)
     delete m_Osd;*/

	delete bufferContext;
	delete crtContext;
	OsdController.Unsubscribe(this);
}

void cCcOsd::setActive(bool enabled){
	m_Enabled = enabled;
}

void cCcOsd::setCCType(int ccType){
}

void cCcOsd::Restart(){
	cMutexLock lock(&m_Mutex);
	int apid[2] = {0, 0};
	int dpid[2] = {0, 0};
	
	dprint(1, "Restarting #1 - initiated");
	if (m_Receiver!=NULL)
	      delete m_Receiver;				
    
	dprint(1, "Restarting #2 - old receiver deleted");
	cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel());
	apid[0] = channel->Apid(0);
	dpid[0] = channel->Dpid(0);
	m_Receiver = new cCcReceiver(channel->Ca(), channel->Vpid(), apid, dpid);
	dprint(1, "Restarting #3 - new receiver created");
//	cDevice::PrimaryDevice()->ActualDevice()->AttachReceiver(m_Receiver);
	cDevice::ActualDevice()->AttachReceiver(m_Receiver);
	
	dprint(1, "Restarting done - receiver attached.");	 
}

void cCcOsd::Activate(bool On)
{
  cMutexLock lock(&m_Mutex);
  dprint(1, "cCcOsd::Activate()"); 
		
  if (On) {
     if (!m_Active){     	
	m_Active = true;
	dprint(1, "cCcOsd: Activated"); 
     }
     else
     	dprint(1, "cCcOsd: Already activated"); 
  }
  else if (m_Active) {     
	 m_Active = false;
     Cancel(0);     
     dprint(1, "cCcOsd: Deactivated"); 
  }
  else
     	dprint(1, "cCcOsd: Already deactivated"); 
   
}

void cCcOsd::Action(void)
{
  int t0;
  dprint(2,"cCcOsd::Action()");
  m_Active = true;
  while (m_Active) {
    t0 = time_ms();
    if(m_Receiver!=NULL){
    	CC_Data *cc = m_Receiver->GetCC();
			if(cc!=NULL){
				dprint(3,"Processing in Action...");
				if(Process(cc, false)){
					//if the cc data were not stored in the cc bufer then they must be erased
						if(cc->data!=NULL)
							delete [] cc->data;
					delete cc;
				}
			}			
			else
					dprint(5, "CC: no data; goto sleep");
    }
    
    cCondWait::SleepMs(OSD_FREQ - (time_ms() - t0));
  }
}

void cCcOsd::ChannelSwitch(const cDevice * device, int channelNumber)
{	
  dprint(1, "cCcOsd::ChannelSwitch(channnel::%d)", channelNumber);
  Restart();

}

void cCcOsd::SetChannel(byte chn){
	nChannel = chn;
}

//! returns false if the data should be preserved
bool cCcOsd::Process(CC_Data* data, bool bWorkInBuffer){
	//dprint(1, "CC:Processing CC...");
	bool bOK = false;
	byte last_hi = 0xFF, last_lo = 0xFF;
	if(data==NULL){
		dprint(2, "CC:No CC data...");
		return bOK;
	}
	
	bOK = true;
	
	//we cannot write in buffer while we are processing the buffer
	if(bWorkInBuffer)		
		bWriteInBuffer = false;	
		
	if(data->type==0x01){ //data
		for(int i=0; i<data->length; i+=2){
			dprint(3, "\nbWorkInBuffer=%s", (bWorkInBuffer ? "=== TRUE ===" : "FALSE"));
			if((data->data[i]==last_hi)	&& (data->data[i+1]==last_lo))
				continue;
			DoCommand(bWriteInBuffer ? bufferContext : crtContext, data->data[i], data->data[i+1]);	
			if(bWriteInBuffer && !bWorkInBuffer){
				if(!bSkipBuffer){
					WriteInBuffer(data,i,2);
					dprint(3, "COMMAND was written in buffer");
				}				
			}
			else
				bWriteInBuffer = false;
			last_hi = data->data[i];
			last_lo = data->data[i+1];
		}
	}
	else{		
		if(bWriteInBuffer && (bufferContext->nBufferPos < CC_BUFFER_SIZE)){
			WriteInBuffer(data,0,data->length);
			dprint(1, "Write TEXT in buffer");
		}
		else
			
			if(nRollup>0){
				dprint(1, "Roll UP mode #%d detected.", nRollup);				
				scroller.AddText((char *)data->data, data->length);
				char **lines = scroller.GetLines();
				//int all = scroller.CountLines();
				dprint(1, "RU%d lines: %d", nRollup, scroller.CountLines());
				PrintRollUp(crtContext, lines, scroller.GetHeight());
			}
			else				
				PrintLine(crtContext, (char *) data->data, data->length);				
	}
	
	return bOK;
}

bool cCcOsd::PrintRollUp(sCcContext *cx, char **text, int lines){
	cMutexLock lock(&m_Mutex);
	if(m_Osd!=NULL){
		ClearScreen();
		
		dprint(3, "Printing RU%d (%d lines)", nRollup, lines);
		
		for(int i=0; i<lines; i++){		
			dprint(3, "ROLLUP TEXT [%d|%d,%d|%d]: %s", cx->nX, cx->nY+i , CHAR_WIDTH, CHAR_HEIGHT, text[i]==NULL ? "NULL" : text[i]);
			if(text[i]!=NULL)			
				m_Osd->DrawText(cx->nX * CHAR_WIDTH, (cx->nY+i) * CHAR_HEIGHT, text[i], clrWhite, clrBlack, m_Font);
		}
		
		m_Osd->Flush();
	}
	else
		dprint(1, "No OSD available");
	return true;
}

bool cCcOsd::PrintLine(sCcContext *cx, char *row, int len){
	dprint(3, "Write TEXT line on screen (%d chars), row is %s", len, (row==NULL) ? "NULL" : "OK");
		
	if(m_Osd==NULL){
		dprint(3, "CC: Osd was not previously created.");
		return false;
	}
	
	if(row==NULL){
		dprint(3, "CC Error: Cannot print a null line.");
		return false;
	}

	if(!m_Active){
		dprint(3, "CC Error: Plugin is not active.");
		return false;
	}
		
	cMutexLock lock(&m_Mutex);
	char line[CC_WIDTH];
	
	int c=0;	
	for(c=0; (c<CC_WIDTH) && (c<len); c++)	
		line[c] = row[c];
	line[c] = 0x00;	
	
	if(c>0 ){
		dprint(1, "DISPLAYED TEXT [%d|%d,%d|%d]: %s", cx->nX , CHAR_WIDTH, cx->nY, CHAR_HEIGHT, line);		
		m_Osd->DrawText(cx->nX * CHAR_WIDTH, cx->nY * CHAR_HEIGHT, line, clrWhite, clrBlack, m_Font);  	
		m_Osd->Flush();
		cx->nX += len;		
  }
	  	
	return true;
}

bool cCcOsd::DoCommand(sCcContext *context, byte hi, byte lo){
	dprint(3, "Do Command...");
	byte pac_lines[] = {0x91/*0*/, 0x91/*1*/, 0x92/*2*/, 0x92/*3*/, 0x15/*4*/, 0x15/*5*/, 0x16/*6*/, 0x16/*7*/, 
		0x97/*8*/, 0x97/*9*/, 0x10/*10*/, 0x13/*11*/, 0x13/*12*/, 0x94/*13*/, 0x94/*14*/};
	byte pac_cols[] = {0x40, 0xC1, 0xC2, 0x43, 0xC4, 0x45, 0x46, 0xC7, 0xC8, 0x49, 0x4A, 0xCB, 0x4C, 0xCD, 0xCE, 0x4F, 0xD0, 0x51, 0x52, 0xD3, 0x54, 0xD5, 0xD6, 0x57, 0x58, 0xD9, 0xDA, 0x5B, 0xDC, 0x5D, 0x5E, 0xDF,
										 0xE0, 0x61, 0x62, 0xE3, 0x64, 0xE5, 0xE6, 0x67, 0x68, 0xE9, 0xEA, 0x6B, 0xEC, 0x6D, 0x6E, 0xEF, 0x70, 0xF1, 0xF2, 0x73, 0xF4, 0x75, 0x76, 0xF7, 0xF8, 0x79, 0x7A, 0xFB, 0xFC, 0xFD, 0xFE, 0x7F};
	bool bIsPAC = false;
	
	//Global commands
	byte channels4g[] = {0x94, 0x1c, 0x15, 0x9d};
	
	//Tab positionning commands
	byte channels2p[] = {0x97, 0x1f};
	
	bool bDone = false;
	bSkipBuffer = false;
	
	//Processing general commands
	if(channels4g[nChannel]==hi){
		bTextMode = false;
		
		switch(lo){
			
			//RCL - Resume Caption Loading
			case 0x20:
				dprint(2, "{RCL}");					
				bWriteInBuffer = true;
				bSkipBuffer = true;
				bDone=true;
				break;
				
			//RDC - Resume Direct Captioning
			case 0x29:
				dprint(2, "{RDC}");
				bWriteInBuffer = false;
				bDone = true;
				break;
				
			//RU2 - 	Roll-Up Captions--2 Rows
			case 0x25:
				dprint(1, "{RU2}");
				SetRollUp(context, 2);
				bDone = true;
				break;
				
			//RU3 - 	Roll-Up Captions--3 Rows
			case 0x26:
				dprint(1, "{RU3}");
				SetRollUp(context, 3);
				bDone = true;
				break;
				
			//RU4 - 	Roll-Up Captions--4 Rows
			case 0xA7:
				dprint(1, "{RU4}");
				SetRollUp(context, 4);
				bDone = true;
				break;
				
			//TR - Text Restart
			case 0x2A:
				dprint(1, "{TR}");
				context->nX = 0;
				context->nY = 0;
				bTextMode = true;
				bDone = true;
				break;
				
			//RTD - Resume Text Display	
			case 0xAB:
				dprint(1, "{RTD}");
				bTextMode = true;
				bDone = true;
				break;
				
			//EDM - Erase Displayed Memory
			case 0x2C:				
				dprint(1, "{EDM}");
				ClearScreen();
				bSkipBuffer = true;
				bDone = true;
				break;
				
			//ENM - Erase Non-displayed Memory
			case 0xAE:
				dprint(2, "{ENM}");
				bSkipBuffer = true;
				ClearBuffer();				
				bDone = true;
				break;
				
			//EOC - End Of Caption
			case 0x2F:
				dprint(1, "{EOC}");
				ClearScreen();
				if(bWriteInBuffer){					
					bSkipBuffer = true;
					dprint(3, "Found %d commands in buffer.", context->nBufferPos);
					for(int i=0; i<context->nBufferPos; i++){
						bWriteInBuffer = false;
						Process(&context->commands[i], true);								
					}												
				}
				ClearBuffer();
				
				bWriteInBuffer = false;				
				bSkipBuffer = true;
				bDone = true;				
				break;
				
			//backspace
			case 0xA1:
				dprint(1, "{BS}");
				scroller.Backspace();			
			  bDone = true;
				break;
				
			//delete to end of row
			case 0xA4:
				dprint(1, "{DER}");
				//clear current line starting from current char
				ClearLine(context->nY, context->nX, -1);
				bDone = true;
				break;
				
			//carriage return
			case 0xAD:
				dprint(1, "{CR}");
				scroller.Scroll();					
				bDone = true;
				break;
		}
		
		if(bDone){
			if(lo!=0xA1 && lo!=0xA4 && lo!=0xAD && lo!=0x25 && lo!=0x26 && lo!=0xA7){
				scroller.Clear();
				nRollup = 0;
			}
			return true;
		}
	}
	//TO1, TO2, TO3
	if(
			(
			(nChannel<2 && channels2p[nChannel/2]==hi) ||
			(nChannel>=2 && channels2p[nChannel/2]==hi)
			) &&
			(lo==0xA1 || lo==0xA2 || lo==0x23)
		){	
			context->nX += (lo & 0x0F);	
			dprint(2, "{TO%d}", lo & 0x0F);		
			return true;
	}	
	
	int pacY = 0;
	//Processing PAC
	for(int i=0; i<15; i++)
		if(nChannel%2==0) // CC1, CC3
			if(pac_lines[i]==hi){
				bIsPAC = true;
				pacY = i;
				break;
			}				
		else //CC2, CC4
			if((pac_lines[i]-0x78	)==hi){
				bIsPAC = true;
				pacY = i;
				break;
			}
			
			
	if(bIsPAC && (nRollup==0)){		
		dprint(1, "{PAC}");
		context->bUnderline = ((lo & 0x01) == 	0x01);
		for(int i=0; i<64; i++)//i=19
			if(pac_cols[i]==lo){
				int p = i%32;
				pacY += (i<32) ? 0 : 1;
				if(p<18)
					context->nX = 0;
				else
					context->nX = 4 *(8 - (32-p)/2);
				context->nY = pacY;
				
				dprint(1,"{%2X%2X}=[%d %d]", hi, lo, context->nX, context->nY);	
				return true;
			}
	}
	

	dprint(2, "{!%2X%2X!}", hi, lo);
	return false;
}

void cCcOsd::ClearScreen(void){	
	cMutexLock lock(&m_Mutex);
	if(m_Osd){
		dprint(1, "[Screen cleared]");
		m_Osd->DrawRectangle(0, 0, OSDWIDTH, OSDHEIGHT, clrTransparent);
		m_Osd->Flush();    
	}
}

void cCcOsd::ClearLine(byte line, byte pos, int len){
	cMutexLock lock(&m_Mutex);
	if(m_Osd){
		int length = (len < 0 ? OSDWIDTH - pos * m_Font->Width('W') : len * m_Font->Width('W'));	
		m_Osd->DrawRectangle(pos * m_Font->Width('W'), m_Font->Height()*line, pos * m_Font->Width('W') + length, m_Font->Height() * (line+1), clrTransparent);
		m_Osd->Flush();
	}
}

void cCcOsd::SetRollUp(sCcContext *context, int nRollupMode){
	nRollup = nRollupMode;
	scroller.SetWindow(32,nRollupMode);
	context->nX = 0;
	context->nY = CC_HEIGHT - nRollupMode;
}

void cCcOsd::ClearBuffer(void){
	for(int i=0; i<bufferContext->nBufferPos; i++)				
		if(bufferContext->commands[i].data!=NULL && (bufferContext->commands[i].length>0)){
			//dprint(1, "\nCleaning data...");	
			if(bufferContext->commands[i].data!=NULL)
				delete [] bufferContext->commands[i].data;

			bufferContext->commands[i].data = NULL;
			bufferContext->commands[i].length = 0;
		}	
	bufferContext->nBufferPos = 0;
}

bool cCcOsd::WriteInBuffer(CC_Data *data, int pos, int len){
	bool bOk = false;

	if(bufferContext->nBufferPos <CC_BUFFER_SIZE){
		bufferContext->commands[bufferContext->nBufferPos].type		= data->type;
		bufferContext->commands[bufferContext->nBufferPos].length	= len;
		bufferContext->commands[bufferContext->nBufferPos].data		= new byte[len];
		for(int i=pos; i<pos+len; i++)
			bufferContext->commands[bufferContext->nBufferPos].data[i-pos] = data->data[i];

		bufferContext->nBufferPos++;
		bOk = true;
		
		
	}
	return bOk;
}

void cCcOsd::InitOSD(void){
	if(!m_Active){
		dprint(1, "OSD is not yet allowed.");
		return;
	}

	cMutexLock lock(&m_Mutex);
	dprint(1, "Creating the OSD...");
		
	if(m_Osd==NULL)
			m_Osd = cOsdProvider::NewOsd(120, 45, true);			
	
	
	if(m_Osd!=NULL){
		 tArea Areas[] = { { 0, 0, OSDWIDTH, OSDHEIGHT, 2 } };
	   if (m_Osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) {
	      m_Osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea));
	      dprint(1, "W:%d | H: %d", OSDWIDTH, OSDHEIGHT);
	   }
	   else {
	    tArea Areas[] = { { 0, 0, (OSDWIDTH-1), OSDHEIGHT/3, 2 },
	                      { 0, OSDHEIGHT/3+1, (OSDWIDTH-1), 2*OSDHEIGHT/3, 2 },
	                      { 0, 2*OSDHEIGHT/3+1, (OSDWIDTH-1), OSDHEIGHT, 2 }
	                    };
	    dprint(1, "Sections W:%d | H: %d", OSDWIDTH, OSDHEIGHT);	    
	    m_Osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea));
	   }	
	   dprint(1, "OSD was set.");
	}
	else
		printf("CC: OSD Not Supported!");
}

void cCcOsd::Show(){
 // if(!m_Enabled)
//	  return;
 dprint(1, "cCcOsd: Showing the CC"); 
 Activate(true); 
 InitOSD();
 Start();
}

void cCcOsd::Hide(){
 dprint(1, "*********** Hiding the CC ***********"); 
 Activate(false);	
 cMutexLock lock(&m_Mutex);
 	
 if ( m_Osd != NULL ){
      cOsd* tmp = m_Osd;
      m_Osd = NULL;
      dprint(1, "cCcOsd: before delete");
      delete tmp;
      dprint(1, "cCcOsd: after delete");
 } 
}
