// DisasmCtrl.cpp : implementation file
//

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


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

/////////////////////////////////////////////////////////////////////////////
// CDisasmCtrl

IMPLEMENT_DYNAMIC(CDisasmCtrl, CMultiEditListCtrl)

CDisasmCtrl::CDisasmCtrl()
{
	m_pDebugger = g_Platform.GetDebuggerPtr();
	m_hBPIcon = ::LoadIcon(::AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_DBG_BPT));
	m_hBPIconOff = ::LoadIcon(::AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_DBG_BPTOFF));
	m_hBPIconMem = ::LoadIcon(::AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_DBG_BPTMEM));
	m_hBPIconMemOff = ::LoadIcon(::AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_DBG_BPTMEMOFF));
	m_hCurrIcon = ::LoadIcon(::AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_DBG_CUR));
}

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

	if (m_hBPIconOff)
	{
		DestroyIcon(m_hBPIconOff);
	}

	if (m_hBPIconMem)
	{
		DestroyIcon(m_hBPIconMem);
	}

	if (m_hBPIconMemOff)
	{
		DestroyIcon(m_hBPIconMemOff);
	}

	if (m_hCurrIcon)
	{
		DestroyIcon(m_hCurrIcon);
	}

	m_vItemsParam.clear();
}

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

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

BOOL CDisasmCtrl::PreTranslateMessage(MSG *pMsg)
{
	// если нажали клавишу Ввод
	if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN)
	{
		if (::GetAsyncKeyState(VK_CONTROL) & 0x8000)
		{
			// если нажали ctrl - то не должно срабатывать
			return TRUE;
		}

		// то будем редактировать ассемблерную команду
		const int nSel = GetSelectionMark();
		EditAsmInstr(nSel, DISASM_LIST::COL_INSTR);
		return FALSE;   // будем обрабатывать сами
	}

	// если нажали alt-ввод, то нужно задать PC текущим адресом
	// если это делать на ходу, глюки гарантированы
	if (pMsg->message == WM_SYSKEYDOWN && pMsg->wParam == VK_RETURN)
	{
		const int nSel = GetSelectionMark();
		const uint16_t addr = GetLineAddress(nSel);
		g_Platform.GetCPUPtr()->SetRON(CPUREG::PC, addr);
		g_pPaneRegistryDumpViewCPU->DisplayRegDump(); // и обновим информацию
		Invalidate(FALSE);
		return FALSE;
	}

	return CMultiEditListCtrl::PreTranslateMessage(pMsg);
}


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
	const int nPixelW = ::GetDeviceCaps(GetDC()->GetSafeHdc(), LOGPIXELSX);
	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();
}

// выдать адрес для строки под курсором
uint16_t CDisasmCtrl::GetCursorAddr() const
{
	const int nRow = GetSelectionMark();
	return (nRow < 0) ? 0 : m_vItemsParam[nRow].addr;
}

// выдать адрес для строки за последней строкой
uint16_t CDisasmCtrl::GetBottomAddr() const
{
	int nLast = GetItemCount() - 1; // номер последней строки

	if (nLast < 0)
	{
		nLast = 0;
	}

	// выдаём адрес не для последней строки, а за строкой
	return m_vItemsParam[nLast].addr + (m_vItemsParam[nLast].len << 1);
}

int CDisasmCtrl::GetLinesCount() const
{
	return GetCountPerPage();
}

// выдать номер строки для заданного адреса.
int CDisasmCtrl::GetLineFromAddr(uint16_t addr)
{
	int nRows = GetItemCount();

	for (int i = 0; i < nRows; ++i)
	{
		if (m_vItemsParam[i].addr == addr)
		{
			return i;
		}
	}

	return -1;
}

