/////////////////////////////////////////////////////////////////////////////
// COutWnd
// outwnd.cpp : implementation file
//

#include "stdafx.h"
#include "outwnd.h"
#include "textfrmt.h"
#include <stdarg.h>
#include <stdio.h>
#include "gmud32.h"
#include "gmud32vw.h"


#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

#define NUMLINES 500
#define NUMSCROLL 10

BEGIN_MESSAGE_MAP(COutWnd, CWnd)
	//{{AFX_MSG_MAP(COutWnd)
	ON_WM_CREATE()
	ON_WM_PAINT()
	ON_WM_VSCROLL()
	ON_WM_SIZE()
	ON_WM_ERASEBKGND()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEMOVE()
	ON_WM_TIMER()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

#define DIM_RGB_VALUE 0x80
#define MED_RGB_VALUE 0xC0

LONG colormap[16]=
{
	RGB(0,0,0),												// black
	RGB(DIM_RGB_VALUE,0,0),									// red
	RGB(0,DIM_RGB_VALUE,0),									// green
	RGB(DIM_RGB_VALUE,DIM_RGB_VALUE,0),						// yellow
	RGB(0,0,DIM_RGB_VALUE),									// blue
	RGB(DIM_RGB_VALUE,0,DIM_RGB_VALUE),						// magenta
	RGB(0,DIM_RGB_VALUE,DIM_RGB_VALUE), 					// cyan
	RGB(MED_RGB_VALUE,MED_RGB_VALUE,MED_RGB_VALUE),			// dim white
															// bright colors
	RGB(DIM_RGB_VALUE,DIM_RGB_VALUE,DIM_RGB_VALUE),			// bright black
	RGB(255,0,0),											// bright red
	RGB(0,255,0),											// bright green
	RGB(255,255,0),											// bright yellow
	RGB(0,0,255),											// bright blue
	RGB(255,0,255),											// bright magenta
	RGB(0,255,255), 										// bright cyan
	RGB(255,255,255)										// bright white
};

#define new DEBUG_NEW

/////////////////////////////////////////////////////////////////////////////
// COutWnd message handlers

COutWnd::COutWnd()
{
	m_iOffset=0;
	m_iFontHeight=0;
    m_bLastcr=TRUE;
	AnsiReset();
	m_nRow=0;
	m_nCol=0;
	m_nSavedCol=0;
	m_nSavedRow=0;
	m_bNextNew=TRUE;
	m_ptUp=m_ptDown=CPoint(0,0);
	m_bUnPauseWhenSelectionDone=FALSE;
  m_iMaxLines=10000;
}

COutWnd::~COutWnd()
{															   
}

int COutWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CWnd::OnCreate(lpCreateStruct) == -1)
		return -1;
	SetScrollRange(SB_VERT,0,NUMLINES);
	SetScrollPos(SB_VERT,NUMLINES-m_iOffset,TRUE);

	return 0;
}


int COutWnd::GetLineHeight()
{
	ASSERT(m_iFontHeight);
	return m_iFontHeight;
}

int COutWnd::CalcFontHeight()
{
	CClientDC dc(this);
	CFont f;
	f.CreateFontIndirect(&m_LogFont);
	CFont *oldfont = dc.SelectObject(&f);
    CSize size=dc.GetTextExtent("ABCDEFGHIJKLMNOPQRSTUVWXyZ",26);
	int height=size.cy;
	m_iCharWidth=size.cx/26;
    dc.SelectObject(oldfont);
	return height;
}

COutWnd::ScrollUp(int numlines /* = 1 */)
{   
	CRect r;
	GetClientRect(&r);
	ScrollWindow(0,0-GetLineHeight()*numlines,&r,&r);
	return TRUE;
}

COutWnd::NewFont(LPLOGFONT lf)
{
	ASSERT_VALID(this);
	memcpy(&m_LogFont,lf,sizeof(LOGFONT));
	m_iFontHeight=CalcFontHeight();
	Invalidate();
	return TRUE;
}

