#include "StdAfx.h"
#include "resource.h"
#include "DumpListCtrl.h"
#include "MemDumpDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


CDumpListCtrl::CDumpListCtrl()
	: m_DisplayMode(DD_WORD_VIEW)
	, m_nTopAddr(g_Config.m_nAdrDump)
	, m_nBottomAddr(0)
{
}


CDumpListCtrl::~CDumpListCtrl()
{
	g_Config.m_nAdrDump = m_nTopAddr;
}

BEGIN_MESSAGE_MAP(CDumpListCtrl, CMultiEditListCtrl)
	ON_WM_SIZE()
	ON_WM_VSCROLL()
	ON_WM_MOUSEWHEEL()
	ON_NOTIFY_REFLECT(LVN_KEYDOWN, &OnLvnKeydown)
END_MESSAGE_MAP()


TCHAR CDumpListCtrl::GetMemDumpByteAsANSI(BYTE byte)
{
	TCHAR ansi;

	if (byte < ' ')
	{
		ansi = _T(' ');
	}
	else if (byte >= 0x80)
	{
		ansi = TCHAR(koi8tbl[byte - 0x80]);
	}
	else
	{
		ansi = TCHAR(byte);
	}

	return ansi;
}

void CDumpListCtrl::SetDisplayMode(DUMP_DISPLAY_MODE mode)
{
	while (m_lockDraw.IsLocked());

	m_lockChangeMode.Lock();
	//сперва удалим всё
	DeleteAllItems();
	//потом удалим все столбцы
	CHeaderCtrl *pHeader = GetHeaderCtrl();
	int n = pHeader->GetItemCount();

	for (int i = 0; i < n; ++i)
	{
		DeleteColumn(0);
	}

	m_DisplayMode = mode;
	SetHeader();
	SetEditAddress();
	m_lockChangeMode.UnLock();
	AdjustRows(); //заполняем всё доступное пространство строками
	AdjustScroll();
}

void CDumpListCtrl::Init(DUMP_DISPLAY_MODE mode)
{
	SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER | LVS_EX_SNAPTOGRID);

	while (m_lockDraw.IsLocked());

	m_lockChangeMode.Lock();
	m_DisplayMode = mode;
	SetHeader();
	SetEditAddress();
	m_lockChangeMode.UnLock();
	AdjustRows(); //заполняем всё доступное пространство строками
	AdjustScroll();
}

void CDumpListCtrl::SetHeader()
{
	int nPixelW = GetDeviceCaps(GetDC()->GetSafeHdc(), LOGPIXELSX);
	CHeaderCtrl *pHeader = GetHeaderCtrl();
	HDITEM hditem;
	ZeroMemory(&hditem, sizeof(HDITEM));
	hditem.mask = HDI_FORMAT | HDI_WIDTH;
	hditem.fmt = HDF_CENTER | HDF_STRING | HDF_FIXEDWIDTH;
	hditem.cxy = MulDiv(COLUMN_WIDTH_ADDR, nPixelW, 96);

	switch (m_DisplayMode)
	{
		case DD_WORD_VIEW:
		{
			//добавляем столбцы
			InsertColumn(IDS_MEMORY_ADDR_WORD, _T("Адрес"), LVCFMT_CENTER | LVCFMT_FIXED_WIDTH, MulDiv(COLUMN_WIDTH_ADDR, nPixelW, 96));
			pHeader->SetItem(IDS_MEMORY_ADDR_WORD, &hditem); //почему-то зафиксировать ширину первого столбца можно только так

			for (int i = IDS_MEMORY_COLUMN1_WORD; i <= IDS_MEMORY_COLUMN4_WORD; ++i)
			{
				CString str;
				str.Format(_T("%02d"), (i - 1) * 2);
				InsertColumn(i, str, LVCFMT_CENTER | LVCFMT_FIXED_WIDTH, MulDiv(COLUMN_WIDTH_WORD, nPixelW, 96));
			}

			InsertColumn(IDS_MEMORY_ASCII_WORD, _T("01234567"), LVCFMT_CENTER | LVCFMT_FIXED_WIDTH, MulDiv(COLUMN_WIDTH_TXT, nPixelW, 96));
			break;
		}

		case DD_BYTE_VIEW:
		{
			//добавляем столбцы
			InsertColumn(IDS_MEMORY_ADDR_BYTE, _T("Адрес"), LVCFMT_CENTER | LVCFMT_FIXED_WIDTH, MulDiv(COLUMN_WIDTH_ADDR, nPixelW, 96));
			pHeader->SetItem(IDS_MEMORY_ADDR_BYTE, &hditem);

			for (int i = IDS_MEMORY_COLUMN1_BYTE; i <= IDS_MEMORY_COLUMN8_BYTE; ++i)
			{
				CString str;
				str.Format(_T("%02d"), (i - 1));
				InsertColumn(i, str, LVCFMT_CENTER | LVCFMT_FIXED_WIDTH, MulDiv(COLUMN_WIDTH_BYTE, nPixelW, 96));
			}

			InsertColumn(IDS_MEMORY_ASCII_BYTE, _T("01234567"), LVCFMT_CENTER | LVCFMT_FIXED_WIDTH, MulDiv(COLUMN_WIDTH_TXT, nPixelW, 96));
			break;
		}
	}
}

