#include "lBuffer.h"
#include "extra.h"
#include "GLCD.h"
#include "stdafx.h"

#define DEFAULT_BACKGROUND_COLOR 0
#define DEFAULT_TEXT_COLOR 7

#define IS_NUM(X) ((X >= '0' && X <='9')?1:0)

/****************************************************************************
	Výchozí konstruktor třídy LBuffer

	Vstup: 
		není

	Výstup:
		není

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

LBuffer::LBuffer(void)
{
	//Inicializace rozměrů konzolového bufferu
	this->length = CONSOLE_HEIGHT;
	this->lineLength = CONSOLE_WIDTH;
	this->offset = 0;
	
	//Inicializace pohyblivého pole pro konzolová data
	for (int i = 0; i < this->length; i++)
	{
		primary[i] = primaryLines[i];
		alternative[i] = alternativeLines[i];

		//Vymazání kazdeho radku konzole
		data = primary;
		this->deleteLine(i);
		data = alternative;
		this->deleteLine(i);
	}

	//Inicializace pracovních parametrů na výchozí hodnoty
	top = 0;
	ctr = 0;
	Cursor.y = 0;
	Cursor.x = 0;
	wrapping = 0;
	this->currentAttrs = 0;
	this->currentColor = ((DEFAULT_BACKGROUND_COLOR<<4)&240)|(DEFAULT_TEXT_COLOR&15);
	this->CursorState.changed = YES;
	this->CursorState.blink = YES;
	this->CursorState.active = YES;
	SavedCursor.isFresh = NO;
	scrolling = NO;
	scrollStart = 0;
	scrollStop = 0;
	data = primary;
	ApplicationCursorKeys = YES;
}


/****************************************************************************
	Metoda vrací aktuální jméno konzole

	Vstup: 
		není

	Výstup:
		ukazatel na název konzole

*****************************************************************************/
unsigned char * LBuffer::GetScreenName(void)
{
	return (unsigned char*) this->ScreenName;
}

/****************************************************************************
	Metoda nahrazující výchozí konstruktor třídy

	Vstup: 
		není

	Výstup:
		není

*****************************************************************************/
void LBuffer::Init(void)
{

	//Inicializace rozmeru konzoloveho bufferu
	this->length = CONSOLE_HEIGHT;
	this->lineLength = CONSOLE_WIDTH;
	this->offset = 0;	

	//Priprava pohybliveho pole pro data
	for (int i = 0; i < this->length; i++)
	{
		primary[i] = primaryLines[i];
		alternative[i] = alternativeLines[i];
		//Vymazani konzolovych dat
		data = primary;
		this->deleteLine(i);
		data = alternative;
		this->deleteLine(i);
	}

	//Nastaveni vychozich parametru
	top = 0;
	ctr = 0;
	Cursor.y = 0;
	Cursor.x = 0;
	wrapping = 0;
	this->currentAttrs = 0;
	this->currentColor = ((DEFAULT_BACKGROUND_COLOR<<4)&240)|(DEFAULT_TEXT_COLOR&15);
	this->CursorState.changed = YES;
	this->CursorState.blink = YES;
	this->CursorState.active = YES;
	SavedCursor.isFresh = NO;
	scrolling = NO;
	scrollStart = 0;
	scrollStop = 0;
	data = primary;
	ApplicationCursorKeys = YES;
}

/****************************************************************************
	Vychozi destruktor diky staticke inicializaci postrada vyznam

	Vstup: 
		neni

	Výstup:
		neni

*****************************************************************************/
LBuffer::~LBuffer(void)
{

}

/****************************************************************************
	Vraci delku bufferu (pocet radku)

	Vstup: 
		neni

	Výstup:
		delka

*****************************************************************************/
int LBuffer::getLength(void)
{
	return this->length;
}

