#include "stdafx.h"
#include "HexEdit.h"

CHexEdit::CHexEdit()
{
	m_pData			= NULL;
	m_nLength		= 0x00;
	m_nCurAddress	= 0x00;
	m_nTopIndex		= 0x00;
	m_nOffHex		= 0x00;
	m_nOffAscii		= 0x00;
	m_nLineHeight	= 0x00;
	m_nNullWidth	= 0x00;
	m_nLpp			= 0x00;
	m_bHalfPage		= FALSE;
	m_bUpdate		= FALSE;
	m_ctrlHex		= CTRL_NONE;
	m_clsFont.CreateFont(-12, 0,0,0,0,0,0,0,0,0,0,0,0, _T("Courier New"));
}

CHexEdit::~CHexEdit()
{
	if(m_pData != NULL)
		free(m_pData);
}

BEGIN_MESSAGE_MAP(CHexEdit, CEdit)
	ON_WM_PAINT()
	ON_WM_CHAR()
	ON_WM_LBUTTONDOWN()
	ON_WM_ERASEBKGND()
	ON_WM_KEYDOWN()
	ON_WM_VSCROLL()
END_MESSAGE_MAP()


void CHexEdit::SetData(BYTE* pData, UINT nLength)
{
	if(m_pData != NULL)
		free(m_pData);
	
	m_pData = (LPBYTE) malloc(nLength);
	memcpy(m_pData, pData, nLength);

	m_nLength		= nLength;
	m_nCurAddress	= 0;
	m_pctEditPos.x	= m_pctEditPos.y = 0;
	// m_currentMode = EDIT_HIGH;
	m_nTopIndex		= 0;
	m_bUpdate		= TRUE;
}

UINT CHexEdit::GetData(BYTE* pData, UINT nLength)
{
	memcpy_s(pData, nLength, m_pData, min(nLength, m_nLength));
	return m_nLength;
}

TCHAR hextable[16] = {_T('0'),_T('1'),_T('2'),_T('3'),_T('4'),_T('5'),_T('6'),_T('7'),_T('8'),_T('9'),_T('A'),_T('B'),_T('C'),_T('D'),_T('E'),_T('F')};
#define TOHEX(a, b)	{*b++ = hextable[a >> 4];*b++ = hextable[a&0xf];}

#define CHAR_PER_RAW		0x08
void CHexEdit::DrawHex(CDC* pDc, CRect rcDC, UINT nHeight)
{
	if(pDc != NULL)
	{
		rcDC.TopLeft().x = m_nOffHex;
		TCHAR szData[MAX_PATH];
		for(UINT i = m_nTopIndex; (UINT)(i < m_nLength) && ((UINT)(rcDC.TopLeft().y) < nHeight);)
		{
			TCHAR* p = &szData[0];
			int	 n = 0;
			for(n = 0; (n < CHAR_PER_RAW) && (i < m_nLength); n++)
			{
				TOHEX(m_pData[i], p);
				*p++ = _T(' ');
				i++;
			}
			while(n < CHAR_PER_RAW)
			{
				*p++ = _T(' ');	*p++ = _T(' ');	*p++ = _T(' ');
				n++;
			}

			pDc->DrawText(szData, CHAR_PER_RAW * 3, rcDC, DT_LEFT|DT_TOP|DT_SINGLELINE|DT_NOPREFIX);
			rcDC.TopLeft().y += m_nLineHeight;
		}
	}
}

void CHexEdit::DrawAscii(CDC* pDc, CRect rcDC, UINT nHeight)
{
	rcDC.TopLeft().x = m_nOffAscii;
	TCHAR szData[MAX_PATH];
	for(UINT	 i = m_nTopIndex; (i < m_nLength) && ((UINT)(rcDC.TopLeft().y) < nHeight);)
	{
		TCHAR* p = &szData[0];
		int	 n = 0;
		for(n = 0; (n < CHAR_PER_RAW) && (i < m_nLength); n++)
		{
			*p++ = isprint(m_pData[i]) ? m_pData[i] : _T('.');
			i++;
		}
		pDc->DrawText(szData, n, rcDC, DT_LEFT|DT_TOP|DT_SINGLELINE|DT_NOPREFIX);
		rcDC.TopLeft().y += m_nLineHeight;
	}
}