void COutWnd::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	ASSERT_VALID(this);
	CWnd::OnVScroll(nSBCode, nPos, pScrollBar);
	
	CRect clientrect;
	GetClientRect(&clientrect);
	
	int numvislines=clientrect.Height()/GetLineHeight();
	int numlines=m_Lines.GetCount();
	int numnonvislines=numlines-numvislines;
	
	if(numnonvislines<1)
		return;

	int pos = 	(int)((int)NUMLINES-(int)nPos);
	int newoffset=m_iOffset;
	switch(nSBCode)
	{
		case SB_BOTTOM:
			newoffset=numnonvislines;
			break;
		case SB_ENDSCROLL:
			break;
		case SB_LINEDOWN:
			newoffset=m_iOffset-1;
			break;
		case SB_LINEUP:
			newoffset=m_iOffset+1;
			break;
		case SB_PAGEDOWN:
			newoffset=m_iOffset-numvislines;
			break;
		case SB_PAGEUP:
			newoffset=m_iOffset+numvislines;
			break;
		case SB_THUMBPOSITION:
			newoffset=pos;
			break;
		case SB_THUMBTRACK:
			newoffset=pos;
			break;
		case SB_TOP:
			newoffset=0;
			break;
		default:
			ASSERT(0);
	}
	if(newoffset<0)
		newoffset=0;
	if(newoffset>(int)numnonvislines)
		newoffset=numnonvislines;
		
	int numlinesmoved=m_iOffset-newoffset;
	
	if(abs(numlinesmoved)<numvislines)
		ScrollUp(numlinesmoved);
	else
		Invalidate(TRUE);
		
	m_iOffset=newoffset;
	SetScrollPos(SB_VERT,NUMLINES-m_iOffset,TRUE);
}

void COutWnd::OnSize(UINT nType, int cx, int cy)
{
	CWnd::OnSize(nType, cx, cy);
	
	SetScrollRange(SB_VERT,0,NUMLINES,FALSE);
	SetScrollPos(SB_VERT,NUMLINES-m_iOffset,TRUE);
	CalcFontHeight();
	m_nCols = cx/m_iCharWidth;
}

BOOL COutWnd::OnEraseBkgnd(CDC* pDC) 
{
	CMudApp *pApp = (CMudApp *)AfxGetApp();
	CBrush wbr(pApp->m_colorBackGround);
	CRect r;
	pDC->GetClipBox(&r);
	pDC->FillRect(&r,&wbr);
	return TRUE;
}

void COutWnd::AnsiReset()
{
	m_iFGIndex=0;
	m_bBold=FALSE;
	m_AnsiColorForeGround = RGB(MED_RGB_VALUE,MED_RGB_VALUE,MED_RGB_VALUE);
	m_AnsiColorBackGround = RGB(0,0,0);
}

COutWnd::PutString(char * minstr)
{
//	return VTPutString(minstr);
	if(!*minstr){TRACE("\nBlank String in putstring.");delete minstr;return TRUE;}
    char *cstr = new char[BUFFSIZE];
	const unsigned char *instr=(const unsigned char *)minstr;
	char *str=cstr;
	int toggle=0,nCharat=0,nMaxChars=80;
	if(GetApp()->m_bScreenWrap)
		nMaxChars=m_nCols;
	CString sLeftOver;
	if(!m_bLastcr)	// the last line didn't end with a carriage return.
	{
		sLeftOver=m_Lines.GetHead();
		instr=(const unsigned char *)(const char *)sLeftOver;
		m_Lines.RemoveHead();
		InvalidateLine(0);
	}
	const unsigned char *pLastBrk=instr;
	const unsigned char *pLastBroken=instr;
	while(1)    // strip out carriage returns and convert to newlines.
	{
		int nChars = 1;
		if( !*instr || *instr == '\r' || *instr == '\n' || nCharat >= nMaxChars) // carriage return
		{
			if(GetApp()->m_bWordWrap && nCharat>=nMaxChars && pLastBrk!=pLastBroken)
			{
				str-=(instr-pLastBrk);
				instr=pLastBrk;
			}
			*str=0;
			if((*instr=='\r'&&*(instr+1)=='\n')||(*instr=='\n'&&*(instr+1)=='\r'))
				instr++;
			if(m_bLastcr)	// we're not joining a line
				toggle++;	// so scroll
			if(*instr)
			{
				instr++;
				m_bLastcr=TRUE;	// we're not at the EOL prematurely, we WANT to scroll
			}
			else if(instr<(const unsigned char *)minstr || instr>((const unsigned char *)minstr)+BUFFSIZE) // were we working on the leftover string?
			{
				instr=(const unsigned char *)minstr;	// now do the original input string
				pLastBrk=pLastBroken=instr;
				continue;
			}
			m_Lines.AddHead(cstr);						// add string to line list
      while(m_Lines.GetCount()>m_iMaxLines)
        m_Lines.RemoveTail();
			str=cstr;										// reset dest string
			if(toggle==NUMSCROLL)							// scroll up window
			{
				ScrollUp(toggle);
				UpdateWindow();
				toggle=0;
			}
			if(!*instr)
				break;
			if(nCharat>=nMaxChars ) // this line was split, and is going to be wrapped.
			{
				if(GetApp()->m_bIndent)
				{
					*str++=' ';
					*str++=' ';
				}
				if(!GetApp()->m_bWordWrap)
					instr--;
			}
			nCharat=0;										// reset char count
			pLastBrk=pLastBroken=instr;
		}
		else if(isprint(*instr)||(*instr == 27&&(!GetApp()->m_bAnsi||(nChars=MyAnsiNext((const char *)instr))==1)))
		{	// this character will be printed on screen
			if(*instr==' ' || *instr== '\t')
				pLastBrk= instr;
			*str++=*instr++;
			nCharat++;
		}
		else if(*instr=='\t')
		{
			int iNum=8-(nCharat%8);
			for(int loop=0;loop<iNum;loop++)
			{
				*str++=' ';
				nCharat++;
			}
			instr++;
		}
		else if(*instr == 27)
		{
			ASSERT(GetApp()->m_bAnsi && nChars!=1);
			unsigned const char* pAnsiCode=(instr+nChars-1);
			switch(*pAnsiCode)
			{
				case 'K':					// line erase functions
					switch(*(pAnsiCode-1))
					{	
						case '0':
						case '[':
							*str=0;
							orignull=cstr;
							break;
						case '1':{
							LPSTR pStr=cstr;
							while(pStr<str)
								*pStr++=' ';
							break;}
						case '2':
							orignull=str=cstr;
							*str=0;
							break;
					}
				default:
					instr+=nChars;
					break;
				case 'm':
					for(int loop=0;loop<nChars;loop++)
						*str++=*instr++;
					break;
			}
		}
		else	// we're not copying a character
		{
			if(*instr == 7 && GetApp()->m_bBell) // bell character
				MessageBeep(0xFFFFFFFF);
			instr++;		// skip over this character
		}
	}
	m_bLastcr=(*(instr-1)=='\r' || *(instr-1)=='\n');
	if(toggle)
	{
		ScrollUp(toggle);
		UpdateWindow();
	}
	delete cstr;
	delete minstr;
	return TRUE;
}