void CDumpListCtrl::InsertNewLine(int nNum)
{
	WORD nAddr = m_nTopAddr + nNum * 010;
	CString str = WordToOctString(nAddr) + _T(":");
	InsertItem(nNum, str);
	str = _T("");
	auto pDlg = reinterpret_cast<CMemDumpDlg *>(GetParent());

	switch (m_DisplayMode)
	{
		case DD_WORD_VIEW:
		{
			for (register int j = IDS_MEMORY_COLUMN1_WORD; j <= IDS_MEMORY_COLUMN4_WORD; ++j)
			{
				register WORD nVal = pDlg->GetDebugMemDumpWord(nAddr);
				SetItemWithModified(nVal, nNum, j);
				str += GetMemDumpByteAsANSI(LOBYTE(nVal));
				str += GetMemDumpByteAsANSI(HIBYTE(nVal));
				nAddr += 2;
			}

			SetItemWithModifiedASCII(str, nNum, IDS_MEMORY_ASCII_WORD);
			SetItemBkColor(GetSysColor(COLOR_BTNFACE), nNum, IDS_MEMORY_ADDR_WORD);
			EnableEdit(false, nNum, IDS_MEMORY_ADDR_WORD);
			EnableEdit(false, nNum, IDS_MEMORY_ASCII_WORD);
			break;
		}

		case DD_BYTE_VIEW:
		{
			for (register int j = IDS_MEMORY_COLUMN1_BYTE; j <= IDS_MEMORY_COLUMN8_BYTE; ++j)
			{
				register BYTE nVal = pDlg->GetDebugMemDumpByte(nAddr);
				SetItemWithModifiedByte(nVal, nNum, j);
				str += GetMemDumpByteAsANSI(nVal);
				nAddr++;
			}

			SetItemWithModifiedASCII(str, nNum, IDS_MEMORY_ASCII_BYTE);
			SetItemBkColor(GetSysColor(COLOR_BTNFACE), nNum, IDS_MEMORY_ADDR_BYTE);
			EnableEdit(false, nNum, IDS_MEMORY_ADDR_BYTE);
			EnableEdit(false, nNum, IDS_MEMORY_ASCII_BYTE);
			break;
		}
	}

	m_nBottomAddr = nAddr;
}