/****************************************************************************
	Vraci pocet sloupcu bufferu

	Vstup: 
		neni

	Výstup:
		delka

*****************************************************************************/
int LBuffer::getLineLength(void)
{
	return this->lineLength;
}


/****************************************************************************
	Metoda pro uvolneni posledniho radku bufferu. Dojde k posunuti aktualnich
	dat nahoru

	Vstup: 
		neni

	Výstup:
		neni

*****************************************************************************/
void LBuffer::createLine(void)
{
	//Pokud neni inicializovan buffer, konec
	if (data == 0)
		return;

	FORMATED_CHAR * tmp = data[0];
	//Posunuti radku o jedna nahoru
	for (int i = 0; i <this->length-1 ; i++)
	{	
		data[i] = data[i+1]; 
	}
	data[this->length-1] = tmp;
	//Vymazani aktualniho radku
	if (data!=0)
	{
		deleteLine(this->length-1);
		data[this->length-1][0].data = ' ';
	}

}

/****************************************************************************
	Metoda ulozi informace o aktualnim stavu kurzoru

	Vstup: 
		neni

	Výstup:
		neni

*****************************************************************************/
void LBuffer::SaveCursor(void)
{
	SavedCursor.x = Cursor.x;
	SavedCursor.y = Cursor.y;
	SavedCursor.isFresh = Cursor.isFresh;
	SavedCursorState.active = CursorState.active;
	SavedCursorState.blink = CursorState.blink;
	SavedCursorState.changed = CursorState.changed;
}


/****************************************************************************
	Metoda obnovi informace o kurzoru

	Vstup: 
		neni

	Výstup:
		neni

*****************************************************************************/
void LBuffer::RestoreCursor(void)
{
	Cursor.x = SavedCursor.x;
	Cursor.y = SavedCursor.y;
	Cursor.isFresh = SavedCursor.isFresh;
	CursorState.active = SavedCursorState.active;
	CursorState.blink = SavedCursorState.blink;
	CursorState.changed = SavedCursorState.changed;
}

/****************************************************************************
	Metoda vlozi nova data na konec bufferu. Pokud jsme na poslednim radku,
	uvolni misto a posune vsechna data nahoru.

	Vstup: 
		neni

	Výstup:
		neni

*****************************************************************************/
void LBuffer::insertLine(unsigned char *input)
{
	this->insertData(input);
	if (top <length-1)
	{
		top++;
	}
	if (Cursor.y+1 < length)
		Cursor.y++;
	else
		this->createLine();
	Cursor.x = 0;
}

