// BreakPoint.cpp: implementation of the CBreakPoint class.
//


#include "pch.h"
#include "BK.h"
#include "BreakPoint.h"
#include "Config.h"
#include "Platform.h"
#include "Debugger.h"
#include "Screen_Sizes.h"

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

/////////////////////////////////////////////////////////////////////////////
// Construction/Destruction


CBreakPoint::CBreakPoint()
	: m_type(BREAKPOINT::ADDRESS)
	, m_nMemAccess(0)
	, m_nMemConditions(0)
	, m_breakAddress(0177777)
	, m_breakedCmdAddr(0)
	, m_bBreaked(false)
	, m_bShowCond(false)
	, m_bEnable(true)
{
}

CBreakPoint::CBreakPoint(uint16_t addr, BREAKPOINT type)
	: m_type(type)
	, m_breakAddress(addr)
	, m_nMemAccess(0)
	, m_nMemConditions(0)
	, m_breakedCmdAddr(0)
	, m_bBreaked(false)
	, m_bShowCond(false)
	, m_bEnable(true)
{
}

CBreakPoint::~CBreakPoint()
    = default;



// ==========================================================================

IMPLEMENT_DYNAMIC(CBPTListCtrl, CListCtrl)

BEGIN_MESSAGE_MAP(CBPTListCtrl, CListCtrl)
	ON_WM_SIZE()
	ON_WM_SETFOCUS()
	ON_NOTIFY_REFLECT(NM_DBLCLK, &CBPTListCtrl::OnNMDblclk)
	ON_NOTIFY_REFLECT(LVN_KEYDOWN, &CBPTListCtrl::OnLvnKeydown)
END_MESSAGE_MAP()

CBPTListCtrl::CBPTListCtrl()
	: m_pDebugger(nullptr)
{
}

CBPTListCtrl::~CBPTListCtrl()
{
}

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

void CBPTListCtrl::OutContent()
{
	if (m_pDebugger)
	{
		const auto pBpt = m_pDebugger->GetBreakpointListPtr();
		const int s = static_cast<int>(pBpt->size());   // количество точек останова в списке
		int i = GetItemCount(); // количество элементов в списке на экране

		if (s > i)
		{
			// надо добавить строк
			while (i < s)
			{
				InsertItem(i++, LPSTR_TEXTCALLBACK);
			}
		}
		else if (s < i)
		{
			// надо удалить строки
			while (i > s)
			{
				DeleteItem(--i);
			}
		}

		// а потом заполним ячейки таблицы
		int nItem = 0;
		CString str;

		for (const auto &bpt : *pBpt)
		{
			if (bpt.IsEnable())
			{
				if (bpt.IsBreaked())
				{
					str = _T("*");
				}
				else
				{
					str.Empty();
				}
			}
			else
			{
				str = _T("-");
			}

			SetItemText(nItem, BPT_LIST::BPTLST_COL_NUMBER, str);
			str = Global::WordToOctString(bpt.GetAddress());
			SetItemText(nItem, BPT_LIST::BPTLST_COL_ADDR, str);

			switch (bpt.GetType())
			{
				case BREAKPOINT::ADDRESS:
					str = _T("Обычный");
					break;

				case BREAKPOINT::MEMORY_ACCESS:
					str = _T("Доступ к памяти");
					{
						const uint32_t m = bpt.GetMemAccess();
						CString s;

						if (m & BP_MEMACCESS_BYTE)
						{
							s += _T("B ");
						}

						if (m & BP_MEMACCESS_RD)
						{
							s += _T('R');
						}

						if (m & BP_MEMACCESS_WR)
						{
							s += _T('W');
						}

						SetItemText(nItem, BPT_LIST::BPTLST_COL_CONDITIONS, s);
					}
					break;

				default:
					str = _T("Ошибка");
					break;
			}

			SetItemText(nItem, BPT_LIST::BPTLST_COL_TYPE, str);
			str.Empty();

			if (bpt.IsShow())
			{
				str = _T("<--");

				if (bpt.GetType() == BREAKPOINT::MEMORY_ACCESS)
				{
					str += _T("  ");
					const uint32_t m = bpt.GetMemAccConditions();

					if (m & BP_MEMACCESS_BYTE)
					{
						str += _T("B ");
					}

					if (m & BP_MEMACCESS_RD)
					{
						str += _T('R');
					}

					if (m & BP_MEMACCESS_WR)
					{
						str += _T('W');
					}
				}

				const uint32_t pc = bpt.GetMemPC(); // адрес инструкции, на котором сработала точка останова

				if ((pc & 0x10000000) != 0)
				{
					str += _T("  [ прерывание по вектору ") + Global::WordToOctString(pc & 0xffff) + _T(" ]");
				}
				else
				{
					str += _T("  [") + Global::WordToOctString(pc) + _T(": ");
					// Дизассемблируем машинный код
					uint16_t instrOpcode[8]; // опкоды, тут не используются
					CString strInstr; // дизассемблированная инструкция
					// на выходе - количество слов в инструкции
					const int len = m_pDebugger->DebugInstruction(pc, strInstr, instrOpcode);

					// у strInstr надо удалить маркеры колоризации.
					for (;;)
					{
						const int pos = strInstr.Find(_T(COLORED_TAG), 0); // находим первый попавшийся маркер

						if (pos < 0)
						{
							break;
						}

						const int len = strInstr.GetLength();
						strInstr = strInstr.Left(pos) + strInstr.Right(len - pos - (COLORED_TAG_LENGTH + 1));
					}

					str += strInstr + _T(" ]");
				}
			}

			SetItemText(nItem, BPT_LIST::BPTLST_COL_STATUS, str);
			nItem++;
		}
	}
}

