#include "pch.h"

#include "DisasmView.h"
#include "Config.h"
#include "Platform.h"

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

/////////////////////////////////////////////////////////////////////////////
// CDisasmView
IMPLEMENT_DYNAMIC(CDisasmView, CDocPaneDlgViewBase)

CDisasmView::CDisasmView()
	: m_strDisasmAddr(_T("000000"))
{
}

CDisasmView::~CDisasmView()
    = default;

void CDisasmView::DoDataExchange(CDataExchange *pDX)
{
	CDocPaneDlgViewBase::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_LIST_DA_DISASM, m_ListDisasm);
	DDX_Control(pDX, IDC_EDIT_DA_DBGADDR, m_EditAddr);
	DDX_Text(pDX, IDC_EDIT_DA_DBGADDR, m_strDisasmAddr);
	DDX_Check(pDX, IDC_CHECK_DA_MMG, g_Config.m_bMMG);
	DDX_Check(pDX, IDC_CHECK_DA_EIS, g_Config.m_bEIS);
	DDX_Check(pDX, IDC_CHECK_DA_FIS, g_Config.m_bFIS);
	DDX_Check(pDX, IDC_CHECK_DA_FPU, g_Config.m_bFPU);
}

BEGIN_MESSAGE_MAP(CDisasmView, CDocPaneDlgViewBase)
	ON_MESSAGE(WM_INITDIALOG, &CDisasmView::HandleInitDialog)
	ON_WM_SETFOCUS()
	ON_MESSAGE(WM_DBG_TOP_ADDRESS_UPDATE, &CDisasmView::OnDisasmTopAddressUpdate)
	ON_MESSAGE(WM_DBG_TOP_ADDRESS_SET, &CDisasmView::OnDisasmTopAddressSet)
	ON_MESSAGE(WM_DBG_CURRENT_ADDRESS_CHANGE, &CDisasmView::OnDisasmCurrentAddressChange)
	ON_MESSAGE(WM_DBG_DISASM_BP_UPDATE, &CDisasmView::OnDisasmBPUpdate)
	ON_BN_CLICKED(IDC_CHECK_DA_MMG, &CDisasmView::ChangeInstructionSet)
	ON_BN_CLICKED(IDC_CHECK_DA_EIS, &CDisasmView::ChangeInstructionSet)
	ON_BN_CLICKED(IDC_CHECK_DA_FIS, &CDisasmView::ChangeInstructionSet)
	ON_BN_CLICKED(IDC_CHECK_DA_FPU, &CDisasmView::ChangeInstructionSet)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// обработчики сообщений CDisasmView

LRESULT CDisasmView::HandleInitDialog(WPARAM wp, LPARAM lp)
{
	CDocPaneDlgViewBase::HandleInitDialog(wp, lp);
	m_EditAddr.SetLimitText(6);
	m_ToolTip.AddTool(GetDlgItem(IDC_EDIT_DA_DBGADDR), IDS_TOOLTIP_EDIT_MD_DUMPADDR);
	m_ToolTip.AddTool(GetDlgItem(IDC_CHECK_DA_MMG), IDS_TOOLTIP_CHECK_DA_MMG);
	m_ToolTip.AddTool(GetDlgItem(IDC_CHECK_DA_EIS), IDS_TOOLTIP_CHECK_DA_EIS);
	m_ToolTip.AddTool(GetDlgItem(IDC_CHECK_DA_FIS), IDS_TOOLTIP_CHECK_DA_FIS);
	m_ToolTip.AddTool(GetDlgItem(IDC_CHECK_DA_FPU), IDS_TOOLTIP_CHECK_DA_FPU);
	MakeSummary();
	m_ListDisasm.SetFont(&m_hFont, FALSE);
	m_ListDisasm.Init();
	return TRUE; // return TRUE unless you set the focus to a control
	// EXCEPTION: OCX Property Pages should return FALSE
}

void CDisasmView::OnSetFocus(CWnd *pOldWnd)
{
	m_ListDisasm.SetFocus();
}


// инициализация.
void CDisasmView::InitDisasm(const uint16_t addr)
{
	SetDisasmAddr(addr);			// задаём начальный адрес
	m_ListDisasm.SetSelectionMark(0); // выделяем первую строку
	m_ListDisasm.Invalidate(FALSE); // перерисуем дизассемблер
}

// получить адрес для строки под курсором
uint16_t CDisasmView::GetCursorAddr() const
{
	return m_ListDisasm.GetCursorAddr();
}

// получить адрес для строки за нижней строкой
uint16_t CDisasmView::GetBottomAddr() const
{
	return m_ListDisasm.GetBottomAddr();
}