// выдать адрес для заданной строки
uint16_t CDisasmCtrl::GetLineAddress(int nRow) const
{
	if (nRow < 0 || nRow >= GetItemCount())
	{
		return 0;
	}

	if (nRow == 0) // для верхней строки всегда выдаём адрес начала дизассемблирования
	{
		return g_Config.m_nDisasmAddr;
	}

	// а для остальных строк делаем так:
	nRow--; // идём к предыдущей строке
	// и выдаем её адрес + длину команды в байтах
	// эта функция используется только при заполнении окна данными.
	// и на момент вызова, для текущей строки данные ещё не заполнены,
	return m_vItemsParam[nRow].addr + (m_vItemsParam[nRow].len << 1);
}



uint16_t CDisasmCtrl::StepBackward(const int nSteps /*= 1*/) const
{
	uint16_t addr = g_Config.m_nDisasmAddr;
	addr -= static_cast<uint16_t>(sizeof(uint16_t)) * nSteps;
	g_Config.m_nDisasmAddr = addr;
	return addr;
}

uint16_t CDisasmCtrl::StepForward(const int nSteps /*= 1*/) const
{
	uint16_t addr = g_Config.m_nDisasmAddr;

	if (nSteps < GetItemCount())
	{
		addr = m_vItemsParam[nSteps].addr;
	}
	else
	{
		addr = GetBottomAddr();
	}

	g_Config.m_nDisasmAddr = addr;
	return addr;
}

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();
		m_vItemsParam.clear();
		const int nCount = GetCountPerPage();

		if (nCount != -1)
		{
			// Insert new items
			for (int i = 0; i < nCount; ++i)
			{
				m_vItemsParam.emplace_back(); // создаём новый элемент массива
				InsertItem(i, LPSTR_TEXTCALLBACK); // а там его заполняем при отображении на экран

				if (i == m_nSelection)
				{
					SetSelected(i);
				}
			}
		}

		// Unlock
		m_lock.unlock();
	}
}


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

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

void CDisasmCtrl::OnItemchanged(NMHDR *pNMHDR, LRESULT *pResult)
{
	const auto pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
	const int nSubItem = pNMLV->iSubItem;
	const int nItem = pNMLV->iItem;

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

	if (nSubItem == DISASM_LIST::COL_INSTR)
	{
		const auto pstr = reinterpret_cast<CString *>(pNMLV->lParam);
		int nAddr = m_vItemsParam[nItem].addr;
		// теперь тут нужно ассемблировать команду в строке по заданному адресу.
		std::array<uint16_t, 8> tmpCmdBuf{ };
		const int len = m_pDebugger->AssembleCPUInstruction(nAddr, tmpCmdBuf, pstr);

		if (len > 0)
		{
			for (int i = 0; i < len; ++i)
			{
				m_pDebugger->SetDebugMemDump(nAddr, tmpCmdBuf[i]);
				nAddr += 2;
			}

			const int nLastItem = GetItemCount() - 1;

			if (nItem == nLastItem) // если и так в низу, то надо сдвинуть всё
			{
				DisasmStepDn();
			}
			else
			{
				m_nSelection++;
				SetSelected(m_nSelection);
			}

			Invalidate(FALSE);
		}
	}

	*pResult = S_OK;
}


void CDisasmCtrl::OnLVNKeyDown(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);
	const int nRow = pNMItemActivate->iItem;
	const int nCol = pNMItemActivate->iSubItem;

	if (nCol == DISASM_LIST::COL_MARK) // если кликнули по самому первому столбцу
	{
		if (m_pDebugger)
		{
			const uint16_t &addr = m_vItemsParam[nRow].addr;

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

			g_pPaneBreakpointView->SendMessage(WM_DBG_BREAKPOINTS_UPDATE);
		}

		Invalidate(FALSE);
	}
	else if (nCol == DISASM_LIST::COL_INSTR)
	{
		EditAsmInstr(nRow, nCol);
	}

	*pResult = S_OK;
}