void CHexEdit::UpdateScrollbars()
{
	SCROLLINFO si;

	si.cbSize = sizeof(SCROLLINFO);
	si.fMask = SIF_ALL;
	si.nMin = 0;
	si.nMax = (m_nLength / CHAR_PER_RAW) - 1;
	si.nPage = m_nLpp;
	si.nPos = m_nTopIndex / CHAR_PER_RAW;

	::SetScrollInfo(this->m_hWnd, SB_VERT, &si, TRUE);
	if(si.nMax > (int)si.nPage)
		::EnableScrollBar(this->m_hWnd, SB_VERT, ESB_ENABLE_BOTH);
}

void CHexEdit::OnPaint() 
{
	CPaintDC pDC(this); // device context for painting
	TCHAR szData[MAX_PATH];

	CRect rcWnd;
	GetClientRect(rcWnd);
	
	CDC	pDcMem;
	pDcMem.CreateCompatibleDC(CDC::FromHandle(pDC.m_ps.hdc));

	CBitmap bmDraw;
	bmDraw.CreateCompatibleBitmap(CDC::FromHandle(pDC.m_ps.hdc), rcWnd.Width(), rcWnd.Height());
	pDcMem.SelectObject(bmDraw);
	pDcMem.SelectObject(m_clsFont);

	CBrush brBkg;
	brBkg.CreateSolidBrush(RGB(0xff,0xff,0xff));
	pDcMem.FillRect(rcWnd, &brBkg);


	if(m_bUpdate)
	{
		pDcMem.GetCharWidth(_T('0'), _T('0'), (LPINT)&m_nNullWidth);
		CSize sz = pDC.GetTextExtent(_T("0"), 1);
		m_nLineHeight = sz.cy;
		
		m_nOffHex	= m_nNullWidth * 9;
		m_nOffAscii	= m_nOffHex;
		m_nOffAscii += CHAR_PER_RAW * 3 * m_nNullWidth;

		m_nLpp = rcWnd.Height() / m_nLineHeight;
		m_bHalfPage = FALSE;
		if(m_nLpp * CHAR_PER_RAW > m_nLength)
		{
			m_nLpp = (m_nLength + (CHAR_PER_RAW / 2)) / CHAR_PER_RAW ;
			if(m_nLength % CHAR_PER_RAW != 0)
			{
				m_bHalfPage = TRUE;
				m_nLpp++;
			}
		}
		m_bUpdate = FALSE;
		UpdateScrollbars();
	}

	UINT nHeight = rcWnd.Height() / ((m_nLineHeight == 0x00) ? 0x01 : m_nLineHeight);
	nHeight *= (m_nLineHeight == 0x00) ? 0x01 : m_nLineHeight;

	// draw the address
	CRect rcDC = rcWnd;
	rcDC.TopLeft().x = 0x00;
	for(UINT i = m_nTopIndex; (i < m_nLength) && ((UINT)(rcDC.TopLeft().y) < nHeight); i+= CHAR_PER_RAW)
	{
		_stprintf_s(szData, MAX_PATH, _T("%08lX"), i);
		pDcMem.DrawText(szData, 0x08, rcDC, DT_LEFT|DT_TOP|DT_SINGLELINE|DT_NOPREFIX);
		rcDC.TopLeft().y += m_nLineHeight;
	}

	DrawHex(&pDcMem, rcWnd, nHeight);
	DrawAscii(&pDcMem, rcWnd, nHeight);


	pDC.BitBlt(0, 0, rcWnd.Width(), rcWnd.Height(), &pDcMem, 0, 0, SRCCOPY);
	pDcMem.DeleteDC();
}

void CHexEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{

	if(nChar != '\t' && m_pData != NULL)
	{
		switch(m_ctrlHex)
		{
			case CTRL_HEX_HIGH:
			case CTRL_HEX_LOW:
				if((nChar >= '0' && nChar <= '9') || (nChar >= 'a' && nChar <= 'f'))
				{
					UINT b = nChar - _T('0');
					if(b > 9) 
						b = 10 + nChar - _T('a');

					m_pData[m_nCurAddress] = (unsigned char)((m_pData[m_nCurAddress] & (m_ctrlHex == CTRL_HEX_HIGH ? 0x0F : 0xF0)) | (m_ctrlHex == CTRL_HEX_HIGH ? (b << 4) : b));
					Move(1,0);
				}
				break;
			case CTRL_TEXT:
			{
				m_pData[m_nCurAddress] = nChar;
				Move(1,0);
			}
			break;
		}
		RedrawWindow();
	}
}

void CHexEdit::OnLButtonDown(UINT nFlags, CPoint point) 
{
	SetFocus();
	if(!m_pData)
		return;

	CPoint pt = CalcPos(point.x, point.y);
	if(pt.x > -1)
	{
		m_pctEditPos = pt;
		pt.x *= m_nNullWidth;
		pt.y *= m_nLineHeight;
		
		if(pt.x == 0)
			CreateAddressCaret();
		else
			CreateEditCaret();

		SetCaretPos(pt);

	}
	ShowCaret();
}

void CHexEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	BOOL bShift = GetKeyState(VK_SHIFT) & 0x80000000;
	switch(nChar)
	{
		case VK_DOWN:	Move(0,1);	break;
		case VK_UP:		Move(0,-1); break;
		case VK_LEFT:	Move(-1,0); break;
		case VK_RIGHT:	Move(1,0);	break;
		case VK_PRIOR:
		{
			OnVScroll(SB_PAGEUP, 0, NULL);
			Move(0,0);
		}break;
	}
}

void CHexEdit::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
	switch(nSBCode)
	{
		case SB_LINEDOWN:
		{
			if(m_nTopIndex < m_nLength - m_nLpp * CHAR_PER_RAW)
				m_nTopIndex += CHAR_PER_RAW;
		}
		break;

		case SB_LINEUP:
		{
			if(m_nTopIndex > 0)
				m_nTopIndex -= CHAR_PER_RAW;
		}
		break;

		case SB_PAGEDOWN:
		{
			if(m_nTopIndex < m_nLength - m_nLpp * CHAR_PER_RAW)
			{
				m_nTopIndex += CHAR_PER_RAW * m_nLpp;
				if(m_nTopIndex > m_nLength - m_nLpp * CHAR_PER_RAW)
					m_nTopIndex = m_nLength - m_nLpp * CHAR_PER_RAW;
			}
		}
		break;

		case SB_PAGEUP:
		{
			if(m_nTopIndex > 0)
			{
				m_nTopIndex -= CHAR_PER_RAW * m_nLpp;
				if(m_nTopIndex < 0)
					m_nTopIndex = 0;
			}
		}
		break;

		case SB_THUMBTRACK:
			m_nTopIndex = nPos * CHAR_PER_RAW; break;
	}

	RedrawWindow();
	::SetScrollPos(this->m_hWnd, SB_VERT, m_nTopIndex / CHAR_PER_RAW, TRUE);
	RepositionCaret(m_nCurAddress);
}

BOOL CHexEdit::OnEraseBkgnd(CDC* pDC)
{
	return TRUE;
}

void CHexEdit::CreateEditCaret()
{
	DestroyCaret();
	CreateSolidCaret(m_nNullWidth, m_nLineHeight);
}

void CHexEdit::CreateAddressCaret()
{
	DestroyCaret();
	CreateSolidCaret(m_nNullWidth * 8 , m_nLineHeight);
}