// line 0 is bottom of screen
COutWnd::InvalidateLine(int nLine)
{
	CRect r;
	GetClientRect(&r);
	int nBottom=r.bottom-(GetLineHeight()*nLine);
	CRect wr(r.left,nBottom-GetLineHeight(),r.right,nBottom);//now erase the last line on the screen.
	InvalidateRect(&wr);
	return TRUE;
}

void COutWnd::OnEnterPressed()
{
	m_bLastcr=TRUE;
	if(m_nRow==0)
		m_bNextNew=TRUE;
}

void COutWnd::OnPaint()
{
	CPaintDC dc(this); // device context for painting

	int lineheight=GetLineHeight();
	CRect r;
	GetClientRect(&r);

	CFont f;
	f.CreateFontIndirect(&m_LogFont);
	CFont *oldfont = dc.SelectObject(&f);
	int numvislines=r.Height()/GetLineHeight();

	CMudApp *pApp = (CMudApp *)AfxGetApp();

	dc.SetTextColor(m_AnsiColorForeGround);
	dc.SetBkColor(m_AnsiColorBackGround);
  dc.SetTextAlign(TA_BOTTOM);
	BOOL bInSelection=FALSE;
  int iTopLine=min(m_Lines.GetCount()-1,m_iOffset+numvislines);
  POSITION pos =NULL;
  if(m_Lines.GetCount()>0)
    pos= m_Lines.FindIndex(iTopLine);
	for(int loop=iTopLine;loop>=m_iOffset && pos !=NULL;loop--)
	{
    
		int textbottom=r.Height()-lineheight*(loop-m_iOffset);
		CRect wr(r.left,textbottom-lineheight,r.right,textbottom);
		CString sLine=m_Lines.GetPrev(pos);
    if(dc.RectVisible(&wr))
		{	
      LPCSTR pOrigStr;
			LPCSTR pStr = pOrigStr = sLine;
      
			CString outstr;
			int leftoffset=r.left;

			int x=0;
			bInSelection=IsInSelection(x,loop);
			while(*pStr)
			{
				outstr="";
				int num=1;	// 1 if not ansi character, >1 if ansi character sequence
				while(*pStr && ((num = MyAnsiNext(pStr)) ==1) && IsInSelection(x,loop)== bInSelection)
				{
					outstr+=*pStr;
					pStr++;
					x++;
				}
				if(outstr.GetLength() && dc.RectVisible(&wr))
				{
				  if(bInSelection)	// the text we just went over was in the selection.
				  {
					  dc.SetBkColor(pApp->m_colorForeGround);
					  dc.SetTextColor(pApp->m_colorBackGround);
				  }
				  else
				  {
					  dc.SetTextColor(m_AnsiColorForeGround); 
					  dc.SetBkColor(m_AnsiColorBackGround);
				  }
					dc.TextOut(leftoffset,textbottom,outstr);
					leftoffset+=outstr.GetLength()*m_iCharWidth;
				}
				bInSelection=IsInSelection(x,loop);
				if(*pStr && num!=1)
				{
					char ansicode = *(pStr+(num-1));
					switch(ansicode)
					{
						case 'm':
							{
								pStr+=2;
								num-=2;
								while(num)
								{
									int value = atoi(pStr);
									switch(value)
									{
										case 0:			// reset
											AnsiReset();
											dc.SetTextColor(m_AnsiColorForeGround);
											dc.SetBkColor(m_AnsiColorBackGround);
											break;
										case 1:			// BOLD
										{
											m_bBold=TRUE;
											int cindex = m_iFGIndex+(m_bBold?8:0);
											m_AnsiColorForeGround=colormap[cindex];
											dc.SetTextColor(m_AnsiColorForeGround);
											num--;
											pStr++;
											break;
										}
										case 4:			// underline
										case 5:			// blink
										case 7:			// reverse
										case 8:			// invisible
											num--;
											pStr++;
											break;
										case 30:case 31:case 32:case 33:case 34:case 35:case 36:case 37:
										{
											num-=2;
											// set the foreground color
											m_iFGIndex=value-30;
											int cindex = m_iFGIndex+(m_bBold?8:0);
											m_AnsiColorForeGround=colormap[cindex];
											dc.SetTextColor(m_AnsiColorForeGround);
											pStr+=2;
											break;
										}
										case 40:case 41:case 42:case 43:case 44:case 45:case 46:case 47:
											num-=2;
											// set the Background color
											m_AnsiColorBackGround=colormap[value-40];
											dc.SetBkColor(m_AnsiColorBackGround);
											pStr+=2;
											break;
										default:
											TRACE("\nError: Unknown ansi 'm' numeric comand.");
											num--;
											pStr++;
											break;
									}
									num--;
									pStr++;
								}
							}
							break;
						default:	// unknown
							TRACE("\nUnknown ansi escape, hiding it.");
						case 0: // truncated sequence
						case 'A':case'B':case 'C':case 'D':case'R':case 'n':case'H':case'f':case's':case 'u':case'J':case'k':
							// not yet supported
							pStr+=num;
							break;
					}
				}
			}
		}
	}
	dc.SelectObject(oldfont);
}

