// TerminalEngine.cpp
// Author: Jörgen Karlsson - d93-jka@nada.kth.se

#include <Windows.h>
#include "Resource.h"
#include "TelnetApp.h"
#include "TelnetEngine.h"
#include "EmulatorEngine.h"
#include "TerminalEngine.h"
#include "char_codes.h"

void TerminalEngine::Open(void)
{
    HINSTANCE hinstance = theApp->AppInstance;
	WNDCLASSEX wcx; 
 	TEXTMETRIC tm;

    /* 
     * Fill in the window class structure with parameters 
     * that describe the main window. 
     */ 
    wcx.cbSize = sizeof(wcx);          /* size of structure      */ 
    wcx.style = CS_OWNDC;//|CS_HREDRAW|CS_VREDRAW;
										/* redraw if size changes */
										// Get own DC
    wcx.lpfnWndProc = TerminalEngine::WindowProc; /* points to window proc. */ 
    wcx.cbClsExtra = 0;                /* no extra class memory  */ 
    wcx.cbWndExtra = 0;                /* no extra window memory */ 
    wcx.hInstance = hinstance;         /* handle of instance     */ 
    wcx.hIcon = LoadIcon(NULL, 
        IDI_APPLICATION);              /* predefined app. icon   */ 
    wcx.hCursor = LoadCursor(NULL, 
        IDC_ARROW);                    /* predefined arrow       */ 
    wcx.hbrBackground = GetStockObject( 
        WHITE_BRUSH);                  /* white background brush */ 
    wcx.lpszMenuName =  MAKEINTRESOURCE(IDR_MENU_TELNET);/* name of menu resource  */ 
    wcx.lpszClassName = "MainWClass";  /* name of window class   */ 
    wcx.hIconSm = LoadImage(hinstance, /* small class icon       */ 
        MAKEINTRESOURCE(5),
		IMAGE_ICON,
        GetSystemMetrics(SM_CXSMICON), 
        GetSystemMetrics(SM_CYSMICON), 
        LR_DEFAULTCOLOR); 
 
    /* Register the window class. */ 
 
    RegisterClassEx(&wcx); 
	
	mWindow = CreateWindow( 
    "MainWClass",           /* class name                  */ 
    "Telnet Session",       /* window name                 */ 
    WS_OVERLAPPEDWINDOW |   /* overlapped window           */ 
        WS_VSCROLL,         /* vertical scroll bar         */ 
    CW_USEDEFAULT,          /* default horizontal position */ 
    CW_USEDEFAULT,          /* default vertical position   */ 
    CW_USEDEFAULT,          /* default width               */ 
    CW_USEDEFAULT,          /* default height              */ 
    (HWND) NULL,            /* no parent or owner window   */ 
    (HMENU) NULL,           /* class menu used             */ 
    hinstance,              /* instance handle             */ 
    mTelnetSession);     /* send session as window creation data     */ 
 
	if (!mWindow) 
		exit(-1);
	
	mHDC = GetDC(mWindow);
	SetMapMode(mHDC, MM_TEXT);

	/* Create fonts needed */	
	LOGFONT  lplf[1] = {16,8,0,0,
						/*LONG lfHeight;LONG lfWidth;
						LONG lfEscapement;LONG lfOrientation;*/
				FW_NORMAL,0,0,0,
						/*LONG lfWeight;BYTE lfItalic; 
						BYTE lfUnderline;BYTE lfStrikeOut;*/
				DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,
					CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,
						/*BYTE lfCharSet;BYTE lfOutPrecision; 
						BYTE lfClipPrecision;BYTE lfQuality;*/ 
					DEFAULT_PITCH|FF_MODERN,0};
						/*BYTE lfPitchAndFamily;TCHAR lfFaceName[LF_FACESIZE]*/ 
	stdFont = CreateFontIndirect(lplf);
	
	LOGFONT lplfb[1] = {16,8,0,0,
						/*LONG lfHeight;LONG lfWidth;
						LONG lfEscapement;LONG lfOrientation;*/
				FW_BOLD,0,0,0,
						/*LONG lfWeight;BYTE lfItalic; 
						BYTE lfUnderline;BYTE lfStrikeOut;*/
				DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,
					CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,
						/*BYTE lfCharSet;BYTE lfOutPrecision; 
						BYTE lfClipPrecision;BYTE lfQuality;*/ 
					DEFAULT_PITCH|FF_MODERN,0};
						/*BYTE lfPitchAndFamily;TCHAR lfFaceName[LF_FACESIZE]*/ 
	
 	boldFont = CreateFontIndirect(lplfb);
	
	LOGFONT lplfu[1] = {16,8,0,0,
						/*LONG lfHeight;LONG lfWidth;
						LONG lfEscapement;LONG lfOrientation;*/
				FW_NORMAL,0,1,0,
						/*LONG lfWeight;BYTE lfItalic; 
						BYTE lfUnderline;BYTE lfStrikeOut;*/
				DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,
					CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,
						/*BYTE lfCharSet;BYTE lfOutPrecision; 
						BYTE lfClipPrecision;BYTE lfQuality;*/ 
					DEFAULT_PITCH|FF_MODERN,0};
						/*BYTE lfPitchAndFamily;TCHAR lfFaceName[LF_FACESIZE]*/ 
	
 	uscoreFont = CreateFontIndirect(lplfu);

	LOGFONT lplfub[1] = {16,8,0,0,
						/*LONG lfHeight;LONG lfWidth;
						LONG lfEscapement;LONG lfOrientation;*/
				FW_BOLD,0,1,0,
						/*LONG lfWeight;BYTE lfItalic; 
						BYTE lfUnderline;BYTE lfStrikeOut;*/
				DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,
					CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,
						/*BYTE lfCharSet;BYTE lfOutPrecision; 
						BYTE lfClipPrecision;BYTE lfQuality;*/ 
					DEFAULT_PITCH|FF_MODERN,0};
						/*BYTE lfPitchAndFamily;TCHAR lfFaceName[LF_FACESIZE]*/ 
	
 	bolduscoreFont = CreateFontIndirect(lplfub);

	
	// Select standard font.
	SelectObject(mHDC, stdFont); 
	
	// Save window size.
	RECT bounds;
	GetClientRect(mWindow, &bounds);
	mWindowHeight = bounds.bottom;
	mWindowWidth = bounds.right;

	// Save the avg. width and height of characters.
	GetTextMetrics(mHDC, &tm);
	mCharWidth = tm.tmAveCharWidth; 
	mCharHeight = tm.tmHeight;

	// Save text and background colors
	mTextColor = GetTextColor(mHDC);
	mBkColor = GetBkColor(mHDC);
	

	// Create and initialize buffer
	mBufSZ.x = GetSystemMetrics(SM_CXFULLSCREEN)/mCharWidth;	// Width of buffer in characters.
	mBufSZ.y = 3*GetSystemMetrics(SM_CYFULLSCREEN)/mCharHeight;	// Height of buffer in characters
	mBuffer = new char[mBufSZ.x*mBufSZ.y];
	mAttrBuffer = new unsigned char[mBufSZ.x*mBufSZ.y];
	mOrigin.x = mOrigin.y = mCursor.x = mCursor.y = 1;


	// Initialize scrollbar
	SCROLLINFO s = {sizeof(SCROLLINFO),SIF_POS|SIF_RANGE,0,
						mBufSZ.y*mCharHeight-mWindowHeight,
								0,0,0};
	SetScrollInfo(mWindow, SB_VERT, &s, FALSE);

	// Show the window 
	Clear();
	ShowWindow(mWindow, SW_SHOWDEFAULT);
}

