// EmulatorEngine.cpp
#include <Windows.h>
#include <stdlib.h>
#include "TelnetApp.h"
#include "TelnetSession.h"
#include "TelnetEngine.h"
#include "EmulatorEngine.h"
#include "TerminalEngine.h"
#include "WinSizeOption.h"
#include "char_codes.h"
#include "TelnetCodes.h"

#define host					mTelnetSession->mTelnetEngine

EmulatorEngine::EmulatorEngine(TelnetSession* thisTS)
: TerminalEngine(thisTS)
{
	Reset();
}

inline void EmulatorEngine::Reset(void)
{
	terminalMode = 0;
	homeRow = 1;
	homeCol = 1;
	scrlRegTop = 1;
	scrlRegBottom = 1;
	marginLeft = marginRight = 1;
	savedCursorPosX = 1;
	savedCursorPosY = 1;
	savedAttr = 0;
	currentAttr = 0;
	bufIndex = 0;
}

inline void EmulatorEngine::ConvVT100Coord(int *newRow, int *newCol, int row, int col)
{
	if(terminalMode&originMode)
	{
		*newRow = row + scrlRegTop - 1;
		*newCol = col + marginLeft - 1;
		if(*newRow > scrlRegBottom)
			*newRow = scrlRegBottom;
		if(*newCol > marginRight)
			*newCol = marginRight;
	}
	else
	{
		*newRow = row;
		*newCol = col;
	}
}

inline void EmulatorEngine::ConvTermCoord(int *newRow, int*newCol, int row, int col)
{
	if(terminalMode&originMode)
	{
		*newRow = row - scrlRegTop + 1;
		*newCol = col - marginLeft + 1;
	}
	else
	{
		*newRow = row;
		*newCol = col;
	}
} 

void EmulatorEngine::SendExtended(WPARAM Key)
{
	ConvertSendToHost(Key, 1);	
}

void EmulatorEngine::ConvertSendToHost(char c, char ExtendedCode)
{
	if(ExtendedCode)
	{
		switch(c)
		{
			case UpArrow:
				if(terminalMode&cursorKeysApplMode)
					AnswerHost(true, " OA", 3);
				else
					AnswerHost(true, " [A", 3);
				break;
			case DownArrow:
				if(terminalMode&cursorKeysApplMode)
					AnswerHost(true, " OB", 3);
				else
					 AnswerHost(true, " [B", 3);
				break;
			case LeftArrow:
				if(terminalMode&cursorKeysApplMode)
					AnswerHost(true, " OD", 3);
				else
					AnswerHost(true, " [D", 3);
				break;
			case RightArrow:
				if(terminalMode&cursorKeysApplMode)
					AnswerHost(true, " OC", 3);
				else
					AnswerHost(true, " [C", 3);
				break;
			case VK_DELETE:
				c = ForwardDelete;
				AnswerHost(false, &c, 1);
				break;
		}
	}
	else
	{
		switch(c)
		{
			case 0x0d:
				host->Send(&c,1);
				c = 0x0a;
				host->Send(&c,1);
				break;
			case BS:
				if(false) {
					c = ForwardDelete;
					host->Send(&c,1);
				} else {
					host->Send(&c,1);
				}
				break;
			default:
				host->Send(&c,1);
		}
	}
}

void EmulatorEngine::AnswerHost(BOOL escSeq, char *s, int len)
{	
	if(escSeq)
		s[0] = ESC;
	host->Send(s, len);
}

int EmulatorEngine::ComposeEscString(char *string,int *arguments, int argCount, char command)
{
	int i = 1, c;
	string[i++] = '[';
	for(c = 0; c < argCount; c++)
	{
		char ArgumentString[20];
		itoa(arguments[c], ArgumentString, 10); 
		strcat(string+i,ArgumentString);
		i += strlen(ArgumentString);
		string[i++] = ';';
		if(c==argCount) i--; // delete last ';'
	}
	string[i++] = command;
	return i;
}	