void CDisasmCtrl::EditAsmInstr(const int nRow, const int nCol)
{
	m_itemsTable[nRow][nCol].bEdited = true;
	// If label click - create edit ctrl for item
	CRect rcSubItem;
	GetSubItemRect(nRow, nCol, LVIR_LABEL, rcSubItem);
	ClientToScreen(&rcSubItem);
	m_pEdit = new CMultiEdit(this, nRow, nCol);

	if (m_pEdit)
	{
		DWORD dwStyle = WS_VISIBLE | WS_POPUP | WS_BORDER | ES_WANTRETURN | ES_MULTILINE;
		m_pEdit->CreateEx(0, _T("edit"), nullptr, dwStyle, rcSubItem, this, 0);
		m_pEdit->SetFont(&m_font);
		// у strInstr надо удалить маркеры колоризации.
		CString strInstr = m_vItemsParam[nRow].instr;

		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));
		}

		m_pEdit->SetWindowText(strInstr);
		m_pEdit->SetFocus();
	}
	else
	{
		g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
	}
}


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 = static_cast<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 = static_cast<int>(pLVCD->nmcd.dwItemSpec);
				// Дизассемблируем машинный код
				const uint16_t wLineAddr = GetLineAddress(nIndex);
				const int len = m_pDebugger->DebugInstruction(wLineAddr, m_vItemsParam[nIndex]);
				// Выводим маркер
				CRect rect;
				GetSubItemRect(nIndex, DISASM_LIST::COL_MARK, LVIR_LABEL, rect);
				const int n = m_pDebugger->IsBpeakpointAtAddress(wLineAddr);

				if (n)
				{
					HICON ico = nullptr;

					switch (n)
					{
						default:
						case 1:
							ico = m_hBPIcon;
							break;

						case 2:
							ico = m_hBPIconOff;
							break;

						case 3:
							ico = m_hBPIconMem;
							break;

						case 4:
							ico = m_hBPIconMemOff;
							break;
					}

					::DrawIconEx(pDC->m_hDC, rect.left, rect.top, ico, 16, 16, 0, nullptr, DI_NORMAL);
				}

				if (g_Platform.IsCPUBreaked() && (wLineAddr == g_Platform.GetCPUPtr()->GetRON(CPUREG::PC)))
				{
					::DrawIconEx(pDC->m_hDC, rect.left, rect.top, m_hCurrIcon, 16, 16, 0, nullptr, DI_NORMAL);
				}

				// Выводим адрес
				CString strTxt = Global::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, m_vItemsParam[nIndex].instr);

				// Выводим комментарий.
				if (true)
				{
					// Это у нас просто машинные инструкции ассемблерной команды
					strTxt = Global::WordToOctString(m_vItemsParam[nIndex].opdata[0]); // код инструкции у нас по любому всегда есть

					// а дальше от 0 до 2-х слов аргументов
					// для инструкций FIS если регистр PC, то 4 слова аргументов
					for (int i = 1; i < len; ++i)
					{
						strTxt += _T(' ') + Global::WordToOctString(m_vItemsParam[nIndex].opdata[i]);
					}
				}
				else
				{
					// эта штука работает прям вот вообще хреново.
					// там по хорошему надо для каждой инструкции свои подсказки формировать
					strTxt = GetDisasmParTxt(m_vItemsParam[nIndex]);
				}

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

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

			if (tagpos == -1) // если не нашли
			{
				tagpos = endpos; // просто выведем строку как есть
			}

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

				if (rect.left >= rect.right)
				{
					break; // если область вывода кончилась, то хватит
				}
			}

			const int clpos = tagpos + COLORED_TAG_LENGTH;

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

				if (colornum > HLCOLOR::NUM_COLORS)
				{
					colornum = HLCOLOR::DEFAULT;
				}

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


void CDisasmCtrl::SetSelected(int nIdx)
{
	SetSelectionMark(nIdx);
	SetItemState(nIdx, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
}

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

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

	Invalidate(FALSE);
}