void CDumpListCtrl::AdjustRows()
{
	int nRows = GetItemCount();
	int n = GetCountPerPage();

	if (n == -1)
	{
		return;
	}

	if (m_lockDraw.IsLocked() || m_lockChangeMode.IsLocked()) //если рисуем или делаются изменения
	{
		return; //ничего не делать, заодно и рекурсивный вызов прекратим
	}

	if (n < nRows)
	{
		//надо удалить лишние строки
		m_lockDraw.Lock();

		for (int i = nRows - 1; i >= n; i--)
		{
			DeleteItem(i);
			m_nBottomAddr -= 010;
		}

		m_lockDraw.UnLock();
	}
	else if (n > nRows)
	{
		//надо добавить недостающие строки
		m_lockDraw.Lock();

		for (int i = nRows; i < n; ++i)
		{
			InsertNewLine(i);
		}

		m_lockDraw.UnLock();
	}

	//иначе ничего делать не надо
}

void CDumpListCtrl::RefreshItems(WORD nNewAddr, bool bNewAddrIsValid)
{
	int nRows = GetItemCount();

	if (nRows <= 0) //если нет никаких строк
	{
		return; //не обновлять
	}

	while (m_lockChangeMode.IsLocked()); //подождём, пока кончатся делаться изменения

	WORD nAddr = 0;
	CString str;
	m_lockDraw.Lock();

	if (bNewAddrIsValid) //если необходимо - переопределим адрес
	{
		m_nTopAddr = nNewAddr & 0177770;
	}

	auto pDlg = static_cast<CMemDumpDlg *>(GetParent());

	switch (m_DisplayMode)
	{
		case DD_WORD_VIEW:
		{
			for (register int i = 0; i < nRows; ++i)
			{
				nAddr = m_nTopAddr + i * 010;
				str = WordToOctString(nAddr) + _T(":");
				SetItemText(i, IDS_MEMORY_ADDR_WORD, str);
				str = _T("");

				for (register int j = IDS_MEMORY_COLUMN1_WORD; j <= IDS_MEMORY_COLUMN4_WORD; ++j)
				{
					register WORD nVal = pDlg->GetDebugMemDumpWord(nAddr);
					SetItemWithModified(nVal, i, j);
					str += GetMemDumpByteAsANSI(LOBYTE(nVal));
					str += GetMemDumpByteAsANSI(HIBYTE(nVal));
					nAddr += 2;
				}

				SetItemWithModifiedASCII(str, i, IDS_MEMORY_ASCII_WORD);
			}

			break;
		}

		case DD_BYTE_VIEW:
		{
			for (register int i = 0; i < nRows; ++i)
			{
				nAddr = m_nTopAddr + i * 010;
				str = WordToOctString(nAddr) + _T(":");
				SetItemText(i, IDS_MEMORY_ADDR_BYTE, str);
				str = _T("");

				for (register int j = IDS_MEMORY_COLUMN1_BYTE; j <= IDS_MEMORY_COLUMN8_BYTE; ++j)
				{
					register BYTE nVal = pDlg->GetDebugMemDumpByte(nAddr);
					SetItemWithModifiedByte(nVal, i, j);
					str += GetMemDumpByteAsANSI(nVal);
					nAddr++;
				}

				SetItemWithModifiedASCII(str, i, IDS_MEMORY_ASCII_BYTE);
			}

			break;
		}
	}

	m_nBottomAddr = nAddr;
	m_lockDraw.UnLock();
	AdjustScroll();
}

void CDumpListCtrl::AdjustScroll()
{
	SetScrollRange(SB_VERT, 0, 65544, FALSE); //надо делать постоянно, видимо стандартные средства постоянно сбрасывают на свои значения
	SetScrollPos(SB_VERT, m_nTopAddr, TRUE);
	ShowScrollBar(SB_VERT, TRUE);
}

void CDumpListCtrl::SetEditAddress()
{
	//в окошке адреса изменим значение
	GetParent()->SetDlgItemText(IDC_DA_DUMP_ADDRESS, WordToOctString(m_nTopAddr));
}

void CDumpListCtrl::ChangeAddr()
{
	SetEditAddress();
	RefreshItems();
}

void CDumpListCtrl::OnSize(UINT nType, int cx, int cy)
{
	CMultiEditListCtrl::OnSize(nType, cx, cy);
	AdjustRows();
	AdjustScroll();
}