BOOL EmulatorEngine::MatchEscSequence(CharStream *stream, BOOL vt100, char *pat, int patLen)
{
	unsigned char c;
	int i;
	if(vt100)
	{
		if(!stream->GetChar(&c)) return false;
		if(c!=ESC)
		{
			stream->RestoreToMark();
			return false;
		}
	}
	for(i=0;i<patLen;i++)
	{
		if(!stream->GetChar(&c)) return false;
		if(pat[i]!=(char)c)
		{
			stream->RestoreToMark();
			return false;
		}
	}
	stream->MarkAsRead();
	return true;
}
	
void EmulatorEngine::ReceiveBuffer(unsigned char *buffer, int bufSZ)
{
	unsigned char *VT100buf;
	unsigned int VT100bufSZ;
	mRecStream->PutChars(buffer, bufSZ);
	if(mRecStream->SkipTo(ESC, &VT100buf, &VT100bufSZ))
	{
		if(VT100bufSZ > 0)
			DoWriteBfr(VT100buf,VT100bufSZ);
		ReceivedFromHost(mRecStream);
	}
	else
	{
		if(VT100bufSZ == 0)
			return;
		DoWriteBfr(VT100buf,VT100bufSZ);
	}
}

void EmulatorEngine::DoWriteBfr(unsigned char* Buffer, unsigned int BufSZ)
{
	for(unsigned int i=0; i<BufSZ; i++)
	{
		if(Buffer[i]<SPACE)
			DoVT100Command(Buffer[i]);
		else
			DrawText(Buffer+i,1);
	}
}

void EmulatorEngine::DoVT100Command(char Command)
{
	switch(Command)
	{
	case Linefeed:

		int row, col;
		GetCursorPos(&row, &col);
		if(row == scrlRegBottom)
		{
			//for(i=1;MatchEscSequence(stream, false, "\n", 1);i++){}
			ScrollRows(scrlRegTop, scrlRegBottom, -1);
			ClearLineFromCursor();
			//stream->MarkAsRead();					
		}
		else
		{
			DrawText((unsigned char*)&Command, 1);
		}
		break;
	
	default:
		DrawText((unsigned char*)&Command, 1);
	}
}

void EmulatorEngine::Send(char *buf, unsigned int buflen)
{
	for(unsigned int i = 0; i < buflen; i++)
		ConvertSendToHost(buf[i], 0);	
}