// !!! Вот это вот всё наверное надо в CDebugger перенести.
// если фичу до ума доводить.
// Там надо делать свою функцию для каждой команды, которая бы
// формировала свою строку подсказки.


CString CDisasmCtrl::GetDisasmParTxt(CDbgItemParam &p)
{
	CString strTxt;

	switch (p.optype)
	{
		case CDbgItemParam::OPTYPE::NO_OP:
			// ничего не выводится
			break;

		case CDbgItemParam::OPTYPE::NUM_ARG_OP:
			// числовой аргумент
			strTxt = Global::WordToOctString(p.arguments[ARGTYPE::DST].arg);
			break;

		case CDbgItemParam::OPTYPE::BRANCH_OP:
		case CDbgItemParam::OPTYPE::RTS_OP:
			// есть адрес перехода
			strTxt.Format(_T("PC <- %06o"), p.arguments[ARGTYPE::DST].addr_arg);
			break;

		case CDbgItemParam::OPTYPE::SOB_OP:
		{
			const auto &r = p.arguments[ARGTYPE::SRC].reg;
			uint16_t v = p.arguments[ARGTYPE::SRC].arg;

			if (--v)
			{
				strTxt.Format(_T("--R%i == %6o, loop to %06o"), r, v, p.arguments[ARGTYPE::DST].addr_arg);
			}
			else
			{
				strTxt.Format(_T("--R%i == 0, end loop"), r);
			}
		}
		break;

		case CDbgItemParam::OPTYPE::JSR_OP:
		{
			const auto &r = p.arguments[ARGTYPE::SRC].reg;
			uint16_t v = p.arguments[ARGTYPE::SRC].arg;
			strTxt.Format(_T("-(SP) <- R%i: %06o, PC <- "), r, v);
			strTxt += GetAddrText(p, ARGTYPE::DST);
		}
		break;

		case CDbgItemParam::OPTYPE::XOR_OP:
		{
			const auto &r = p.arguments[ARGTYPE::SRC].reg;
			uint16_t v = p.arguments[ARGTYPE::SRC].arg;
			strTxt.Format(_T("R%i: %06o -> "), r, v);
			strTxt += GetArgText(p, ARGTYPE::DST);
		}
		break;

		case CDbgItemParam::OPTYPE::ONE_OP:
			strTxt = GetArgText(p, ARGTYPE::DST);
			break;

		case CDbgItemParam::OPTYPE::TWO_OP:
			strTxt = GetArgText(p, ARGTYPE::SRC);
			strTxt += _T(" -> ");
			strTxt += GetArgText(p, ARGTYPE::DST);
			break;

		case CDbgItemParam::OPTYPE::EIS_OP:
		{
			const auto &r = p.arguments[ARGTYPE::SRC].reg;
			uint32_t v = p.arguments[ARGTYPE::SRC].arg;

			if (r & 1)
			{
				strTxt.Format(_T(" -> R%i: %06o"), r, v & 0xffff);
			}
			else
			{
				uint16_t v1 = v >> 16;
				strTxt.Format(_T(" -> R%i:R%i: %06o:%06o"), r, r + 1, v & 0xffff, v1);
			}

			strTxt = GetArgText(p, ARGTYPE::DST) + strTxt;
		}
		break;

		case CDbgItemParam::OPTYPE::FIS_OP:
		{
			const auto &ap_s = p.arguments[ARGTYPE::SRC];
			const auto &ap_d = p.arguments[ARGTYPE::DST];
			CString strValSrc = GetFloatNum(ap_s, 2);
			CString strValDst = GetFloatNum(ap_d, 2);
			strTxt.Format(_T("(R%i): "), ap_s.reg);
			strTxt += strValSrc + _T(", ") + strValDst;
		}
		break;

		case CDbgItemParam::OPTYPE::FPU_OP:
			strTxt = GetFPUArgText(p, ARGTYPE::DST);
			break;

		case CDbgItemParam::OPTYPE::FPU_OP1:
		{
			strTxt = GetFPUArgText(p, ARGTYPE::SRC)
			         + _T(" -> ")
			         + GetFPUArgText(p, ARGTYPE::DST);
		}
		break;

		case CDbgItemParam::OPTYPE::FPU_OP2:
			strTxt = GetFPUArgText(p, ARGTYPE::DST)
			         + _T(" -> ")
			         + GetFPUArgText(p, ARGTYPE::SRC);
			break;
	}

	return strTxt;
}