LRESULT CALLBACK TerminalEngine::WindowProc(HWND window, UINT msg, WPARAM wParam, LPARAM lParam)
{
	static TelnetSession *theSession;
	PAINTSTRUCT paint;
	switch(msg)
	{
	
	case WM_CREATE:
		theSession = (TelnetSession *)(((CREATESTRUCT*)lParam)->lpCreateParams);
		break;
	
	case WM_DESTROY:
		CloseWindow((HWND)wParam);
		PostQuitMessage(0); break;
	
	case WM_CHAR:
		theSession->mTerminalEngine->Send((char*)&wParam, 1);
		break;

	case WM_KEYDOWN: 
		switch (wParam)
		{
		case VK_LEFT:   /* LEFT ARROW */ 
		case VK_RIGHT:  /* RIGHT ARROW */ 
		case VK_UP:     /* UP ARROW   */ 
		case VK_DOWN:   /* DOWN ARROW */
		case VK_DELETE:	// DELETE KEY
			theSession->mTerminalEngine->SendExtended(wParam);
			break;
		case VK_HOME:   /* HOME */ 
		case VK_END:    /* END */
			break;
		}
		break;
	
	case WM_PAINT:
/*		RECT test;
		if(GetUpdateRect(theSession->mTerminalEngine->mWindow, &test, FALSE))
*/		{
			BeginPaint(theSession->mTerminalEngine->mWindow, &paint);
			theSession->mTerminalEngine->Redraw(paint.rcPaint);
			EndPaint(theSession->mTerminalEngine->mWindow, &paint);
		}
		break;

	case WM_TIMER:
			theSession->mTerminalEngine->UpdateWindowSize();
			break;

	case WM_COMMAND:
		if(HIWORD(wParam) == 0) // Menu selected.
			theSession->InvokeCommand(LOWORD(wParam));
		break;
	
	case WM_SETFOCUS:
		if(!CreateCaret(theSession->mTerminalEngine->mWindow,0,
			0,
			theSession->mTerminalEngine->mCharHeight))
			TelnetApp::Error(GetLastError(),
					"TerminalEngine::WindowProc: Unable to create caret.\n");
		theSession->mTerminalEngine->UpdateCaret();
		ShowCaret(theSession->mTerminalEngine->mWindow);
		break;
	
	case WM_KILLFOCUS:
		HideCaret(theSession->mTerminalEngine->mWindow);
		DestroyCaret();
		break;
		
	case WM_VSCROLL:
		switch (LOWORD(wParam))
		{
		case SB_LINEUP:
		case SB_LINEDOWN:
		case SB_PAGEUP:
 		case SB_PAGEDOWN:
			theSession->mTerminalEngine->SetOriginRelative(LOWORD(wParam));
			break;

		case SB_THUMBPOSITION:
		case SB_THUMBTRACK:
			int nPos = HIWORD(wParam);
			theSession->mTerminalEngine->SetOrigin(0, nPos);
			break;
		}
		break;
	
	case WM_SIZE: 
		theSession->mTerminalEngine->SetWindowSize(LOWORD(lParam), HIWORD(lParam));
		break;

	case SM_READ:
		theSession->mTelnetEngine->ReadSocket();
		break;

	default: return DefWindowProc(window, msg, wParam, lParam);
	}
	return 0;
}