void EmulatorEngine::ReceivedFromHost(CharStream *stream)
{
	int row, col, i;
	unsigned char c;
	int arv[100], arc = 0;
	
	if(!stream->GetChar(&c)) return;

		if(c == 255)
		{
			TelnetApp::Error(0, "EmulatorEngine: c == 255.");
			return;
		}
	
		switch(c) {
			case ESC:
			if(!stream->GetChar(&c)) return;
			switch(c) {
				case '[':
					if(!ReadArgs(stream, arv, &arc, &c)) return;
					switch(c) {
						case 'A': MoveCursor((arc != 0)?-arv[0]:-1, 0); stream->MarkAsRead(); break;
						case 'B': MoveCursor((arc != 0)? arv[0]: 1, 0); stream->MarkAsRead(); break;
						case 'C': MoveCursor(0, (arc != 0)? arv[0]: 1); stream->MarkAsRead(); break;
						case 'D': MoveCursor(0, (arc != 0)?-arv[0]:-1); stream->MarkAsRead(); break;
						case 'H':
						case 'f': //cursor direct address
						{
							row = col = 1;
							if(arc == 1) row = arv[0];
							else if(arc == 2) {row = arv[0]; col = arv[1];}
							if(row<=0) row=1; if(col<=0) col=1;
							ConvVT100Coord(&row, &col, row, col);
							PlaceCursor(row,col);
							stream->MarkAsRead();
							break;
						}
						case 'n': //cursor pos report, answer ESC[pl;pcR
						{
							char s[10];
							int a[2], len;
							GetCursorPos(&row,&col);
							ConvTermCoord(a, a+1, row, col);
							len = ComposeEscString(s,a,2,'R');
							AnswerHost(true, s, len);
							stream->MarkAsRead();
							break;
						}
						case 'm': //attributes
						{
							SetAttr(ExtractArg(arv, arc));
							break;
						}
						case 'P': //delete char
							UnimplementedEscSequence(stream, "Delete char, Esc [ P");
							break;
						case 'L': //insert line
							UnimplementedEscSequence(stream, "Insert line, Esc [ L");
							break;
						case 'M': //delete line
						{
							ClearLineToCursor();
							ClearLineFromCursor();
							stream->MarkAsRead();
							break;
						}
						case 'J': //erase screen
						{
							int alt = 0;
							if(arc != 0) alt = arv[0];
							switch(alt) {
								case 0: ClearScreenFromCursor(); stream->MarkAsRead(); break;
								case 1: ClearScreenToCursor(); stream->MarkAsRead(); break;
								case 2: ClearScreen(); stream->MarkAsRead(); break;
							}
							break;
						}
						case 'K': // erase line
						{
							int alt = 0;
							if(arc != 0) alt = arv[0];
							switch(alt) {
								case 0: ClearLineFromCursor(); stream->MarkAsRead(); break;
								case 1: ClearLineToCursor(); stream->MarkAsRead(); break;
								case 2:
									ClearLineToCursor();
									ClearLineFromCursor();
									stream->MarkAsRead();
									break;
							}
							break;
						}
						case 'g': // clear tab
						{
							int alt = 0;
							if(arc != 0) alt = arv[0];
							switch(alt) {
								case 0:
									UnimplementedEscSequence(stream, "Clear tab, Esc [ g");
									break;
								case 3:
									UnimplementedEscSequence(stream, "Clear tab, Esc [ 3 g");
									break;
							}
							break;
						}
						case 'r': // set scrolling region
						{
							int top=-1,bottom=-1;
							if(arc==1)top=arv[0];
							else if(arc==2){top=arv[0];bottom=arv[1];}
							scrlRegTop = top;
							scrlRegBottom = bottom;
							ConvVT100Coord(&row, &col, 1, 1);
							PlaceCursor(row, col);
							stream->MarkAsRead();
							break;
						}
						case 'c': //what are you?
						{
							// answer ESC[?1; ps c
							// ps = 0 base vt100, 1 processor opt(stp), 2 adv video opt(avo),
							// 4 graphics processor opt(gpo)
							AnswerHost(true, " [?1;2c", 7); 
							stream->MarkAsRead();
							break;
						}
						case '?': // Set, reset private modes 
							if(!ReadArgs(stream, arv, &arc, &c)) return;
							SetMode(c, arv, arc);
							stream->MarkAsRead();
							break;
						case 'h': // Set, reset ansi modes
						case 'l':
							SetMode(c, arv, arc);
							stream->MarkAsRead();
							break;
						default: UnrecognizedEscSequence(stream, c); break;
					}
					break;
				case 'D': // Cursor down at bottom of region scroll up
				{
					GetCursorPos(&row, &col);
					if(row == scrlRegBottom)
					{
						stream->MarkAsRead();
						for(i=1;MatchEscSequence(stream, true, "D", 1);i++){}
						ScrollRows(scrlRegTop, scrlRegBottom, -i);
						ClearLineFromCursor();
					}
					else
						MoveCursor(0,-1);
					stream->MarkAsRead();
					break;
				}
				case 'M': // Cursor up at top of region scroll down
				{
					GetCursorPos(&row, &col);
					if(row == scrlRegTop)
					{
						stream->MarkAsRead();
						for(i=1;MatchEscSequence(stream, true, "M", 1);i++){}
						ScrollRows(scrlRegTop, scrlRegBottom, i);
						ClearLineFromCursor();
					}
					else
						MoveCursor(0,1);
					stream->MarkAsRead();
					break;
				}
				case 'E': // next line, same as CR LF
				{
					char buf[2]; buf[0] = Return; buf[1] = Linefeed;
					DoWriteBfr((unsigned char*)buf,2);
					stream->MarkAsRead();
					break;
				}
				case 'Z': //what are you?
				{
					// answer ESC[?1; ps c
					// ps = 0 base vt100, 1 processor opt(stp), 2 adv video opt(avo),
					// 4 graphics processor opt(gpo)
					AnswerHost(true, " [?1;2c", 7); 
					stream->MarkAsRead();
					break;
				}
				case 'H': // set tab at column
					UnimplementedEscSequence(stream, "Set tab, Esc H");
					break;
				case '7': // save cursor and attributes
				{
					GetCursorPos(&savedCursorPosX, &savedCursorPosY);
					savedAttr = currentAttr;
					stream->MarkAsRead();
					break;
				}
				case '8': // restore cursor and attributes
				{
					PlaceCursor(savedCursorPosX, savedCursorPosY);
					SetAttr(savedAttr);
					stream->MarkAsRead();
					break;
				}
				case '(':
					if(!stream->GetChar(&c)) return;
					UnimplementedEscSequence(stream, "Set character-set, Esc ( (A|B|0)");
					switch(c) {
						case 'A': break; // UK char set as G0
						case 'B': break; // US cha set as G0
						case '0': break; // Line char set as G0
						default: UnrecognizedEscSequence(stream, c); break;
					}
					break;
				case ')':
					if(!stream->GetChar(&c)) return;
					UnimplementedEscSequence(stream, "Set character-set, Esc ) (A|B|0)");
					switch(c) {
						case 'A': break; // UK char set as G1
						case 'B': break; // US cha set as G1
						case '0': break; // Line char set as G1
						default: UnrecognizedEscSequence(stream, c); break;
					}
					break;
				case 'N': // Select G2 for next char 
					UnimplementedEscSequence(stream, "Select G2 for next char, Esc N");
					break;
				case 'O': // Select G3 for next char
					UnimplementedEscSequence(stream, "Select G3 for next char, Esc O");
					break;
				case '=': // Keypad application mode
					SetMode(c, 0, 0);
					break;
				case '>': // Keypad numeric mode
					SetMode(c, 0, 0);
					break;
				case 'c': Reset(); break; //reset
				default:
					UnrecognizedEscSequence(stream, c);
					break;
			}
			break;

			default:
			//unsigned char *data;
			//unsigned int size;
			//stream->UngetChar();
			//stream->SkipTo(ESC, &data, &size);
			DoWriteBfr(&c, 1);
			stream->MarkAsRead();
		}

}