CString CDisasmCtrl::GetArgText(const CDbgItemParam &p, const ARGTYPE::ARGTYPE t)
{
	CString strTxt;
	const auto &ap = p.arguments[t];

	if (p.byteop)
	{
		if (ap.meth)
		{
			strTxt.Format(_T("%06o: %03o"), ap.addr_arg, ap.arg);
		}
		else
		{
			strTxt.Format(_T("R%i: %03o"), ap.reg, ap.arg);
		}
	}
	else
	{
		if (ap.meth)
		{
			strTxt.Format(_T("%06o: %06o"), ap.addr_arg, ap.arg);
		}
		else
		{
			strTxt.Format(_T("R%i: %06o"), ap.reg, ap.arg);
		}
	}

	return strTxt;
}

CString CDisasmCtrl::GetAddrText(const CDbgItemParam &p, const ARGTYPE::ARGTYPE t)
{
	CString strTxt;
	const auto &ap = p.arguments[t];

	if (ap.meth)
	{
		strTxt.Format(_T("%06o"), ap.addr_arg);
	}
	else
	{
		strTxt.Format(_T("R%i"), ap.reg);
	}

	return strTxt;
}

CString CDisasmCtrl::GetFPUArgText(const CDbgItemParam &p, const ARGTYPE::ARGTYPE t)
{
	CString strTxt;
	const auto &ap = p.arguments[t];

	if (ap.meth)
	{
		strTxt.Format(_T("%06o: %06o"), ap.addr_arg, ap.arg);
	}
	else
	{
		strTxt.Format(_T("AC%i: "), ap.reg);
		strTxt += GetFloatNum(ap, 4);
	}

	return strTxt;
}

// преобразуем плавающее число в текст
// вход: size - размер числа 1 в словах, 1, 2, 4
CString CDisasmCtrl::GetFloatNum(const CDbgItemParam::argParam &ap, int size)
{
	CString strTxt;
	uint32_t uf = ap.arg;

	switch (size)
	{
		case 1:
			uf &= 0xffff0000;
			[[fallthrough]];

		case 2:
		{
			// преобразуем порядок. для float
			constexpr uint32_t emask = (0xff000000 >> 1);
			uint8_t e = (uf & emask) >> 23;
			e -= 0201;
			e += 127;   // вот так в ИЕЕЕ
			uf &= ~emask;
			uf |= uint32_t(e) << 23; // вернём порядок на место
			float f = *(reinterpret_cast<float *>(&uf)); // теперь это плавающее число в IEEE754
			strTxt.Format(_T("%g"), f);
		}
		break;

		case 4:
		{
			// теперь PDP double в IEEE754 double
			uint64_t ud = uint64_t(ap.arg) << 32 | uint64_t(ap.argl);
			uint64_t sign = ud & 0x8000000000000000ull;
			constexpr uint64_t emask64 = (0xff00000000000000ull >> 1);
			uint16_t e64 = (ud & emask64) >> 55; //порядок
			e64 -= 0201;
			e64 += 1023; // вот так в ИЕЕЕ
			ud >>= 3; // расширим порядок на 3 бита
			ud &= ~0xfff0000000000000ull; // оставим только мантиссу
			ud |= uint64_t(e64) << 52 | sign;
			double d = *(reinterpret_cast<double *>(&ud));
			strTxt.Format(_T("%g"), d);
		}
		break;
	}

	return strTxt;
}