/****************************************************************************
	Metoda zpracuje data pro vlozeni do konzoloveho bufferu a provede jejich
	spravne naformatovani. Je to pomerne komplexni zalezitost, ktera zabrala 
	enormni mnozstvi casu. Je pocitano s konfiguraci terminalu jako xterm

	Vstup: 
		input:	Data ke vlozeni

	Výstup:
		neni

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

void LBuffer::insertData(unsigned char* input)
{
	int ilen = get_length((unsigned char*)input);
	ctr = 0;
	for (int i = 0; i < ilen; i++)
	{
		switch(input[i])
		{
		//Znak Backspace
		case (char)BS:
			//Vrati se o jeden znak zpet a provede smazani dane pozice
			Cursor.x -= (Cursor.x>0)?1:0;
			if (ApplicationCursorKeys == NO)
			{
				this->data[Cursor.y][Cursor.x].data = ' ';
				this->data[Cursor.y][Cursor.x].dataAttrs = 0;
				this->data[Cursor.y][Cursor.x].dataColors = ((DEFAULT_BACKGROUND_COLOR<<4)&240)|(DEFAULT_TEXT_COLOR&15);
			}
			break;
		//Znak FF
		case (char) FF:
			for (int j = 0; j < this->length; j++)
			{
				deleteLine(j);
				//clearBuffer(this->data[j],this->lineLength);
				Cursor.x = 0;
				Cursor.y = 0;
				this->data[Cursor.y][Cursor.x].data = ' ';
			}
			break;
		//Escape sequence - slozitejsi zpracovani
		case (char) ESC:
			i+=ParseEscape(this,ilen-i-1,input+i+1);
			break;
		//Zvonek - nevedeme
		case (char) BEL:
			break;
		case 0x0F:
			break;
		//Znak CR
		case '\r':
			Cursor.x = 0;
			/*if (Cursor.y >=length-1)
				this->createLine();
			else
				Cursor.y++;*/
			break;
		//Znak LF
		case '\n':
			if (Cursor.y >=length-1)
				this->createLine();
			else
				Cursor.y++;
			
			break;
		//Ostatni znaky
		default:
			//Kontrola, jestli server nepozaduje zapis mimo okraje bufferu
			if (Cursor.y < 0 || Cursor.y >= length || Cursor.x < 0 || Cursor.x >= lineLength)
			{
				DebugOut("CONSOLE ERROR - OUT OF RANGE!!!");
				break;
			}

			//Vlozeni dat do bufferu na pozici kurzoru
			this->data[Cursor.y][Cursor.x].data = input[i];
			this->data[Cursor.y][Cursor.x].dataColors = currentColor;
			this->data[Cursor.y][Cursor.x++].dataAttrs = currentAttrs;
			break;
		}
		//Zalamovani radku. Je-li aktivni, provede se po dosazeni konce radku odskok na novy radek.
		//
		if (wrapping == 0)
		{
			if (Cursor.x >= lineLength)
			{
				Cursor.x = 0;
				/*if (Cursor.y >=length-1)
					this->createLine();
				else
					Cursor.y++;*/
				continue;
			}
		}
		else
			DebugOut("wrapping=1");
	}
	if (ApplicationCursorKeys == NO)
		this->data[Cursor.y][Cursor.x].data = ' ';

}


/****************************************************************************
	Tisk radku s danym indexem. Je zpracovano take formatovani

	Vstup: 
		line:	Index radku

	Výstup:
		neni

*****************************************************************************/
int LBuffer::PrintLine(int line)
{

	LCD_console_printf(data[line],lineLength, line==this->Cursor.y?Cursor.x:-1);
	return 0;
}


/****************************************************************************
	Vymaze dany radek a nastavi fomatovani do defaultu

	Vstup: 
		line:	index radku

	Výstup:
		neni

*****************************************************************************/
void LBuffer::deleteLine(int line)
{
	deleteLine(line,0,lineLength);
}

/****************************************************************************
	Vymaze dany radek a nastavi fomatovani do defaultu

	Vstup: 
		line:	index radku
		start:	pocatecni pozice na radku
		stop:	konecna pozica na radku

	Výstup:
		neni

*****************************************************************************/
void LBuffer::deleteLine(int line, int start, int stop)
{
	for (int j = start; j < stop; j++)
	{
		this->data[line][j].data = 0;
		this->data[line][j].dataAttrs = 0;
		this->data[line][j].dataColors = ((DEFAULT_BACKGROUND_COLOR<<4)&240)|(DEFAULT_TEXT_COLOR&15);
	}

}

/****************************************************************************
	Metoda castecne resetuje buffer. Plny reset je proveden pomoci metody init.

	Vstup: 
		neni

	Výstup:
		neni

*****************************************************************************/
void LBuffer::Reset(void)
{
	Cursor.x = 0;
	Cursor.y = 0;
	wrapping = 0;
	for (int i = 0; i < length; i++)
	{
		this->deleteLine(i);
	}
	//SavedCursor.isFresh = NO;
	//SavedCursor.x = 0;
	//SavedCursor.y = 0;
	scrolling = NO;
	scrollStart = 0;
	scrollStop = 0;
	wrapping = 0;
	currentAttrs = 0;
	ApplicationCursorKeys = NO;
}