void CDumpListCtrl::OnLvnKeydown(NMHDR *pNMHDR, LRESULT *pResult)
{
	auto pLVKeyDow = reinterpret_cast<LPNMLVKEYDOWN>(pNMHDR);
	int nOffs = GetItemCount() * 010;
	*pResult = 1;

	switch (pLVKeyDow->wVKey)
	{
		case VK_UP:
			m_nTopAddr -= 010;
			break;

		case VK_DOWN:
			m_nTopAddr += 010;
			break;

		case VK_PRIOR:
			m_nTopAddr -= nOffs;
			break;

		case VK_NEXT:
			m_nTopAddr = m_nBottomAddr;
			break;

		case VK_HOME:
			m_nTopAddr = 0;
			break;

		case VK_END:
			m_nTopAddr = -nOffs;
			break;

		default:
			*pResult = 0;
	}

	if (*pResult)
	{
		ChangeAddr();
	}
}


void CDumpListCtrl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
{
	int nOffs = GetItemCount() * 010;
	bool bChange = true;

	switch (nSBCode)
	{
		case SB_LINEUP:
			m_nTopAddr -= 010;
			break;

		case SB_LINEDOWN:
			m_nTopAddr += 010;
			break;

		case SB_PAGEUP:
			m_nTopAddr -= nOffs;
			break;

		case SB_PAGEDOWN:
			m_nTopAddr = m_nBottomAddr;
			break;

		case SB_TOP:
			m_nTopAddr = 0;
			break;

		case SB_BOTTOM:
			m_nTopAddr = -nOffs;
			break;

		case SB_THUMBTRACK:
			m_nTopAddr = (nPos & 0177770);
			break;

		default:
			bChange = false;
	}

	if (bChange)
	{
		ChangeAddr();
	}
}


BOOL CDumpListCtrl::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	int nOffs = (nFlags & MK_CONTROL) ? GetItemCount() * 010 : 010;

	if (zDelta < 0)
	{
		m_nTopAddr += nOffs;
		ChangeAddr();
	}
	else if (zDelta > 0)
	{
		m_nTopAddr -= nOffs;
		ChangeAddr();
	}

	return TRUE;
}


BOOL CDumpListCtrl::PreCreateWindow(CREATESTRUCT &cs)
{
	//попытка сделать принудительный показ вертикального скроллбара.
	//но всё равно не работает, вертикальный скроллбар рисуется поверх правой колонки.
	cs.style |= WS_VSCROLL;
	return CMultiEditListCtrl::PreCreateWindow(cs);
}

void CDumpListCtrl::SetItemWithModifiedByte(register BYTE byte, register int nItem, register int nSubitem, bool bColored)
{
	register ITEM_INFO *p = &(m_colorTable[nItem][nSubitem]);

	//если этот итем в данный момент редактируется, то ничего не делать
	if (p->bEdited)
	{
		return;
	}

	if (bColored)
	{
		//если значение изменилось
		if (p->nValue != byte)
		{
			p->nValue = byte;
			p->bModified = true; //пометим, что оно изменилось
			SetItemColor(MODIFIED_COLOR, nItem, nSubitem); //выведем его красным цветом
			SetItemText(nItem, nSubitem, ByteToOctString(byte));
		}
		//если не менялось, но в прошлый раз было выведено красным цветом
		else if (p->bModified)
		{
			p->bModified = false; //пометим, что больше не нужно ничего рисовать, пока значение снова не изменится
			SetItemColor(m_StdColor, nItem, nSubitem); //выведем его обычным цветом
			SetItemText(nItem, nSubitem, ByteToOctString(byte));
		}

		//иначе - вообще не надо ничего выводить.
	}
	else
	{
		//если значение изменилось
		if (p->nValue != byte)
		{
			p->nValue = byte;
			p->bModified = false;
			SetItemColor(m_StdColor, nItem, nSubitem); //выведем его обычным цветом
			SetItemText(nItem, nSubitem, ByteToOctString(byte));
		}

		//если не менялось - не выводим ничего
	}
}