void TerminalEngine::DrawText(unsigned char *buf, unsigned int buflen)
{
	unsigned int i;
	const int tabspace = 8;
	unsigned int invTopRow = mCursor.y;
	for(i=0;i<buflen;i++)
	{
		switch(buf[i])
		{
		case NUL: break;
		
		case Linefeed:	// Line	Feed
			InvalidateRows(mCursor.y, mCursor.y);
			mCursor.y++;
			break;
		
		case Return:	// Carrige Return
			mCursor.x = 1;
			break;

		case BS:		// Back Space
			mCursor.x--;
			break;

		case BEL:		// Bell
			MessageBeep(MB_ICONHAND);
			continue;
			break;
		
		case HT:		// Horizontal Tab
			mCursor.x += tabspace - (mCursor.x-1)%tabspace;
			break;
		case 0x01:
		case 0x02:
		case 0x03:
		case 0x04:
		case 0x05:
		case 0x06:
		case 0x0b:
		case 0x0c:
		case 0x0e:
		case 0x0f:
		case 0x10:
		case 0x11:
		case 0x12:
		case 0x13:
		case 0x14:
		case 0x15:
		case 0x16:
		case 0x17:
		case 0x18:
		case 0x19:
		case 0x1a:
		case 0x1b:
		case 0x1c:
		case 0x1d:
		case 0x1e:
		case 0x1f:
			break;

		default:		// Printable character
			int x = mCursor.x, y = 0;
			LCtoBC(&y,&x);
			if(x <= mBufSZ.x)
			{
			*Cur2Addr(mBuffer, mCursor.y, mCursor.x) = buf[i];
			*Cur2Addr(mAttrBuffer, mCursor.y, mCursor.x) = mCurrentAttr;
			mCursor.x++;
			}
		}
		int y = mCursor.y, x = 0;
		LCtoBC(&y,&x);
		if(y > mBufSZ.y) Scroll(-1);
	}

	unsigned int invBottomRow = mCursor.y;
	BOOL erase = AdjustOrigin();
	InvalidateRows(invTopRow, invBottomRow);
}

