// DisasmCtrl.cpp : implementation file
//

#include "pch.h"
#include "resource.h"
#include "DisasmCtrl.h"
#include "Debugger.h"
#include "Platform.h"
#include "Screen_Sizes.h"

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

/////////////////////////////////////////////////////////////////////////////
// CDisasmCtrl
IMPLEMENT_DYNAMIC(CDisasmCtrl, CListCtrl)

CDisasmCtrl::CDisasmCtrl() :
	m_pDebugger(nullptr),
	m_nSelection(-1)
{
	m_hBPIcon = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_DBG_BPT));
	m_hCurrIcon = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_DBG_CUR));
}

CDisasmCtrl::~CDisasmCtrl()
{
	if (m_hBPIcon)
	{
		DestroyIcon(m_hBPIcon);
	}

	if (m_hCurrIcon)
	{
		DestroyIcon(m_hCurrIcon);
	}
}

BEGIN_MESSAGE_MAP(CDisasmCtrl, CListCtrl)
	ON_WM_SIZE()
	ON_WM_SETFOCUS()
	ON_WM_MOUSEWHEEL()
	ON_NOTIFY_REFLECT(LVN_ITEMCHANGED, &CDisasmCtrl::OnItemchanged)
	ON_NOTIFY_REFLECT(LVN_KEYDOWN, &CDisasmCtrl::OnKeydown)
	ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &CDisasmCtrl::OnCustomDraw)
	ON_NOTIFY_REFLECT(NM_DBLCLK, &CDisasmCtrl::OnNMDblclk)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDisasmCtrl message handlers

void CDisasmCtrl::Init()
{
	ModifyStyle(0, LVS_REPORT | LVS_OWNERDATA | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | SBS_VERT);
	SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER | LVS_EX_SNAPTOGRID);
	// Create Header
	int nPixelW = ::GetDeviceCaps(GetDC()->GetSafeHdc(), LOGPIXELSX);
	CString strHeader;
	InsertColumn(DISASM_LIST::COL_MARK, CString(MAKEINTRESOURCE(IDS_DEBUG_MARK)), LVCFMT_LEFT, ::MulDiv(20, nPixelW, DEFAULT_DPIX));
	InsertColumn(DISASM_LIST::COL_ADDR, CString(MAKEINTRESOURCE(IDS_DEBUG_ADDRESS)), LVCFMT_LEFT, ::MulDiv(50, nPixelW, DEFAULT_DPIX));
	InsertColumn(DISASM_LIST::COL_INSTR, CString(MAKEINTRESOURCE(IDS_DEBUG_COMMAND)), LVCFMT_LEFT, ::MulDiv(200, nPixelW, DEFAULT_DPIX));
	InsertColumn(DISASM_LIST::COL_COMMENT, CString(MAKEINTRESOURCE(IDS_DEBUG_COMMENTS)), LVCFMT_LEFT, ::MulDiv(150, nPixelW, DEFAULT_DPIX));
	OutContent();
}

void CDisasmCtrl::AttachDebugger(CDebugger *pDebugger)
{
	m_pDebugger = pDebugger;
}

void CDisasmCtrl::OnSize(UINT nType, int cx, int cy)
{
	CListCtrl::OnSize(nType, cx, cy);
	OutContent();
}

void CDisasmCtrl::OutContent()
{
	if (m_lock.try_lock())  // Lock for recurse call preserve
	{
		// Delete All Items
		DeleteAllItems();
		const int nCount = GetCountPerPage();

		if (nCount != -1)
		{
			// Insert new items
			for (int i = 0; i < nCount; ++i)
			{
				InsertItem(i, LPSTR_TEXTCALLBACK);

				if (i == m_nSelection)
				{
					SetItemState(m_nSelection, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
				}
			}
		}

		// Unlock
		m_lock.unlock();
	}
}


void CDisasmCtrl::DisasmStepUp(int nCount)
{
	if (m_pDebugger) // сдвигаемся вверх
	{
		const uint16_t addr = m_pDebugger->StepBackward(nCount);
		GetParent()->SendMessage(WM_DBG_TOP_ADDRESS_SET, WPARAM(addr));
	}
}

void CDisasmCtrl::DisasmStepDn(int nCount)
{
	if (m_pDebugger) // сдвигаемся вниз
	{
		const uint16_t addr = m_pDebugger->StepForward(nCount);
		GetParent()->SendMessage(WM_DBG_TOP_ADDRESS_SET, WPARAM(addr));
	}
}

void CDisasmCtrl::OnItemchanged(NMHDR *pNMHDR, LRESULT *pResult)
{
	const auto pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);

	// Change selection marker
	if (pNMListView->iItem != -1 && pNMListView->uNewState & LVIS_SELECTED)
	{
		m_nSelection = pNMListView->iItem;
	}

	*pResult = S_OK;
}