BOOL COutWnd::IsInSelection(int x,int y)
{

	if(m_ptUp==m_ptDown)
		return FALSE;

	CPoint minpt;
	CPoint maxpt;

	if(m_ptDown.y<m_ptUp.y)
	{
		minpt=m_ptDown;
		maxpt=m_ptUp;
	}
	else if(m_ptDown.y>m_ptUp.y)
	{
		minpt=m_ptUp;
		maxpt=m_ptDown;
	}
	else if(m_ptDown.x<m_ptUp.x)
	{
		minpt=m_ptDown;
		maxpt=m_ptUp;
	}
	else
	{
		minpt=m_ptUp;
		maxpt=m_ptDown;
	}

	if(y > minpt.y && y < maxpt.y)
		return TRUE;
	if(y == minpt.y)
	{
		if(y == maxpt.y)
		{
			if(x >=minpt.x && x < maxpt.x)
				return TRUE;
			else
				return FALSE;
		}
		if(x< minpt.x)
			return TRUE;
	}
	if(y == maxpt.y && x>=maxpt.x)
		return TRUE;
	return FALSE;
}

CPoint COutWnd::PointToChar( CPoint pt)
{
	CRect r;
	GetClientRect(&r);

	pt.x= pt.x/m_iCharWidth;
	pt.y= (r.bottom-pt.y)/GetLineHeight();
	pt.y+=m_iOffset;
	return pt;
}

void COutWnd::OnLButtonDown(UINT nFlags, CPoint point) 
{
	CWnd::OnLButtonDown(nFlags, point);

	m_bUnPauseWhenSelectionDone = !((CMudView *) GetParent())->Pause(TRUE);

	m_ptUp=m_ptDown = PointToChar(point);
	TRACE("\nDown: (%4d,%4d)",m_ptDown.x,m_ptDown.y);
	SetCapture();
	SetTimer(1,10,0);
}