void TerminalEngine::RedrawAll(void)
{
/*	int err, i;
	int RowsToDraw, StartRow;
	RECT bounds;
	POINT origin;


	GetClientRect(mWindow, &bounds);
	GetWindowOrgEx(mHDC, &origin);
	StartRow = origin.y/mCharHeight;
	RowsToDraw = bounds.bottom/mCharHeight+1;
	for(i=0;i<RowsToDraw;i++)
	{
		if(StartRow+i>=mBufSZ.y) break;
		bounds.top = mCharHeight * (StartRow+i);
		bounds.bottom =	mCharHeight * (StartRow+i+1);
		err = ::DrawText(mHDC, mBuffer+mBufSZ.x*(StartRow+i), -1,
						&bounds, 0);
	}
	SetCaretPos(mCharWidth*(mCursor.x-1), mCharHeight*(mCursor.y-1));
	ValidateRect(mWindow, 0);
*/}


void TerminalEngine::Close(void)
{
	ReleaseDC(mWindow, mHDC);
	PostMessage(mWindow,WM_DESTROY,0,0);
}

void TerminalEngine::Scroll(int Rows)
{
	mCursor.y += Rows;
	mCursor.x = 1;
	ScrollRows(1, mBufSZ.y, Rows);
	//AdjustOrigin();
}

void TerminalEngine::Clear(void)
{
	FillMemory(mBuffer, mBufSZ.x*mBufSZ.y, SPACE);
	FillMemory(mAttrBuffer, mBufSZ.x*mBufSZ.y, 0);
}

// Adjust window origin so that caret i visible.
// Return TRUE if origin is moved, FALSE otherwise.
BOOL TerminalEngine::AdjustOrigin(void)
{
	BOOL wasMoved = FALSE;
	POINT caret, origin, bottomright;
 	int x = mCursor.x;
 	int y = mCursor.y;
	LCtoBC(&y, &x);
	caret.x = mCharWidth*(x-1);
	caret.y = mCharHeight*(y-1);

	bottomright.y = mWindowHeight;
	DPtoLP(mHDC, &bottomright, 1);

	// Get current origin.
	GetWindowOrgEx(mHDC, &origin);

	if(	bottomright.y<caret.y+mCharHeight-mCharHeight/4 ||	// Caret bellow bottom 
		caret.y+mCharHeight/4 < origin.y)					// Caret above origin
	{
		origin.y = (mOrigin.y-1)*mCharHeight;
		SetOrigin(0, origin.y);
		wasMoved = TRUE;
	}
	return wasMoved;
}

// Adjust origin so that only buffer is visible.
int TerminalEngine::AdjustCharOrigin(void)
{
	int x,y;
	GetCharWindowSize(&x,&y);
	
	if(mCursor.y > y)
	{
		mOrigin.y += mCursor.y - y;
		mCursor.y = y;
	}

	LCtoBC(&y,&x);
	if(y>mBufSZ.y)
	{
		int move = y - mBufSZ.y;
		mOrigin.y -= move;
		mCursor.y += move;
	}
	
	return 0;
}