void CHexEdit::RepositionCaret(int p)
{
	int y = (p - m_nTopIndex) / CHAR_PER_RAW;
	int x = (p - m_nTopIndex) % CHAR_PER_RAW;

	switch(m_ctrlHex)
	{
		case CTRL_NONE:
		{
			CreateAddressCaret();
			x = 0;
		}
		break;

		case CTRL_HEX_HIGH:
		{
			CreateEditCaret();
			x *= m_nNullWidth * 3;
			x += m_nOffHex;
		}
		break;

		case CTRL_HEX_LOW:
		{
			CreateEditCaret();
			x *= m_nNullWidth * 3;
			x += m_nNullWidth;
			x += m_nOffHex;
		}
		break;

		case CTRL_TEXT:
		{
			CreateEditCaret();
			x *= m_nNullWidth;
			x += m_nOffAscii;
		}
		break;
	}

	m_pctEditPos.x = x;
	m_pctEditPos.y = y * m_nLineHeight;

	CRect rc;
	GetClientRect(&rc);
	if(rc.PtInRect(m_pctEditPos))
	{
		SetCaretPos(m_pctEditPos);
		ShowCaret();
	}
}

void CHexEdit::Move(int x, int y)
{
	switch(m_ctrlHex)
	{
		case CTRL_HEX_HIGH:
		{
			if(x != 0)
				m_ctrlHex = CTRL_HEX_LOW;
			if(x == -1)
				m_nCurAddress --;
			m_nCurAddress += y* CHAR_PER_RAW;
		}break;

		case CTRL_HEX_LOW:
		{
			if(x != 0)
				m_ctrlHex = CTRL_HEX_HIGH;
			if(x == 1)
				m_nCurAddress++;
			m_nCurAddress += y* CHAR_PER_RAW;
		}break;

		case CTRL_TEXT:
		{
			m_nCurAddress += x;
			m_nCurAddress += y * CHAR_PER_RAW;
		}
		break;
	}

	if(m_nCurAddress < 0)
		m_nCurAddress = 0;

	if(m_nCurAddress >= m_nLength)
	{
		m_nCurAddress -= x;
		m_nCurAddress -= y * CHAR_PER_RAW;
	}

	if(m_nCurAddress < m_nTopIndex)
		OnVScroll(SB_LINEUP, 0, NULL);

	if(m_nCurAddress >= m_nTopIndex + m_nLpp * CHAR_PER_RAW)
		OnVScroll(SB_LINEDOWN, 0, NULL);


	RepositionCaret(m_nCurAddress);
}

CPoint CHexEdit::CalcPos(int x, int y)
{
	y /= m_nLineHeight;
	if(y < 0 || y > m_nLpp)
		return CPoint(-1, -1);

	if(y * CHAR_PER_RAW > m_nLength)
		return CPoint(-1, -1);

	x += m_nNullWidth;
	x /= m_nNullWidth;

	int xp;

	if(x <=  8)
	{
		m_ctrlHex = CTRL_NONE;
		return CPoint(-1, -1);
	}

	xp = (m_nOffHex  / m_nNullWidth) + CHAR_PER_RAW * 3;
	if(x < xp)
	{
		if(x % 3)
			x--;
		m_nCurAddress = m_nTopIndex + (CHAR_PER_RAW * y) + (x - (m_nOffHex  / m_nNullWidth)) / 3;
		m_ctrlHex =  ((x%3) & 0x01) ? CTRL_HEX_LOW : CTRL_HEX_HIGH;
		return CPoint(x, y);
	}

	xp = (m_nOffAscii  / m_nNullWidth) + CHAR_PER_RAW;
	if(x <= xp)
	{
		m_nCurAddress = m_nTopIndex + (CHAR_PER_RAW * y) + (x - (m_nOffAscii  / m_nNullWidth));
		m_ctrlHex = CTRL_TEXT;
		return CPoint(x, y);
	}

	return CPoint(-1,-1);
}