void COutWnd::OnLButtonUp(UINT nFlags, CPoint point) 
{
	KillTimer(1);
	CWnd::OnLButtonUp(nFlags, point);
	if(GetCapture()!=this)
		return;
	ReleaseCapture();
	m_ptUp = PointToChar(point);

	TRACE("\n  Up: (%4d,%4d)",m_ptUp.x,m_ptUp.y);
	// prepare clipboard
	if(!OpenClipboard())
	{
		AfxMessageBox("Unable to open clipboard");
		Invalidate();
		m_ptUp=m_ptDown=CPoint(0,0);
		return;
	}
	EmptyClipboard();

	int iSize=0;
	// count size of text;

	m_ptUp.x=max(0,m_ptUp.x);
	m_ptDown.x=max(0,m_ptDown.x);

	for(int loop=max(0,min(m_ptUp.y,m_ptDown.y));loop<=max(m_ptUp.y,m_ptDown.y) && loop<m_Lines.GetCount();loop++)
		iSize+=m_Lines.GetAt(m_Lines.FindIndex(loop)).GetLength()+3;

	// Allocate a global memory object for the text.
    HGLOBAL hglbCopy = GlobalAlloc(GMEM_DDESHARE,iSize);
    if (hglbCopy == NULL) {
		AfxMessageBox("Couldn't allocate memory to copy to clipboard");
        CloseClipboard();
		Invalidate();
		m_ptUp=m_ptDown=CPoint(0,0);
        return;
    }

    // Lock the handle and copy the text to the buffer.
	LPSTR pBuf;
    LPSTR pCopy = pBuf=(LPSTR)GlobalLock(hglbCopy);
	for(loop=min(m_Lines.GetCount()-1,max(m_ptUp.y,m_ptDown.y));loop>=max(0,min(m_ptUp.y,m_ptDown.y));loop--)
	{
    CString s=m_Lines.GetAt(m_Lines.FindIndex(loop));
		if(loop==max(m_ptUp.y,m_ptDown.y))	// first line, so defer to first char.
		{
			int firstchar;
			int lastchar=s.GetLength();
			if(m_ptUp.y > m_ptDown.y)
				firstchar=m_ptUp.x;
			else
				firstchar=m_ptDown.x;

			if(m_ptUp.y==m_ptDown.y)
			{
				firstchar=min(m_ptDown.x,m_ptUp.x);
				lastchar=max(m_ptDown.x,m_ptUp.x);
				lastchar=min(lastchar,s.GetLength());
			}
			int length=lastchar-firstchar;
			pCopy+=Ansimemcpy(pCopy,(LPCSTR)s,firstchar,length);
			if(m_ptUp.y!=m_ptDown.y)
			{
				*pCopy='\n';
				pCopy++;
			}
		}
		else if(loop==min(m_ptUp.y,m_ptDown.y))	// last line, so copy only up to last char.
		{
			int lastchar;
			if(m_ptUp.y < m_ptDown.y)
				lastchar=m_ptUp.x;
			else
				lastchar=m_ptDown.x;
			lastchar=min(lastchar,s.GetLength());
			pCopy+=Ansimemcpy(pCopy,s,0,lastchar);
		}
		else
		{
			pCopy+=Ansimemcpy(pCopy,s,0,s.GetLength());
			*pCopy='\n';
			pCopy++;
		}
	}

    *pCopy=0;    // null character
    GlobalUnlock(hglbCopy);

    // Place the handle on the clipboard.
    SetClipboardData(CF_TEXT, hglbCopy);

	
	if(!CloseClipboard())
	{
		AfxMessageBox("Unable to close clipboard");
	}
	m_ptUp=m_ptDown=CPoint(0,0);
	Invalidate();

	if(m_bUnPauseWhenSelectionDone)
		((CMudView *) GetParent())->Pause(FALSE);
	m_bUnPauseWhenSelectionDone=FALSE;
}

void COutWnd::OnMouseMove(UINT nFlags, CPoint point) 
{	
	CWnd::OnMouseMove(nFlags, point);
	if(GetCapture()==this)
	{
		CPoint ptOldUp=m_ptUp;
		m_ptUp=PointToChar(point);

		for(int loop=min(m_ptUp.y,ptOldUp.y);loop<=max(m_ptUp.y,ptOldUp.y);loop++)
		{
			InvalidateLine(loop-m_iOffset);
		}
	}
}



void COutWnd::OnTimer(UINT nIDEvent) 
{
	CWnd::OnTimer(nIDEvent);

	if(m_ptUp.y < m_iOffset)
		OnVScroll(SB_LINEDOWN,0,0);

	CRect r;
	GetClientRect(&r);
	int numvislines=r.Height()/GetLineHeight();

	if(m_ptUp.y >m_iOffset+numvislines)
		OnVScroll(SB_LINEUP,0,0);
}