TerminalEngine::TerminalEngine(TelnetSession *thisTelnetSession)
{
	mTelnetSession = thisTelnetSession;
	mRecStream = new CharStream();
	mCurrentAttr = 0;
	mBuffer = 0;mAttrBuffer = 0;
}

TerminalEngine::~TerminalEngine(void)
{
	if(mBuffer)
		delete []mBuffer;
	if(mAttrBuffer)
		delete []mAttrBuffer;
	delete mRecStream;
}



void TerminalEngine::MoveCursor(int drow, int dcol)
{
	int x,y;
	GetCharWindowSize(&x,&y);
	mCursor.y += drow;
	if(mCursor.y < 1) mCursor.y = 1;
	if(mCursor.y > y) mCursor.y = y;

	mCursor.x += dcol;
	if(mCursor.x < 1) mCursor.x = 1;
	if(mCursor.x > x) mCursor.x = x;

	UpdateCaret();
}

void TerminalEngine::PlaceCursor(int row, int col)
{
	int x,y;
	GetCharWindowSize(&x,&y);
	mCursor.y = row;
	if(mCursor.y < 1) mCursor.y = 1;
	if(mCursor.y > y) mCursor.y = y;

 	mCursor.x = col;
	if(mCursor.x < 1) mCursor.x = 1;
	if(mCursor.x > x) mCursor.x = x;

	UpdateCaret();
}

void TerminalEngine::GetCursorPos(int *row, int *col)
{
 	*row = mCursor.y;
	*col = mCursor.x;
}

void TerminalEngine::ScrollRows(int ScrRegTop, int ScrRegBot, int Rows)
{

	// Redraw invalid part of window, to make sure window is ok to scroll.
	UpdateWindow(mWindow);

	int Width, Height;
	GetCharWindowSize(&Width, &Height);
	if(ScrRegTop == 1 && ScrRegBot == Height && Rows < 0)
	{
		// Whole screen being scrolled.
		LCtoBC(&Height, &Width);
		
		// Check if it's posible just to move origin.
		if(Height < mBufSZ.y)
		{
			Rows *= -1;
			int RowsToScroll = mBufSZ.y-Height;
			if(RowsToScroll > Rows) RowsToScroll = Rows;
			int RowsLeft = Rows - (mBufSZ.y-Height);
			if(RowsLeft < 0) RowsLeft = 0;
			mOrigin.y += RowsToScroll;
			if(RowsLeft) ScrollRows(ScrRegTop, ScrRegBot, -RowsLeft);
			return;
		}
	}
	
	
	// Scroll into buffer if possible.
	int ScrRegTopBuf;
	if(ScrRegTop == 1) ScrRegTopBuf = ScrRegTop - (mOrigin.y-1);
	else ScrRegTopBuf = ScrRegTop;
	
	// Scroll contents of buffer.
	int memrow = Rows;
	if(Rows < 0)
	{
		Rows *= -1;
		MoveMemory(Cur2Addr(mBuffer, ScrRegTopBuf,1),Cur2Addr(mBuffer, ScrRegTopBuf+Rows,1)
					,mBufSZ.x*(ScrRegBot-ScrRegTopBuf+1-Rows));
		FillMemory(Cur2Addr(mBuffer, ScrRegBot+1-Rows,1),mBufSZ.x*Rows, SPACE);
		MoveMemory(Cur2Addr(mAttrBuffer, ScrRegTopBuf,1),Cur2Addr(mAttrBuffer, ScrRegTopBuf+Rows,1)
					,mBufSZ.x*(ScrRegBot-ScrRegTopBuf+1-Rows));
		FillMemory(Cur2Addr(mAttrBuffer, ScrRegBot+1-Rows,1),mBufSZ.x*Rows, 0);
	
		//ScrollRect.top += (ScrRegBot-ScrRegTop-Rows)*mCharHeight;
	}
	else
	{
		MoveMemory(Cur2Addr(mBuffer, ScrRegTop+Rows,1),Cur2Addr(mBuffer, ScrRegTop,1)
				,mBufSZ.x*(ScrRegBot-ScrRegTop+1-Rows));
		FillMemory(Cur2Addr(mBuffer, ScrRegTop, 1),mBufSZ.x*Rows, SPACE);
		MoveMemory(Cur2Addr(mAttrBuffer, ScrRegTop+Rows,1),Cur2Addr(mAttrBuffer, ScrRegTop,1)
				,mBufSZ.x*(ScrRegBot-ScrRegTop+1-Rows));
		FillMemory(Cur2Addr(mAttrBuffer, ScrRegTop, 1),mBufSZ.x*Rows, 0);
	
		//ScrollRect.bottom -= (ScrRegBot-ScrRegTop-Rows)*mCharHeight;
	}
	

	// Scroll contents of window
	HideCaret(mWindow);
	
	RECT ScrollRect;
	ScrollRect.top = (ScrRegTop-1)*mCharHeight;
	ScrollRect.bottom = ScrRegBot*mCharHeight;
	ScrollRect.left = 0;
	ScrollRect.right = mWindowWidth;
	
	//GetClientRect(mWindow,(RECT*)&ScrollRect);	// Always scroll all of window.
						// Should only scroll the specified rows.
	ScrollWindowEx(	mWindow, 0, mCharHeight*memrow, &ScrollRect,&ScrollRect,
					0, 0, SW_ERASE|SW_INVALIDATE);

	ShowCaret(mWindow);
}