void CBPTListCtrl::Init()
{
	ModifyStyle(0, LVS_REPORT | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | SBS_VERT);
	SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER | LVS_EX_SNAPTOGRID);
	// Create Header
	const int nPixelW = ::GetDeviceCaps(GetDC()->GetSafeHdc(), LOGPIXELSX);
	CString strHeader;
	InsertColumn(BPT_LIST::BPTLST_COL_NUMBER, _T("#"), LVCFMT_LEFT, ::MulDiv(20, nPixelW, DEFAULT_DPIX));
	InsertColumn(BPT_LIST::BPTLST_COL_ADDR, CString(MAKEINTRESOURCE(IDS_DEBUG_ADDRESS)), LVCFMT_LEFT, ::MulDiv(60, nPixelW, DEFAULT_DPIX));
	InsertColumn(BPT_LIST::BPTLST_COL_TYPE, _T("Тип"), LVCFMT_LEFT, ::MulDiv(150, nPixelW, DEFAULT_DPIX));
	InsertColumn(BPT_LIST::BPTLST_COL_CONDITIONS, _T("Условия"), LVCFMT_LEFT, ::MulDiv(100, nPixelW, DEFAULT_DPIX));
	InsertColumn(BPT_LIST::BPTLST_COL_STATUS, _T("Состояние"), LVCFMT_LEFT, ::MulDiv(400, nPixelW, DEFAULT_DPIX));
}


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

void CBPTListCtrl::OnSetFocus(CWnd *pOldWnd)
{
	CListCtrl::OnSetFocus(pOldWnd);

	if (GetSelectionMark() < 0)
	{
		SetSelectionMark(0);
	}

	Invalidate(FALSE);
}

void CBPTListCtrl::SwitchOnOffSelected()
{
	const auto pBpt = m_pDebugger->GetBreakpointListPtr();
	bool b = false;
	POSITION pos = GetFirstSelectedItemPosition();

	while (pos)
	{
		b = true;
		const int n = GetNextSelectedItem(pos);
		auto p = pBpt->begin();
		std::advance(p, n);
		p->SetEnabled(!p->IsEnable());
	}

	if (b)
	{
		OutContent();
		g_Platform.GetMainPtr()->GetDisasmViewPtr()->PostMessage(WM_DBG_DISASM_BP_UPDATE);
	}
}


void CBPTListCtrl::OnNMDblclk(NMHDR *pNMHDR, LRESULT *pResult)
{
	*pResult = 0;
	const auto pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
	const auto pBpt = m_pDebugger->GetBreakpointListPtr();
	auto p = pBpt->begin();
	std::advance(p, pNMItemActivate->iItem);
	p->SetEnabled(!p->IsEnable());
	OutContent();
}

void CBPTListCtrl::DeleteSelected()
{
	std::vector <int> items;
	const auto pBpt = m_pDebugger->GetBreakpointListPtr();
	bool b = false;
	POSITION pos = GetFirstSelectedItemPosition();
	int nFirst = 0;

	while (pos)
	{
		const int n = GetNextSelectedItem(pos);
		items.push_back(n);

		if (!b)
		{
			b = true;
			nFirst = n;
		}
	}

	while (!items.empty())
	{
		const int n = items.back();
		items.pop_back();
		auto p = pBpt->begin();
		std::advance(p, n);
		pBpt->erase(p);
	}

	if (b)
	{
		OutContent();
		SetItemState(-1, ~LVIS_SELECTED, LVIS_SELECTED);

		if (nFirst >= GetItemCount())
		{
			nFirst = GetItemCount() - 1;
		}

		SetItemState(nFirst, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
		g_Platform.GetMainPtr()->GetDisasmViewPtr()->PostMessage(WM_DBG_DISASM_BP_UPDATE);
	}
}


void CBPTListCtrl::OnLvnKeydown(NMHDR *pNMHDR, LRESULT *pResult)
{
	*pResult = 0;
	const auto pLVKeyDow = reinterpret_cast<LPNMLVKEYDOWN>(pNMHDR);

	if (pLVKeyDow->wVKey == VK_DELETE)
	{
		DeleteSelected();
	}
}