// сводка по текущим функциональным опциям эмулятора
void CDisasmView::MakeSummary()
{
	CString str;
	str.Format(_T("CPU: %s\nБаг бита С: %s\nТочное поведение регистра 177702: %s\nЭмуляция EIS: %s\nЭмуляция FIS: %s\nЭмуляция FPU: %s"),
			   g_Config.m_bVM1G ? _T("1801ВМ1Г") : _T("1801ВМ1А"),
			   g_Config.m_bEmulateCBug ? _T("Да") : _T("Нет"),
			   g_Config.m_bEmulate177702 ? _T("Да") : _T("Нет"),
			   g_Config.m_bEmulateEIS ? _T("Да") : _T("Нет"),
			   g_Config.m_bEmulateFIS ? _T("Да") : _T("Нет"),
			   g_Config.m_bEmulateFPU ? _T("Да") : _T("Нет")
	);
	m_ToolTip.DelTool(GetDlgItem(IDC_STATIC_DA_SUMMARY));
	m_ToolTip.AddTool(GetDlgItem(IDC_STATIC_DA_SUMMARY), str);
}

// Внутренняя функция.
// установка нового адреса, с которого начинается дизассемблирование
// при этом перерисовка окна дизассемблера не делается.
void CDisasmView::SetDisasmAddr(const uint16_t addr)
{
	g_Config.m_nDisasmAddr = addr; // сохраняем новое значение в глобальной переменной
	m_strDisasmAddr = Global::WordToOctString(g_Config.m_nDisasmAddr); // и для контрола значение сделаем
	UpdateData(FALSE); // обновим контрол
}

// это сообщение приходит из контрола - поля ввода адреса
// когда там делаются изменения, чтобы перерисовать список
LRESULT CDisasmView::OnDisasmTopAddressUpdate(WPARAM, LPARAM)
{
	UpdateData(TRUE); // примем в переменную, что мы там наредактировали
	const uint16_t nAddr = Global::OctStringToWord(m_strDisasmAddr); // получим числовое значение
	// обновим значение, чтобы оно было всегда 6 значным
	SetDisasmAddr(nAddr);  // обновим адрес в поле адреса.
	m_ListDisasm.Invalidate(FALSE); // перерисуем дизассемблер
	return S_OK;
}

// это сообщение передаётся из списка m_ListDisasm сюда, когда там в результате
// действий меняется адрес, и надо его обновить
LRESULT CDisasmView::OnDisasmTopAddressSet(WPARAM wp, LPARAM)
{
	SetDisasmAddr(static_cast<uint16_t>(wp));
	return S_OK;
}

// это сообщение передаётся из отладчика когда происходит отладочный останов
// или ручное изменение значения PC (обычно в отладочном останове)
LRESULT CDisasmView::OnDisasmCurrentAddressChange(WPARAM wp, LPARAM)
{
	const auto wCurAddr = static_cast<uint16_t>(wp);
	if (g_Config.m_nDisasmAddr <= wCurAddr && wCurAddr < m_ListDisasm.GetBottomAddr())
	{
		// то ничего не делаем.
		// но можно следить, чтобы не достигали самого низа и не начинали с самого верха.
		int nCount = m_ListDisasm.GetLinesCount();
		int nCurr = m_ListDisasm.GetLineFromAddr(wCurAddr);
		if (nCurr >= 0 && nCount > 6)
		{
			if (nCurr >= nCount - 3)
			{
				m_ListDisasm.DisasmStepDn(nCount - 6);
			}
		}
		else
		{
			// когда что-то не так или окошко слишком маленькое
			// будем выводить с начала
			SetDisasmAddr(wCurAddr);
		}
	}
	else
	{
		SetDisasmAddr(wCurAddr - 8);
		// тут может получиться так, что у нас не будет правильно дизассемблировано
	}

	//m_ListDisasm.Invalidate(FALSE); // перерисуем дизассемблер
	m_ListDisasm.RedrawWindow(); // вот так перерисуем, чтобы синхронно было
	// и теперь проверим, вдруг не получилось правильно дизассемблировать
	if (m_ListDisasm.GetLineFromAddr(wCurAddr) < 0)
	{
		// и сделаем по старинке
		SetDisasmAddr(wCurAddr);
		m_ListDisasm.Invalidate(FALSE); // перерисуем дизассемблер
	}

	return S_OK;
}

// когда мы изменяем точки останова в окне течек останова
// нужно перерисовать окно дизассемблера
LRESULT CDisasmView::OnDisasmBPUpdate(WPARAM, LPARAM)
{
	m_ListDisasm.Invalidate(FALSE);
	return S_OK;
}

// когда мы меняем набор поддерживаемых инструкций
// надо перерисовать окно дизассемблера
void CDisasmView::ChangeInstructionSet()
{
	UpdateData(TRUE);
	g_Platform.GetDebuggerPtr()->InitMaps(true);
	m_ListDisasm.Invalidate(FALSE);
	m_ListDisasm.SetFocus();
}
