#include "pch.h"
#include "DumpListCtrl.h"
#include "MemDumpDlg.h"
#include "Config.h"

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


constexpr auto MAX_DUMP_COLS_WORD = 4;
constexpr auto MAX_DUMP_COLS_BYTE = 8;
constexpr auto COLUMN_WIDTH_ADDR  = 60;
constexpr auto COLUMN_WIDTH_WORD  = 68;
constexpr auto COLUMN_WIDTH_BYTE  = 34;
constexpr auto COLUMN_WIDTH_TXT   = 70;

CDumpListCtrl::CDumpListCtrl()
	: m_DisplayMode(DUMP_DISPLAY_MODE::DD_WORD_VIEW)
	, m_nBottomAddr(0)
{
}


CDumpListCtrl::~CDumpListCtrl()
{
}

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


TCHAR CDumpListCtrl::GetMemDumpByteAsANSI(uint8_t 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)
{
	{
		std::lock_guard<std::mutex> lk(m_lock);
		// сперва удалим всё
		DeleteAllItems();
		// потом удалим все столбцы
		CHeaderCtrl *pHeader = GetHeaderCtrl();
		int n = pHeader->GetItemCount();

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

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

void CDumpListCtrl::Init(DUMP_DISPLAY_MODE mode)
{
	SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER | LVS_EX_SNAPTOGRID);
	{
		std::lock_guard<std::mutex> lk(m_lock);
		m_DisplayMode = mode;
		SetHeader();
		SetEditAddress();
	}
	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 DUMP_DISPLAY_MODE::DD_WORD_VIEW:
		{
			CString s(MAKEINTRESOURCE(IDS_DEBUG_ADDRESS));
			// добавляем столбцы
			InsertColumn(DUMP_LISTW::W_ADDR, s, LVCFMT_CENTER | LVCFMT_FIXED_WIDTH, ::MulDiv(COLUMN_WIDTH_ADDR, nPixelW, 96));
			pHeader->SetItem(DUMP_LISTW::W_ADDR, &hditem); // почему-то зафиксировать ширину первого столбца можно только так

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

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

		case DUMP_DISPLAY_MODE::DD_BYTE_VIEW:
		{
			CString s(MAKEINTRESOURCE(IDS_DEBUG_ADDRESS));
			// добавляем столбцы
			InsertColumn(DUMP_LISTB::B_ADDR, s, LVCFMT_CENTER | LVCFMT_FIXED_WIDTH, ::MulDiv(COLUMN_WIDTH_ADDR, nPixelW, 96));
			pHeader->SetItem(DUMP_LISTB::B_ADDR, &hditem);

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

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

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

	switch (m_DisplayMode)
	{
		case DUMP_DISPLAY_MODE::DD_WORD_VIEW:
		{
			for (register int j = DUMP_LISTW::W_COL1; j <= DUMP_LISTW::W_COL4; ++j)
			{
				register uint16_t nVal = pDlg->GetDebugMemDumpWord(nAddr);
				SetItemWithModified(nVal, nNum, j);
				str += GetMemDumpByteAsANSI(LOBYTE(nVal));
				str += GetMemDumpByteAsANSI(HIBYTE(nVal));
				nAddr += 2;
			}

			SetItemWithModifiedASCII(str, nNum, DUMP_LISTW::W_ASCII);
			SetItemBkColor(GetSysColor(COLOR_BTNFACE), nNum, DUMP_LISTW::W_ADDR);
			EnableEdit(false, nNum, DUMP_LISTW::W_ADDR);
			EnableEdit(false, nNum, DUMP_LISTW::W_ASCII);
			break;
		}

		case DUMP_DISPLAY_MODE::DD_BYTE_VIEW:
		{
			for (register int j = DUMP_LISTB::B_COL1; j <= DUMP_LISTB::B_COL8; ++j)
			{
				register uint8_t nVal = pDlg->GetDebugMemDumpByte(nAddr);
				SetItemWithModifiedByte(nVal, nNum, j);
				str += GetMemDumpByteAsANSI(nVal);
				nAddr++;
			}

			SetItemWithModifiedASCII(str, nNum, DUMP_LISTB::B_ASCII);
			SetItemBkColor(GetSysColor(COLOR_BTNFACE), nNum, DUMP_LISTB::B_ADDR);
			EnableEdit(false, nNum, DUMP_LISTB::B_ADDR);
			EnableEdit(false, nNum, DUMP_LISTB::B_ASCII);
			break;
		}
	}

	m_nBottomAddr = nAddr;
}

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

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

	if (m_lock.try_lock()) // если рисуем или делаются изменения
	{
		if (n < nRows)
		{
			// надо удалить лишние строки
			for (int i = nRows - 1; i >= n; i--)
			{
				DeleteItem(i);
				m_nBottomAddr -= 010;
			}
		}
		else if (n > nRows)
		{
			// надо добавить недостающие строки
			for (int i = nRows; i < n; ++i)
			{
				InsertNewLine(i);
			}
		}

		// иначе ничего делать не надо
		m_lock.unlock();
	}
}

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

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

	{
		m_lock.lock();

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

		auto pDlg = static_cast<CMemDumpDlg *>(GetParent());
		CString str;
		register uint16_t nAddr = 0;

		switch (m_DisplayMode)
		{
			case DUMP_DISPLAY_MODE::DD_WORD_VIEW:
			{
				for (register int i = 0; i < nRows; ++i)
				{
					nAddr = g_Config.m_nDumpAddr + i * 010;
					::WordToOctString(nAddr, str), str += _T(":");
					SetItemText(i, DUMP_LISTW::W_ADDR, str);
					str.Empty();

					for (register int j = DUMP_LISTW::W_COL1; j <= DUMP_LISTW::W_COL4; ++j)
					{
						register uint16_t nVal = pDlg->GetDebugMemDumpWord(nAddr);
						SetItemWithModified(nVal, i, j);
						str += GetMemDumpByteAsANSI(LOBYTE(nVal));
						str += GetMemDumpByteAsANSI(HIBYTE(nVal));
						nAddr += 2;
					}

					SetItemWithModifiedASCII(str, i, DUMP_LISTW::W_ASCII);
				}

				break;
			}

			case DUMP_DISPLAY_MODE::DD_BYTE_VIEW:
			{
				for (register int i = 0; i < nRows; ++i)
				{
					nAddr = g_Config.m_nDumpAddr + i * 010;
					::WordToOctString(nAddr, str), str += _T(":");
					SetItemText(i, DUMP_LISTB::B_ADDR, str);
					str.Empty();

					for (register int j = DUMP_LISTB::B_COL1; j <= DUMP_LISTB::B_COL8; ++j)
					{
						register uint8_t nVal = pDlg->GetDebugMemDumpByte(nAddr);
						SetItemWithModifiedByte(nVal, i, j);
						str += GetMemDumpByteAsANSI(nVal);
						nAddr++;
					}

					SetItemWithModifiedASCII(str, i, DUMP_LISTB::B_ASCII);
				}

				break;
			}
		}

		m_nBottomAddr = nAddr;
		m_lock.unlock();
	}

	AdjustScroll();
}

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

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

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:
			g_Config.m_nDumpAddr -= 010;
			break;

		case VK_DOWN:
			g_Config.m_nDumpAddr += 010;
			break;

		case VK_PRIOR:
			g_Config.m_nDumpAddr -= nOffs;
			break;

		case VK_NEXT:
			g_Config.m_nDumpAddr = m_nBottomAddr;
			break;

		case VK_HOME:
			g_Config.m_nDumpAddr = 0;
			break;

		case VK_END:
			g_Config.m_nDumpAddr = -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:
			g_Config.m_nDumpAddr -= 010;
			break;

		case SB_LINEDOWN:
			g_Config.m_nDumpAddr += 010;
			break;

		case SB_PAGEUP:
			g_Config.m_nDumpAddr -= nOffs;
			break;

		case SB_PAGEDOWN:
			g_Config.m_nDumpAddr = m_nBottomAddr;
			break;

		case SB_TOP:
			g_Config.m_nDumpAddr = 0;
			break;

		case SB_BOTTOM:
			g_Config.m_nDumpAddr = -nOffs;
			break;

		case SB_THUMBTRACK:
			g_Config.m_nDumpAddr = (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)
	{
		g_Config.m_nDumpAddr += nOffs;
		ChangeAddr();
	}
	else if (zDelta > 0)
	{
		g_Config.m_nDumpAddr -= nOffs;
		ChangeAddr();
	}

	return TRUE;
}


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

void CDumpListCtrl::SetItemWithModifiedByte(register uint8_t 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));
		}

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