void CDisasmCtrl::OnKeydown(NMHDR *pNMHDR, LRESULT *pResult)
{
	const auto pLVKeyDow = reinterpret_cast<LPNMLVKEYDOWN>(pNMHDR);
	const int nLastItem = GetItemCount() - 1;
	const int nSel = GetSelectionMark();
	const int nCount = GetCountPerPage();

	if (nSel == 0)
	{
		if (pLVKeyDow->wVKey == VK_UP)
		{
			// если мы вверху и нажали кнопку вверх
			DisasmStepUp();
		}
		else if (pLVKeyDow->wVKey == VK_PRIOR)
		{
			// если мы вверху и нажали кнопку pgup
			DisasmStepUp(nCount);
		}

		RedrawItems(0, nLastItem); // и всё перерисовываем
	}
	else if (nSel == nLastItem)
	{
		if (pLVKeyDow->wVKey == VK_DOWN)
		{
			// если мы внизу и нажали кнопку вниз
			DisasmStepDn();
		}
		else if (pLVKeyDow->wVKey == VK_NEXT)
		{
			// если мы внизу и нажали кнопку pgdn
			DisasmStepDn(nCount);
		}

		RedrawItems(0, nLastItem); // и всё перерисовываем
	}

	*pResult = S_OK;
}


BOOL CDisasmCtrl::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	const int nLastItem = GetItemCount() - 1;

	if (zDelta < 0)
	{
		DisasmStepDn();
	}
	else if (zDelta > 0)
	{
		DisasmStepUp();
	}

	RedrawItems(0, nLastItem); // и всё перерисовываем
	return TRUE;
}

void CDisasmCtrl::OnNMDblclk(NMHDR *pNMHDR, LRESULT *pResult)
{
	const auto pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);

	if (pNMItemActivate->iSubItem == 0) // если кликнули по самому первому столбцу
	{
		if (m_pDebugger)
		{
			const uint16_t addr = m_pDebugger->GetLineAddress(pNMItemActivate->iItem);

			if (!m_pDebugger->SetSimpleBreakpoint(addr))
			{
				m_pDebugger->RemoveBreakpoint(addr);
			}
		}

		Invalidate(FALSE);
	}

	*pResult = 0;
}