void TerminalEngine::ClearLineFromCursor(void)
{
	FillMemory(Cur2Addr(mBuffer, mCursor.y, mCursor.x), mBufSZ.x-mCursor.x+1, SPACE);
	FillMemory(Cur2Addr(mAttrBuffer, mCursor.y, mCursor.x), mBufSZ.x-mCursor.x+1, 0);
	InvalidateRows(mCursor.y, mCursor.y);
}

void TerminalEngine::ClearLineToCursor(void)
{
	FillMemory(Cur2Addr(mBuffer, mCursor.y, 1), mCursor.x, SPACE);
	FillMemory(Cur2Addr(mAttrBuffer, mCursor.y, 1), mCursor.x, 0);
	InvalidateRows(mCursor.y, mCursor.y);
}

void TerminalEngine::ClearScreenFromCursor(void)
{
	FillMemory(Cur2Addr(mBuffer, mCursor.y,1), mBufSZ.x*(mBufSZ.y-mCursor.y+1), SPACE);
	FillMemory(Cur2Addr(mAttrBuffer, mCursor.y,1), mBufSZ.x*(mBufSZ.y-mCursor.y+1), 0);
	InvalidateRows(mCursor.y, mBufSZ.y);
}

void TerminalEngine::ClearScreenToCursor(void)
{
	FillMemory(mBuffer, mBufSZ.x*mCursor.y, SPACE);
	FillMemory(mAttrBuffer, mBufSZ.x*mCursor.y, 0);
	InvalidateRows(1, mCursor.y);
}

void TerminalEngine::ClearScreen(void)
{
	Clear();
	InvalidateRect(mWindow, 0, TRUE);
}


void TerminalEngine::SetOrigin(int x, int y)
{
	int oldy;
	POINT oldorg;

	// Check range
	if(y<0) y=0;
	int maxOrg = mBufSZ.y*mCharHeight-mWindowHeight;
	if(y>maxOrg) y=maxOrg;

	// Get old origin
	GetWindowOrgEx(mHDC, &oldorg);
	oldy = oldorg.y;
	
	if(y != oldy)
	{
		// Update scrollbar
		SCROLLINFO scri = {	sizeof(SCROLLINFO), SIF_POS, 0,0,0, 0,0};
		scri.nPos = y;
		SetScrollInfo(mWindow, SB_VERT, &scri, TRUE);
	
		UpdateWindow(mWindow);
		
		// Set window to show right part of buffer
		HideCaret(mWindow);
		SetWindowOrgEx(mHDC, 0, y, NULL);
		
		// Scroll contents of window
		RECT r;
		GetClientRect(mWindow,(RECT*)&r);
		ScrollWindowEx(	mWindow, 0, oldy-y, 0,(const RECT*)&r,
					0,0, SW_ERASE|SW_INVALIDATE);
		ShowCaret(mWindow);
	}
}