/****************************************************************************
	Provede zpracovani ridiciho prikazu z bufferu. Pocatek bufferu musi 
	obsahovat znak nasledujici za znakem esc. Funkce je umistena mimo
	tridu, proto je treba vlozit jako jeden z parametru instanci aktualni tridy

	Slovni popis struktury funkce by byl pomerne neprehledny, proto uvadim odkaz na 
	tabulku, podle niz byly funkce terminalu implementovany.

	TODO: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html

	Vstup: 
		curr:		instance (obdoba this)
		maxLength:	Maximalni delka parametru pro pripad, ze by prikaz nebyl kompletni
		input:		Vstupni retezec

	Výstup:
		delka zpracovane sekvence

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

char ParseEscape(LBuffer * curr, int maxLength, unsigned char* input)
{
	char buff[7] = "";
	char out = 0;
	char command = 0;
	
	switch(input[0])
	{
	case '[':
		while(!((input[out]>='A' && input[out]<='Z')||(input[out]>='a' && input[out]<='z') || (input[out]==0) || out == maxLength))
		{
			out++;
		}
		command = input[out++];
		switch (command)
		{
		case 'h':
			if (input[1] == '?')
			{
				switch(getNumber(input+2))
				{
				case 1:
					//zvlastni zpusob mazani
					curr->ApplicationCursorKeys = YES;
					break;
				case 7:
					curr->wrapping = 1;
					break;
				case 12:
					curr->CursorState.blink = YES;
					curr->CursorState.changed = YES;
					break;
				case 25:
					curr->CursorState.active = YES;
					curr->CursorState.changed = YES;
					break;
				case 1000:
					//is ignored allways
					break;
				case 1047:
					curr->data = curr->alternative;
					break;
				case 1048:
					curr->SaveCursor();
					break;
				case 1049:
					curr->data = curr->alternative;
					curr->SaveCursor();
					curr->Reset();
					break;
				default:
					out = 0;
					break;
				}
			}
			else
			{
				out = 0;
			}
			break;
		case 'l':
			if (input[1] == '?')
			{
				switch(getNumber(input+2))
				{
				case 1:
					curr->ApplicationCursorKeys = NO;
					//bezny zpusob mazani.
					break;
				case 7:
					curr->wrapping = 0;
					break;
				case 12:
					curr->CursorState.blink = NO;
					curr->CursorState.changed = YES;
					break;
				case 25:
					curr->CursorState.active = NO;
					curr->CursorState.changed = YES;
					break;
				case 1000:
					//is ignored allways
					break;
				case 1047:
					curr->data = curr->primary;
					break;
				case 1048:
					curr->RestoreCursor();
					break;
				case 1049:
					curr->data = curr->primary;
					curr->RestoreCursor();
					break;
				default:
					out = 0;
					break;
				}
			}
			else
			{
				out = 0;
			}
			break;
		case 'm':
			if (out == 2)
			{
				curr->currentAttrs = 0;
				curr->wrapping = 0;
				curr->currentColor = ((DEFAULT_BACKGROUND_COLOR<<4)&240)|(DEFAULT_TEXT_COLOR&15);
			}
			else
			{
				int par = 0;
				int lng = 0;
				for (int i = 1; i < out; i++)
				{
					
					if (IS_NUM(input[i]))
					{
						buff[lng++] = input[i];
					}
					else
					{
						buff[lng] = 0;
						lng = 0;
						par = char2int(buff);
						if (par == 7)
							curr->currentAttrs |= 1<<ATTR_INVERSE;
						else if (par >=40 && par < 50)
						{
							if (par > 47)
								par = DEFAULT_BACKGROUND_COLOR+40;
							curr->currentColor &= 15;
							curr->currentColor |= (par-40) << 4;
							

						}
						else if (par >=30 && par < 40)
						{
							if (par > 37)
								par = DEFAULT_TEXT_COLOR+30;
							curr->currentColor &= 240;
							curr->currentColor |= (par-30);
						}
						
					}
					
					
				}
			}
			break;
		case 'A':
			if (out == 2)
				curr->Cursor.y--;
			else
			{
				curr->Cursor.y-=(short)getNumber(input+1);
			}
			break;
		case 'B':
			if (out == 2)
				curr->Cursor.y++;
			else
			{
				curr->Cursor.y+=(short)getNumber(input+1);
			}
			break;
		case 'C':
			if (out == 2)
				curr->Cursor.x++;
			else
			{
				curr->Cursor.x+=(short)getNumber(input+1);
			}
			break;
		case 'D':
			if (out == 2)
				curr->Cursor.x--;
			else
			{
				curr->Cursor.x-=(short)getNumber(input+1);
			}
			break;
		case 'G':
			if (out == 2)
			{
				curr->Cursor.x = 1;
			}
			else
			{
				int sem_pos = 0;
				for (sem_pos = 1; sem_pos < out-1; sem_pos++)
				{
					buff[sem_pos-1] = input[sem_pos];
					
				}
				buff[sem_pos] = 0;
				curr->Cursor.x = (short)char2int(buff);

			}
			break;
		case 'f':
		case 'H':
			if (out == 2)
			{
				curr->Cursor.x = 0;
				curr->Cursor.y = 0;
			}
			else 
			{
				int sem_pos = 0;

				for (sem_pos = 0; sem_pos < out; sem_pos++)
				{
					if (input[sem_pos+1] == ';')
						break;
					buff[sem_pos] = input[sem_pos+1];
				}
				curr->Cursor.y = (short)char2int(buff)-LBUFFER_OFFSET;
				for (int i = sem_pos+2; i < out; i++)
				{
					if (input[i] == 'H' || input[i] == 'f')
					{
						buff[i-sem_pos-2] = 0;
						break;
					}
					buff[i-sem_pos-2] = input[i];
				}
				curr->Cursor.x = (short)char2int(buff)-LBUFFER_OFFSET;
			}
			break;
		/*case 'g':
			break;*/
		case 'K':
			if (out == 2)
			{
				curr->deleteLine(curr->Cursor.y,curr->Cursor.x,curr->lineLength);
			}
			break;
		case 'J':
			if (out == 2)
			{
				for (int i = curr->Cursor.y; i < curr->length; i++)
					curr->deleteLine(i);
			}
			else
			{
				switch(getNumber(input+1))
				{
				case 0:
					for (int i = curr->Cursor.y; i < curr->length; i++)
						curr->deleteLine(i);
					break;
				case 1:
					for (int i = curr->Cursor.y; i >= 0; i--)
						curr->deleteLine(i);
					break;
				case 2:
					for (int i = 0; i < curr->length; i++)
						curr->deleteLine(i);
					break;
				}
			}
			break;
		/*case 'n':
			break;
		case 'c':
			break;
		case 'q':
			break;
		case 'y':
			break;*/
		case 'P':
			if (out == 2)
			{
				curr->deleteLine(curr->Cursor.y,curr->Cursor.x,1);
				curr->Cursor.x = curr->Cursor.x>0?curr->Cursor.x-1:curr->Cursor.x;
			}
			else
			{
				int num =(short)getNumber(input+1);
				curr->deleteLine(curr->Cursor.y,curr->Cursor.x-(num-1),num);
				curr->Cursor.x = curr->Cursor.x-num>=0?curr->Cursor.x-num:0;
			}
			break;
		case 'r':
			if (out == 2)
			{
				curr->scrollStart = 0;
				curr->scrollStop = curr->length-LBUFFER_OFFSET;
			}
			else 
			{
				int sem_pos = 0;

				for (sem_pos = 0; sem_pos < out; sem_pos++)
				{
					if (input[sem_pos+1] == ';')
						break;
					buff[sem_pos] = input[sem_pos+1];
				}
				curr->scrollStart = char2int(buff)-LBUFFER_OFFSET;
				for (int i = sem_pos+2; i < out; i++)
				{
					if (input[i] == 'r')
					{
						buff[i-sem_pos-2] = 0;
						break;
					}
					buff[i-sem_pos-2] = input[i];
				}
				curr->scrollStop = char2int(buff)-LBUFFER_OFFSET;
			}
			break;
		case 'M':
			break;
		case 'd':
			if (out == 2)
			{
				curr->Cursor.x = 0;
				curr->Cursor.y = 0;
			}
			else 
			{
				int sem_pos = 0;
				for (sem_pos = 0; sem_pos < out; sem_pos++)
				{
					if (input[sem_pos+1] == 'd')
						break;
					buff[sem_pos] = input[sem_pos+1];
				}
				curr->Cursor.y = (short)char2int(buff)-LBUFFER_OFFSET;
				curr->Cursor.x = 0;
			}
			break;
		default:
			out = 0;
			break;
		}
		break;
	case '#':
		if (input[1] >= '0' && input[1] <='9')
		{
			out = 2;
			//change size of letters
		}
		else
			out = 0;
		break;
	case '(':
	case ')':
		if (input[1] == 'A' ||input[1] == 'B' ||input[1] == '0' ||input[1] == '1' ||input[1] == '2')
		{
			//Todo change font accordingly
			out = 2;
		}
		else
			out = 0;

		break;
	case 'c':							//Reset terminal
		curr->Reset();
		out = 1;
		break;
	case '7':
		curr->SavedCursor.isFresh = YES;
		curr->SavedCursor.x = curr->Cursor.x;
		curr->SavedCursor.y = curr->Cursor.y;
		out = 1;
		break;
	case '8':
		curr->Cursor.isFresh = YES;
		curr->Cursor.x = curr->SavedCursor.x;
		curr->Cursor.y = curr->SavedCursor.y;
		out = 1;
		break;
	/*case 'D':												//Scroll down
		//TODO: Scroll down
		out = 1;
		break;
	case 'M':												//Scroll up
		//TODO: Scroll up
		out = 1;
		break;*/
	case 'E':
		curr->Cursor.y++;
		out = 1;
		break;
	case 'H':												//Home
		curr->Cursor.x = 0;
		curr->Cursor.y = 0;
		out = 1;
		break;
	case 'I':
		out = 1;
		//TODO: reverse line feed.
		break;
	case 'K':
		//clearBuffer(curr->data[curr->Cursor.y]+curr->Cursor.x,curr->lineLength-curr->Cursor.x);
		curr->deleteLine(curr->Cursor.y,curr->Cursor.x,curr->lineLength);
		out = 1;
		break;
	case 'J':
		for (int i = curr->Cursor.y; i < curr->length; i++)
			curr->deleteLine(i);
		out = 1;
		break;
	/*case 'Z':
		//TODO: Identify...
		out = 1;
		break;*/
	case '=':
		//TODO: keypad mode - jak se s tim vyporadaT?
		out = 1;
		break;
	/*case '>':
		//TODO: Numeric keypad mode
		out = 1;
		break;
	case 'N':
		//TODO: Set single shift 2
		out = 1;
	case 'O':
		//TODO: Set single shift 3
		out = 1;
		break;*/
	case ']':											//OSC
		while(!(input[out]==7) || (input[out]==0))
		{
			out++;
		}
		command = input[out++];
		
		switch(command)
		{
		
		case 7:				//BELL
			{
				int lng = 0;
				for (int i = 1; i < out; i++)
				{					
					curr->ScreenName[lng++] = input[i];
				}
				curr->ScreenName[lng] = 0;
				DebugOut("Screen name changed: \"");
				DebugOut(curr->ScreenName);
				DebugOut("\n");
			}
			break;
		default:
			
			out=0;
			break;
		}
		break;
	default:
		out = 0;
		break;


	}
	return out;
}