Boolean EmulatorEngine::ReadNum(CharStream *stream, char c, int *num, unsigned char *lookAhead)
{
	char s[100];
	int i = 0;
	s[i++] = c;
	while(stream->GetChar(lookAhead))
	{
		if(isdigit(*lookAhead))
		{
			if(i<100)
				s[i++] = *lookAhead;
		} else
		{
			if(i<100)
			{
				s[i] = '\0';
				*num = atoi(s);
				return true;
			} else
			{
				*num = 0;
				return true;
			}
		}
	}
	return false;
}


Boolean EmulatorEngine::ReadArgs(CharStream *stream, int *arv, int *arc, unsigned char *lookAhead)
{
	*arc = 0;
	while(stream->GetChar(lookAhead))
	{
		if(isdigit(*lookAhead))
		{
			if(!ReadNum(stream, *lookAhead, arv+*arc, lookAhead)) return false;
			(*arc)++;
			if(*lookAhead != ';')
				return true;
		}
		else if(*lookAhead == ';')
		{
			arv[(*arc)++] = 0;
		} else
		{
			if(*arc > 0)
				arv[(*arc)++] = 0;
			return true;
		}
	}
	return false;
}

AttributeWord EmulatorEngine::ExtractArg(int *arv, int arc)
{
	int i;
	AttributeWord res = 0;
	if(arc == 0) return attrOff;
	for(i=0;i<arc;i++)
	{
		if(arv[i] == Off)
			res = attrOff;
		else if(arv[i] == Bold)
			res |= BOLD;
		else if(arv[i] == Underscore)
			res |= UNDERSCORE;
		else if(arv[i] == Blink)
			res |= BLINK;
		else if(arv[i] == Reverse)
			res |= REVERSE;
	}
	return res;
}