void CDisasmCtrl::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)
{
	const auto pLVCD = reinterpret_cast<NMLVCUSTOMDRAW *>(pNMHDR);
	*pResult = CDRF_DODEFAULT;

	switch (pLVCD->nmcd.dwDrawStage)
	{
		case CDDS_PREPAINT:
			*pResult = CDRF_NOTIFYITEMDRAW;
			break;

		case CDDS_ITEMPREPAINT:
		{
			// меняем цвет курсора выделения
			const auto nIndex = int(pLVCD->nmcd.dwItemSpec);

			if (GetItemState(nIndex, LVIS_SELECTED | LVIS_FOCUSED)
			        && ::GetFocus() == pLVCD->nmcd.hdr.hwndFrom)
			{
				pLVCD->clrTextBk = RGB(0xff, 0xff, 0); // RGB(0, 0xcc, 0xff);
				SetItemState(nIndex, 0, LVIS_SELECTED);
			}

			*pResult = CDRF_NOTIFYPOSTPAINT;
		}
		break;

//      case (CDDS_ITEMPREPAINT | CDDS_SUBITEM):
//          // здесь можно поменять текст конкретного итема
//          // только это всё равно медленнее работает, чем прорисовать всю строку за раз
//          switch (pLVCD->iSubItem)
//          {
//          case DISASM_LIST::COL_MARK:
//              break;
//          case DISASM_LIST::COL_ADDR:
//                  break;
//          case DISASM_LIST::COL_INSTR:
//              break;
//          }
//          break;

		case CDDS_ITEMPOSTPAINT:
			if (m_pDebugger && g_Platform.GetBoardPtr()) //пока board не приаттачен, делать нечего, и ничего не работает
			{
				CDC *pDC = CDC::FromHandle(pLVCD->nmcd.hdc);
				// Get item index
				const auto nIndex = int(pLVCD->nmcd.dwItemSpec);
				// Дизассемблируем машинный код
				uint16_t instrOpcode[8];
				CString strInstruction;
				const register uint16_t wLineAddr = m_pDebugger->GetLineAddress(nIndex);
				const register int len = m_pDebugger->DebugInstruction(wLineAddr, strInstruction, instrOpcode);
				// Выводим маркер
				CRect rect;
				GetSubItemRect(nIndex, DISASM_LIST::COL_MARK, LVIR_LABEL, rect);

				if (m_pDebugger->IsBpeakpointAtAddress(wLineAddr))
				{
					::DrawIconEx(pDC->m_hDC, rect.left, rect.top, m_hBPIcon, 16, 16, 0, nullptr, DI_NORMAL);
				}

				if (m_pDebugger->CheckDebuggedLine(wLineAddr))
				{
					::DrawIconEx(pDC->m_hDC, rect.left, rect.top, m_hCurrIcon, 16, 16, 0, nullptr, DI_NORMAL);
				}

				// Выводим адрес
				CString strTxt = ::WordToOctString(wLineAddr);
				GetSubItemRect(nIndex, DISASM_LIST::COL_ADDR, LVIR_BOUNDS, rect);
				rect.left += 4; rect.right -= 4;
				pDC->SetTextColor(g_crDebugColorHighLighting[HLCOLOR_ADDRESS]);
				pDC->DrawText(strTxt, &rect, DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS);
				// Выводим инструкцию
				GetSubItemRect(nIndex, DISASM_LIST::COL_INSTR, LVIR_BOUNDS, rect);
				rect.left += 4; rect.right -= 4;
				pDC->SetTextColor(g_crDebugColorHighLighting[HLCOLOR_MNEMONIC]);
				DrawColoredText(pDC, rect, strInstruction);
				// Выводим комментарий. Это у нас просто машинные инструкции ассемблерной команды
				strTxt = ::WordToOctString(instrOpcode[0]); // код инструкции у нас по любому всегда есть

				// а дальше от 0 до 2-х слов аргументов
				// для инструкций FIS если регистр PC, то 4 слова аргументов
				for (int i = 1; i < len; ++i)
				{
					strTxt += _T(' ') + ::WordToOctString(instrOpcode[i]);
				}

				GetSubItemRect(nIndex, DISASM_LIST::COL_COMMENT, LVIR_BOUNDS, rect);
				rect.left += 4; rect.right -= 4;
				pDC->SetTextColor(g_crDebugColorHighLighting[HLCOLOR_DEFAULT]);
				pDC->DrawText(strTxt, &rect, DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS);
				*pResult = CDRF_SKIPDEFAULT;
			}

			break;
	}
}

void CDisasmCtrl::DrawColoredText(CDC *pDC, CRect &rect, CString &str)
{
	bool bEnd = false;

	if (!str.IsEmpty())
	{
		// новый алгоритм. тут возможно начало строки без тега
		// и вообще можно вывести строку без тега
		register int beginpos = 0; // начальная позиция текста
		register const int endpos = str.GetLength(); // конечная позиция текста

		while (true)
		{
			// ищем тег
			register int tagpos = str.Find(_T(COLORED_TAG), beginpos);

			if (tagpos == -1) // если не нашли
			{
				tagpos = endpos;
				bEnd = true;
			}

			if (beginpos < tagpos) // если перед тегом есть какой-то текст
			{
				CString substr = str.Mid(beginpos, tagpos - beginpos);
				CSize size = pDC->GetTextExtent(substr); // текущая позиция вывода текста
				pDC->DrawText(substr, &rect, DT_LEFT | DT_VCENTER);
				rect.left += size.cx;

				if (rect.left >= rect.right)
				{
					bEnd = true;
				}
			}

			if (bEnd) // если тегов больше нет
			{
				break; // не надо их искать, выходим
			}

			register const int clpos = tagpos + COLORED_TAG_LENGTH;

			if (clpos < endpos)
			{
				register int colornum = (str.GetAt(clpos) - _T('0')) & 7; // номер цвета

				if (colornum > HLCOLOR_NUM_COLS)
				{
					colornum = HLCOLOR_DEFAULT;
				}

				pDC->SetTextColor(g_crDebugColorHighLighting[colornum]); // устанавливаем цвет
				beginpos = clpos + 1; // пойдём искать новый тег
			}
			else
			{
				break; // строка оборвана
			}
		}
	}
}


void CDisasmCtrl::OnSetFocus(CWnd *pOldWnd)
{
	CListCtrl::OnSetFocus(pOldWnd);
	Invalidate(FALSE);
}