void TerminalEngine::SetWindowSize(int x, int y)
{
	BOOL setSize = FALSE;
	int maxX = GetSystemMetrics(SM_CXSCREEN);
	int maxY = GetSystemMetrics(SM_CYSCREEN);
	
	// Check range
	if(x<0){x=0; setSize = TRUE;}
	if(x>maxX){x=maxX; setSize = TRUE;}

	if(y<0){y=0; setSize = TRUE;}
	if(y>maxY){y=maxY; setSize = TRUE;}

	// If size out of range, make sure window gets a valid size
	if(setSize) SetWindowPos(	mWindow,0,0,0,		// No x,y,z position
								x,					// width
								y,					// height
						SWP_NOMOVE|SWP_NOZORDER);	// window-positioning flags

	// Invalidate window if necessary.
	if(x > mWindowWidth)
	{
		RECT invRect = {mWindowWidth ,0, x, y};
		InvalidateRect(mWindow, &invRect, TRUE);
	}
	if(y > mWindowHeight)
	{
		RECT invRect = {0, mWindowHeight, x, y};
		InvalidateRect(mWindow, &invRect, TRUE);
	}
	
	// width of client area
	mWindowWidth = x;
	// height of client area 
	mWindowHeight = y;
	
	// Update size of scrollbar
	SCROLLINFO s = {sizeof(SCROLLINFO), SIF_RANGE,0,0,0,0,0};
	s.nMax = mBufSZ.y*mCharHeight-mWindowHeight;
	SetScrollInfo(mWindow, SB_VERT, &s, TRUE);

	// Correct origin to new size.
	s.fMask = SIF_POS;
	GetScrollInfo(mWindow, SB_VERT, &s);
	SetOrigin(0, s.nPos);
	AdjustCharOrigin();
}

void TerminalEngine::Redraw(RECT invRect)
{
	int outLen;
	int FirstRow, LastRow, CurRow;
	const int tmpBufSZ = 200;
	char tmpBuf[tmpBufSZ];
	AttributeWord CurAttr;
	RECT bounds = invRect;

	FirstRow = invRect.top/mCharHeight+1;
	LastRow = invRect.bottom/mCharHeight+1;

	if(FirstRow == LastRow)
		FirstRow = 1;

	for(CurRow=FirstRow;CurRow<=LastRow;CurRow++)
	{
		if(CurRow>mBufSZ.y) break;
		bounds.top = mCharHeight * (CurRow-1);
		bounds.bottom =	mCharHeight * CurRow;
		while(BuildPrintable(CurRow, &bounds, tmpBuf, tmpBufSZ, &outLen, &CurAttr))
		{
			if(CurAttr&BOLD && !(CurAttr&UNDERSCORE))
				SelectObject(mHDC, boldFont);
			else if(!(CurAttr&BOLD) && CurAttr&UNDERSCORE)
				SelectObject(mHDC, uscoreFont);
			else if(CurAttr&BOLD && CurAttr&UNDERSCORE)
				SelectObject(mHDC, bolduscoreFont);
			else
				SelectObject(mHDC, stdFont);

			if(CurAttr&REVERSE || CurAttr&BLINK)
			{
				SetTextColor(mHDC, mBkColor);
				SetBkColor(mHDC, mTextColor);
			}
			else
  			{
				SetTextColor(mHDC, mTextColor);
				SetBkColor(mHDC, mBkColor);
			}

			::DrawText(mHDC, tmpBuf, outLen, &bounds, DT_NOPREFIX);
		}
	}
	UpdateCaret();
}

// ---------------------------------------------------------------------------
//		SetAttribute
// ---------------------------------------------------------------------------
void
TerminalEngine::SetAttribute(AttributeWord attr)
{
	mCurrentAttr |= attr;
}

// ---------------------------------------------------------------------------
//		ClearAttribute
// ---------------------------------------------------------------------------
void
TerminalEngine::ClearAttribute(AttributeWord attr)
{
	mCurrentAttr ^= attr & mCurrentAttr;
}