void EmulatorEngine::SetAttr(AttributeWord attr)
{
	if(attr&attrOff)
	{
		TerminalEngine::ClearAttribute(currentAttr);
		attr &= attrOff-1;
	}
	TerminalEngine::SetAttribute(attr);
	currentAttr = attr;
}

Boolean EmulatorEngine::ReadModeArgs(	CharStream *stream,
									int *arv,
									int *arc,
									unsigned char *lookAhead)
{
	while(stream->GetChar(lookAhead))
	{
		if(isdigit(*lookAhead))
			arv[(*arc)++] = *lookAhead-'0';
		else 
			return true;
	}
	return false;
}
	
void EmulatorEngine::SetMode(char c, int *arv, int arc)
{
	if(c == '=')
		terminalMode |= keypadApplMode;
	else if(c == '>')
		terminalMode &= !keypadApplMode;
	else
	{
		int i;
		for(i=0;i<arc;i++)
		{
			switch(c) {
				case 'h':
					if(arv[0] == 0) break;
					else terminalMode |= 1<<(arv[0]-1);
					break;
				case 'l':
					if(arv[0] == 0) break;
					else terminalMode &= !(1<<(arv[0]-1));
					break;
			}
		}
		if(terminalMode&originMode)
		{
			homeRow = scrlRegTop;
			homeCol = marginLeft;
		}
		else
		{
			homeRow = 1;
			homeCol = 1;
		}
	}
}

void EmulatorEngine::UnimplementedEscSequence(CharStream *stream, char *msg)
{
	OutputDebugString("Unimplemented VT-100 command:\n");
	OutputDebugString(msg);
	OutputDebugString("\n");
	stream->MarkAsRead();
}

void EmulatorEngine::UnrecognizedEscSequence(CharStream *stream, char c)
{
	char *msg = "Unrecognized VT100-command:       \n";
	msg[30] = c;
	OutputDebugString(msg);
	stream->MarkAsRead();
}



int EmulatorEngine::SetWindowSize(int Width, int Height)
{
	int OldWidth, OldHeight;
	TerminalEngine::GetWindowSize(&OldWidth, &OldHeight);
	if(Width!=OldWidth||Height!=OldHeight)
	{
		TerminalEngine::SetWindowSize(Width, Height);
		SizeChanged = TRUE;
	}
	SetTimer(mWindow, 1, 100, NULL);
	return 0;
}

int EmulatorEngine::UpdateWindowSize(void)
{
	int Width, Height;
	if(SizeChanged)
	{
		TerminalEngine::GetCharWindowSize(&Width, &Height);
		if(Width < 0) Width = 0;
		if(Width > 1<<15) Width = 1<<15;
		if(Height < 0) Height = 0;
		if(Height > 1<<15) Height = 1<<15;
		unsigned short Data[2] = {Width, Height}; 
		mTelnetSession->InvokeCommand(WINSIZE_CHANGED, &Data);
		if(scrlRegTop == 1 && scrlRegBottom == windowRows)
			scrlRegBottom = Height;
		windowRows = Height;
		windowCols = Width;
		SizeChanged = FALSE;
	}
	KillTimer(mWindow, 1);
	return 0;
}

void EmulatorEngine::Open(void)
{
	TerminalEngine::Open();
	int Width, Height;
	GetCharWindowSize(&Width, &Height);
	windowRows = Height;
	windowCols = Width;
	scrlRegTop = 1;
	scrlRegBottom = Height;
}