BOOL TerminalEngine::BuildPrintable(int curRow, RECT *bounds, char *Buf,
									int maxBufLen, int *BufLen,
									AttributeWord *Attr)
{
	static int lastRow = 0, LastCol = 0;
	static BOOL RowDone = FALSE;
	int CurCol;

	if(curRow != lastRow)
	{
		lastRow	= curRow;
		LastCol = CurCol = 1;
		RowDone = FALSE;
	}
	else
	{
		CurCol = LastCol;
	}

	*BufLen = 0;
	FillMemory(Buf, maxBufLen, SPACE);

	bounds->left = (CurCol-1)*mCharWidth;
	bounds->right = mWindowWidth;
	
	if(RowDone || bounds->left >= bounds->right)
	{
		bounds->left = 0;
		bounds->right = 0;
		lastRow = 0;
		return FALSE;
	}

	
	*Attr = *LCtoAddr(mAttrBuffer, curRow, CurCol);
	while(*Attr == (AttributeWord)*LCtoAddr(mAttrBuffer, curRow, CurCol))
	{
		if(CurCol > mBufSZ.x) {RowDone = TRUE; break;}
		Buf[*BufLen] = *LCtoAddr(mBuffer, curRow, CurCol);
		CurCol++;

		(*BufLen)++;
		if(*BufLen == maxBufLen) break;
	}
	LastCol = CurCol;
	return TRUE;
}

void TerminalEngine::InvalidateRows(unsigned int topRow, unsigned int bottomRow)
{
	// Calculate compensation because origin is not always on char boundaries.
	POINT oldorg;
	GetWindowOrgEx(mHDC, &oldorg);
	int orgDiff = oldorg.y - (mOrigin.y-1)*mCharHeight;

	// Calculate invalid rectangle.
	RECT invRect = {0,0,0,0};
	invRect.right = mWindowWidth;
	invRect.top = mCharHeight*(topRow-1)- orgDiff;
	invRect.bottom = mCharHeight*bottomRow - orgDiff;
	
	InvalidateRect(mWindow, &invRect, TRUE);
}

void TerminalEngine::UpdateCaret(void)
{
	int x = mCursor.x, y = mCursor.y;
	LCtoBC(&y,&x);
	SetCaretPos(mCharWidth*(x-1), mCharHeight*(y-1));
}


int TerminalEngine::GetWindowSize(int* Width, int* Height)
{
	*Width = mWindowWidth;
	*Height = mWindowHeight;
	return 0;
}

int TerminalEngine::GetCharWindowSize(int *Cols, int* Rows)
{
	GetWindowSize(Cols, Rows);
	*Cols = (*Cols)/mCharWidth;
	*Rows = (*Rows)/mCharHeight;
	return 0;
}

// Logical Coordinates to Buffer Coordinates.
int TerminalEngine::LCtoBC(int* Row, int* Col)
{
	*Row += mOrigin.y-1;
	*Col += mOrigin.x-1;
	return 0;
}

char* TerminalEngine::LCtoAddr(void* BaseAddr, int row, int col)
{
	return (char*)BaseAddr+mBufSZ.x*(row-1)+col-1;
}

char* TerminalEngine::Cur2Addr(void* BaseAddr, int row, int col)
{
	LCtoBC(&row, &col);
	return (char*)BaseAddr+mBufSZ.x*(row-1)+col-1;
}


void TerminalEngine::SetOriginRelative(int How)
{
	int nPos;
	switch (How)
	{
	case SB_LINEUP:
		nPos = -mCharHeight;
		break;
	case SB_LINEDOWN:
		nPos = mCharHeight;
		break;
	
	case SB_PAGEUP:
		nPos = -mWindowHeight;
		break;
 	case SB_PAGEDOWN:
		nPos = mWindowHeight;
		break;
	}
	// Get old origin
	POINT oldorg;
	GetWindowOrgEx(mHDC, &oldorg);
	SetOrigin(0, oldorg.y+nPos);
}
