// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+

#include "MemoryView.h"

#include "Debugger/JsonValueWrapper.h"

#include "QtHost.h"
#include "QtUtils.h"
#include <QtCore/QObject>
#include <QtCore/QPointer>
#include <QtGui/QActionGroup>
#include <QtGui/QClipboard>
#include <QtGui/QMouseEvent>
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QMessageBox>

using namespace QtUtils;

/*
	MemoryViewTable
*/
void MemoryViewTable::UpdateStartAddress(u32 start)
{
	startAddress = start & ~0xF;
}

void MemoryViewTable::UpdateSelectedAddress(u32 selected, bool page)
{
	selectedAddress = selected;
	if (startAddress > selectedAddress)
	{
		if (page)
			startAddress -= 0x10 * rowVisible;
		else
			startAddress -= 0x10;
	}
	else if (startAddress + ((rowVisible - 1) * 0x10) < selectedAddress)
	{
		if (page)
			startAddress += 0x10 * rowVisible;
		else
			startAddress += 0x10;
	}
}

void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32 height, DebugInterface& cpu)
{
	rowHeight = painter.fontMetrics().height() + 2;
	const s32 charWidth = painter.fontMetrics().averageCharWidth();
	const s32 x = charWidth; // Left padding
	const s32 y = rowHeight;
	const s32 displayTypeWidth = MemoryViewTypeWidth[static_cast<s32>(displayType)];
	const s32 displayTypeVisualWidth = MemoryViewTypeVisualWidth[static_cast<s32>(displayType)];
	rowVisible = (height / rowHeight);
	rowCount = rowVisible + 1;

	row1YAxis = 0;

	// Draw the row addresses
	painter.setPen(palette.text().color());
	for (u32 i = 0; i < rowCount; i++)
	{
		painter.drawText(x, y + (rowHeight * i), FilledQStringFromValue(startAddress + (i * 0x10), 16));
	}
	valuexAxis = x + (charWidth * 8);

	// Draw the row values
	for (u32 i = 0; i < rowCount; i++)
	{
		const u32 currentRowAddress = startAddress + (i * 0x10);
		s32 valX = valuexAxis;
		segmentXAxis[0] = valX;
		for (int j = 0; j < 16 / displayTypeWidth; j++)
		{
			valX += charWidth;
			const u32 thisSegmentsStart = currentRowAddress + (j * displayTypeWidth);

			segmentXAxis[j] = valX;

			bool penDefault = false;
			if ((selectedAddress & ~0xF) == currentRowAddress)
			{
				if (selectedAddress >= thisSegmentsStart && selectedAddress < (thisSegmentsStart + displayTypeWidth))
				{ // If the current byte and row we are drawing is selected
					if (!selectedText)
					{
						s32 charsIntoSegment = 0;
						if (displayType == MemoryViewType::FLOAT)
						{
							charsIntoSegment = selectedIndex;
						}
						else
						{
							charsIntoSegment = ((selectedAddress - thisSegmentsStart) * 2) + ((selectedNibbleHI ? 0 : 1) ^ littleEndian);
						}

						if (littleEndian)
							charsIntoSegment = displayTypeVisualWidth - charsIntoSegment - 1;
						painter.setPen(QColor::fromRgb(205, 165, 0)); // SELECTED NIBBLE LINE COLOUR
						const QPoint lineStart(valX + (charsIntoSegment * charWidth) + 1, y + (rowHeight * i));
						painter.drawLine(lineStart, lineStart + QPoint(charWidth - 3, 0));
					}
					painter.setPen(QColor::fromRgb(0xaa, 0x22, 0x22)); // SELECTED BYTE COLOUR
				}
				else
				{
					penDefault = true;
					painter.setPen(palette.text().color()); // Default colour
				}
			}
			else
			{
				penDefault = true;
				painter.setPen(palette.text().color()); // Default colour
			}

			bool valid;
			switch (displayType)
			{
				case MemoryViewType::BYTE:
				{
					const u8 val = static_cast<u8>(cpu.read8(thisSegmentsStart, valid));
					if (penDefault && val == 0)
						painter.setPen(QColor::fromRgb(145, 145, 155)); // ZERO BYTE COLOUR
					painter.drawText(valX, y + (rowHeight * i), valid ? FilledQStringFromValue(val, 16) : "??");
					break;
				}
				case MemoryViewType::BYTEHW:
				{
					const u16 val = convertEndian<u16>(static_cast<u16>(cpu.read16(thisSegmentsStart, valid)));
					if (penDefault && val == 0)
						painter.setPen(QColor::fromRgb(145, 145, 155)); // ZERO BYTE COLOUR
					painter.drawText(valX, y + (rowHeight * i), valid ? FilledQStringFromValue(val, 16) : "????");
					break;
				}
				case MemoryViewType::WORD:
				{
					const u32 val = convertEndian<u32>(cpu.read32(thisSegmentsStart, valid));
					if (penDefault && val == 0)
						painter.setPen(QColor::fromRgb(145, 145, 155)); // ZERO BYTE COLOUR
					painter.drawText(valX, y + (rowHeight * i), valid ? FilledQStringFromValue(val, 16) : "????????");
					break;
				}
				case MemoryViewType::DWORD:
				{
					const u64 val = convertEndian<u64>(cpu.read64(thisSegmentsStart, valid));
					if (penDefault && val == 0)
						painter.setPen(QColor::fromRgb(145, 145, 155)); // ZERO BYTE COLOUR
					painter.drawText(valX, y + (rowHeight * i), valid ? FilledQStringFromValue(val, 16) : "????????????????");
					break;
				}
				case MemoryViewType::FLOAT:
				{
					const u32 intVal = convertEndian<u32>(cpu.read32(thisSegmentsStart, valid));
					float val = 0.0;
					std::memcpy(&val, &intVal, sizeof(val));
					if (penDefault && val == 0.0)
						painter.setPen(QColor::fromRgb(145, 145, 155)); // ZERO BYTE COLOUR
					QString floatStr = QString::number(val, 'g');
					painter.drawText(valX, y + (rowHeight * i), valid ? QString("%1").arg(floatStr, displayTypeVisualWidth) : "??????????????");
					break;
				}
			}
			valX += charWidth * displayTypeVisualWidth;
		}

		// valX is our new X position after the hex values
		valX = valX + 6;
		textXAxis = valX;

		// Print the string representation
		for (s32 j = 0; j < 16; j++)
		{
			if (selectedAddress == j + currentRowAddress)
				painter.setPen(palette.highlight().color());
			else
				painter.setPen(palette.text().color());

			bool valid;
			const u8 value = cpu.read8(currentRowAddress + j, valid);
			if (valid)
			{
				QChar curChar = QChar::fromLatin1(value);
				if (!curChar.isPrint() && curChar != ' ') // Default to '.' for unprintable characters
					curChar = '.';

				painter.drawText(valX, y + (rowHeight * i), curChar);
			}
			else
			{
				painter.drawText(valX, y + (rowHeight * i), "?");
			}
			valX += charWidth + 1;
		}
	}
}

void MemoryViewTable::SelectAt(QPoint pos)
{
	// Check if SelectAt was called before DrawTable.
	if (rowHeight == 0)
		return;

	const u32 selectedRow = (pos.y() - 2) / (rowHeight);
	const s32 x = pos.x();
	const s32 avgSegmentWidth = segmentXAxis[1] - segmentXAxis[0];
	const s32 displayTypeWidth = MemoryViewTypeWidth[static_cast<s32>(displayType)];
	const s32 displayTypeVisualWidth = MemoryViewTypeVisualWidth[static_cast<s32>(displayType)];
	const u32 nibbleWidth = (avgSegmentWidth / displayTypeVisualWidth);
	selectedAddress = (selectedRow * 0x10) + startAddress;

	if (x <= segmentXAxis[0])
	{
		// The user clicked before the first segment
		selectedText = false;
		if (littleEndian)
			selectedAddress += displayTypeWidth - 1;
		selectedNibbleHI = true;
	}
	else if (x > valuexAxis && x < textXAxis)
	{
		selectedText = false;
		// The user clicked inside of the hexadecimal area
		for (s32 i = 0; i < 16; i++)
		{
			if (i == ((16 / displayTypeWidth) - 1) || (x >= segmentXAxis[i] && x < (segmentXAxis[i + 1])))
			{
				u32 indexInSegment = (x - segmentXAxis[i]) / nibbleWidth;
				if (littleEndian)
					indexInSegment = displayTypeVisualWidth - indexInSegment - 1;
				selectedIndex = indexInSegment;
				if (displayType == MemoryViewType::FLOAT)
				{
					// Selecting float always points to starting address of float
					selectedAddress = selectedAddress + i * displayTypeWidth;
					selectedNibbleHI = false;
				}
				else
				{
					selectedAddress = selectedAddress + i * displayTypeWidth + (indexInSegment / 2);
					selectedNibbleHI = littleEndian ? indexInSegment & 1 : !(indexInSegment & 1);
				}
				break;
			}
		}
	}
	else if (x >= textXAxis)
	{
		selectedText = true;
		// The user clicked the text area
		selectedAddress += std::min((x - textXAxis) / 8, 15);
	}
}

u128 MemoryViewTable::GetSelectedSegment(DebugInterface& cpu)
{
	u128 val;
	switch (displayType)
	{
		case MemoryViewType::BYTE:
			val.lo = cpu.read8(selectedAddress);
			break;
		case MemoryViewType::BYTEHW:
			val.lo = convertEndian(static_cast<u16>(cpu.read16(selectedAddress & ~1)));
			break;
		case MemoryViewType::WORD:
			val.lo = convertEndian(cpu.read32(selectedAddress & ~3));
			break;
		case MemoryViewType::DWORD:
			val._u64[0] = convertEndian(cpu.read64(selectedAddress & ~7));
			break;
		case MemoryViewType::FLOAT:
			val.lo = convertEndian(cpu.read32(selectedAddress & ~3));
			break;
	}
	return val;
}

void MemoryViewTable::InsertIntoSelectedHexView(u8 value, DebugInterface& cpu)
{
	const u8 mask = selectedNibbleHI ? 0x0f : 0xf0;
	u8 curVal = cpu.read8(selectedAddress) & mask;
	u8 newVal = value << (selectedNibbleHI ? 4 : 0);
	curVal |= newVal;

	const QPointer<MemoryViewTable> table(this);
	Host::RunOnCPUThread([table, address = selectedAddress, &cpu, val = curVal] {
		cpu.write8(address, val);

		QtHost::RunOnUIThread([table] {
			if (!table)
				return;

			table->parent->update();
		});
	});
}

bool MemoryViewTable::InsertFloatIntoSelectedHexView(DebugInterface& cpu)
{
	// Get currently selected float as string
	const u32 currentIntVal = GetSelectedSegment(cpu).lo;
	float currentFloatVal = 0;
	std::memcpy(&currentFloatVal, &currentIntVal, sizeof(currentFloatVal));
	const QString currentfloatStr = QString("%1").arg(QString::number(currentFloatVal, 'g'), 14).trimmed();

	// Prompt user to enter a new float value
	bool isValidInput = false;
	QString newFloatStr = QInputDialog::getText(parent, tr("Input New Float"), "",
		QLineEdit::Normal, currentfloatStr, &isValidInput);
	if (!isValidInput)
		return false;

	// Convert string into float value
	bool isValidFloat = false;
	const float newFloatVal = newFloatStr.toFloat(&isValidFloat);
	if (!isValidFloat)
	{
		QMessageBox::warning(parent, tr("Input Error"), tr("Invalid float value"));
		return false;
	}

	// Write new float value back to memory
	u32 newIntVal = 0;
	std::memcpy(&newIntVal, &newFloatVal, sizeof(newIntVal));
	newIntVal = convertEndian(newIntVal);

	const QPointer<MemoryViewTable> table(this);
	Host::RunOnCPUThread([table, address = selectedAddress, &cpu, val = newIntVal] {
		cpu.write32(address, val);

		QtHost::RunOnUIThread([table] {
			if (!table)
				return;

			table->parent->update();
		});
	});

	return true;
}

void MemoryViewTable::InsertAtCurrentSelection(const QString& text, DebugInterface& cpu)
{
	if (!cpu.isValidAddress(selectedAddress))
		return;

	if (displayType == MemoryViewType::FLOAT)
	{
		// Convert string into float value
		bool isValidFloat = false;
		const float newFloatVal = text.toFloat(&isValidFloat);
		if (!isValidFloat)
		{
			QMessageBox::warning(parent, tr("Input Error"), tr("Invalid float value"));
			return;
		}

		// Write new float value back to memory
		u32 newIntVal = 0;
		std::memcpy(&newIntVal, &newFloatVal, sizeof(newIntVal));
		newIntVal = convertEndian(newIntVal);

		const QPointer<MemoryViewTable> table(this);
		Host::RunOnCPUThread([table, address = selectedAddress, &cpu, val = newIntVal] {
			cpu.write32(address, val);

			QtHost::RunOnUIThread([table] {
				if (!table)
					return;

				table->parent->update();
			});
		});
	}
	else
	{
		// If pasting into the hex view, also decode the input as hex bytes.
		// This approach prevents one from pasting on a nibble boundary, but that is almost always
		// user error, and we don't have an undo function in this view, so best to stay conservative.
		QByteArray input = selectedText ? text.toUtf8() : QByteArray::fromHex(text.toUtf8());

		const QPointer<MemoryViewTable> table(this);
		const MemoryViewType display_type = displayType;
		const bool little_endian = littleEndian;
		Host::RunOnCPUThread([table, address = selectedAddress, &cpu, input, display_type, little_endian] {
			u32 currAddr = address;
			for (int i = 0; i < input.size(); i++)
			{
				cpu.write8(currAddr, input[i]);
				currAddr = nextAddress(currAddr, address, display_type, little_endian);
			}

			u32 end_address = address + input.size();
			QtHost::RunOnUIThread([table, end_address] {
				if (!table)
					return;

				table->UpdateSelectedAddress(end_address);
				table->parent->update();
			});
		});
	}
}

u32 MemoryViewTable::nextAddress(u32 addr, u32 selected_address, MemoryViewType display_type, bool little_endian)
{
	if (!little_endian)
	{
		return addr + 1;
	}
	else
	{
		if (selected_address % MemoryViewTypeWidth[static_cast<s32>(display_type)] == 0)
			return addr + (MemoryViewTypeWidth[static_cast<s32>(display_type)] * 2 - 1);
		else
			return addr - 1;
	}
}

u32 MemoryViewTable::prevAddress(u32 addr, u32 selected_address, MemoryViewType display_type, bool little_endian)
{
	if (!little_endian)
	{
		return addr - 1;
	}
	else
	{
		// It works
		if ((addr & (static_cast<u32>(display_type) - 1)) == (static_cast<u32>(display_type) - 1))
			return addr - (MemoryViewTypeWidth[static_cast<s32>(display_type)] * 2 - 1);
		else
			return selected_address + 1;
	}
}

void MemoryViewTable::ForwardSelection()
{
	if (displayType == MemoryViewType::FLOAT)
	{
		if (!littleEndian)
		{
			// Bump to next address if selection is at end of current float segment
			if (selectedIndex >= MemoryViewTypeVisualWidth[static_cast<s32>(MemoryViewType::FLOAT)] - 1)
			{
				UpdateSelectedAddress(selectedAddress + 4);
				selectedIndex = 0;
			}
			else
			{
				selectedIndex++;
			}
		}
		else
		{
			if (selectedIndex <= 0)
			{
				UpdateSelectedAddress(selectedAddress + 4);
				selectedIndex = MemoryViewTypeVisualWidth[static_cast<s32>(MemoryViewType::FLOAT)] - 1;
			}
			else
			{
				selectedIndex--;
			}
		}
	}
	else
	{
		if (!littleEndian)
		{
			if ((selectedNibbleHI = !selectedNibbleHI))
				UpdateSelectedAddress(selectedAddress + 1);
		}
		else
		{
			if ((selectedNibbleHI = !selectedNibbleHI))
			{
				if (selectedAddress % MemoryViewTypeWidth[static_cast<s32>(displayType)] == 0)
					UpdateSelectedAddress(selectedAddress + (MemoryViewTypeVisualWidth[static_cast<s32>(displayType)] - 1));
				else
					UpdateSelectedAddress(selectedAddress - 1);
			}
		}
	}
}

void MemoryViewTable::BackwardSelection()
{
	const s32 displayTypeWidth = MemoryViewTypeWidth[static_cast<s32>(displayType)];
	const s32 displayTypeVisualWidth = MemoryViewTypeVisualWidth[static_cast<s32>(displayType)];

	if (displayType == MemoryViewType::FLOAT)
	{
		if (!littleEndian)
		{
			// Bump to previous address if selection is at beginning of current float segment
			if (selectedIndex <= 0)
			{
				UpdateSelectedAddress(selectedAddress - 4);
				selectedIndex = displayTypeVisualWidth - 1;
			}
			else
			{
				selectedIndex--;
			}
		}
		else
		{
			if (selectedIndex >= displayTypeVisualWidth - 1)
			{
				UpdateSelectedAddress(selectedAddress - 4);
				selectedIndex = 0;
			}
			else
			{
				selectedIndex++;
			}
		}
	}
	else
	{
		if (!littleEndian)
		{
			if (!(selectedNibbleHI = !selectedNibbleHI))
				UpdateSelectedAddress(selectedAddress - 1);
		}
		else
		{
			if (!(selectedNibbleHI = !selectedNibbleHI))
			{
				// It works
				if ((selectedAddress & (static_cast<u32>(displayTypeWidth) - 1)) == (static_cast<u32>(displayTypeWidth) - 1))
					UpdateSelectedAddress(selectedAddress - (displayTypeVisualWidth - 1));
				else
					UpdateSelectedAddress(selectedAddress + 1);
			}
		}
	}
}


// We need both key and keychar because `key` is easy to use, but is case insensitive
bool MemoryViewTable::KeyPress(int key, QChar keychar, DebugInterface& cpu)
{
	if (!cpu.isValidAddress(selectedAddress))
		return false;

	bool pressHandled = false;

	const bool keyCharIsText = keychar.isLetterOrNumber() || keychar.isSpace();

	if (selectedText)
	{
		if (keyCharIsText || (!keychar.isNonCharacter() && keychar.category() != QChar::Other_Control))
		{
			const QPointer<MemoryViewTable> table(this);
			Host::RunOnCPUThread([table, address = selectedAddress, &cpu, val = keychar.toLatin1()] {
				cpu.write8(address, val);

				QtHost::RunOnUIThread([table, address]() {
					if (!table)
						return;

					table->UpdateSelectedAddress(address + 1);
					table->parent->update();
				});
			});
			pressHandled = true;
		}

		switch (key)
		{
			case Qt::Key::Key_Backspace:
			case Qt::Key::Key_Escape:
			{
				const QPointer<MemoryViewTable> table(this);
				Host::RunOnCPUThread([table, address = selectedAddress, &cpu] {
					cpu.write8(address, 0);

					QtHost::RunOnUIThread([table] {
						if (!table)
							return;

						table->BackwardSelection();
						table->parent->update();
					});
				});
				pressHandled = true;
				break;
			}
			case Qt::Key::Key_Right:
				ForwardSelection();
				pressHandled = true;
				break;
			case Qt::Key::Key_Left:
				BackwardSelection();
				pressHandled = true;
				break;
			default:
				break;
		}
	}
	else
	{
		// Hex view is selected

		if (keyCharIsText)
		{
			if (displayType != MemoryViewType::FLOAT)
			{
				// Check if key pressed is hex before insertion (QString conversion fails otherwise)
				const u8 keyPressed = static_cast<u8>(QString(QChar(key)).toInt(&pressHandled, 16));
				if (pressHandled)
				{
					InsertIntoSelectedHexView(keyPressed, cpu);
					ForwardSelection();
				}
			}
		}

		switch (key)
		{
			case Qt::Key::Key_Backspace:
			case Qt::Key::Key_Escape:
				InsertIntoSelectedHexView(0, cpu);
				BackwardSelection();
				pressHandled = true;
				break;
			case Qt::Key::Key_Right:
				ForwardSelection();
				pressHandled = true;
				break;
			case Qt::Key::Key_Left:
				BackwardSelection();
				pressHandled = true;
				break;
			case Qt::Key::Key_Return:
			case Qt::Key::Key_Enter:
				if (displayType == MemoryViewType::FLOAT)
				{
					pressHandled = InsertFloatIntoSelectedHexView(cpu);
				}
				break;
			default:
				break;
		}
	}

	// Keybinds that are the same for the text and hex view

	switch (key)
	{
		case Qt::Key::Key_Up:
			UpdateSelectedAddress(selectedAddress - 0x10);
			pressHandled = true;
			break;
		case Qt::Key::Key_PageUp:
			UpdateSelectedAddress(selectedAddress - (0x10 * rowVisible), true);
			pressHandled = true;
			break;
		case Qt::Key::Key_Down:
			UpdateSelectedAddress(selectedAddress + 0x10);
			pressHandled = true;
			break;
		case Qt::Key::Key_PageDown:
			UpdateSelectedAddress(selectedAddress + (0x10 * rowVisible), true);
			pressHandled = true;
			break;
		default:
			break;
	}

	return pressHandled;
}

/*
	MemoryView
*/
MemoryView::MemoryView(const DebuggerViewParameters& parameters)
	: DebuggerView(parameters, MONOSPACE_FONT)
	, m_table(this)
{
	ui.setupUi(this);

	setFocusPolicy(Qt::FocusPolicy::ClickFocus);

	setContextMenuPolicy(Qt::CustomContextMenu);
	connect(this, &MemoryView::customContextMenuRequested, this, &MemoryView::openContextMenu);

	m_table.UpdateStartAddress(0x100000);

	receiveEvent<DebuggerEvents::Refresh>([this](const DebuggerEvents::Refresh& event) -> bool {
		update();
		return true;
	});

	receiveEvent<DebuggerEvents::GoToAddress>([this](const DebuggerEvents::GoToAddress& event) -> bool {
		if (event.filter != DebuggerEvents::GoToAddress::NONE &&
			event.filter != DebuggerEvents::GoToAddress::MEMORY_VIEW)
			return false;

		gotoAddress(event.address);

		if (event.switch_to_tab)
			switchToThisTab();

		return true;
	});
}

MemoryView::~MemoryView() = default;

void MemoryView::toJson(JsonValueWrapper& json)
{
	DebuggerView::toJson(json);

	json.value().AddMember("startAddress", m_table.startAddress, json.allocator());
	json.value().AddMember("viewType", static_cast<int>(m_table.GetViewType()), json.allocator());
	json.value().AddMember("littleEndian", m_table.GetLittleEndian(), json.allocator());
}

bool MemoryView::fromJson(const JsonValueWrapper& json)
{
	if (!DebuggerView::fromJson(json))
		return false;

	auto start_address = json.value().FindMember("startAddress");
	if (start_address != json.value().MemberEnd() && start_address->value.IsUint())
		m_table.UpdateStartAddress(start_address->value.GetUint());

	auto view_type = json.value().FindMember("viewType");
	if (view_type != json.value().MemberEnd() && view_type->value.IsInt())
	{
		MemoryViewType type = static_cast<MemoryViewType>(view_type->value.GetInt());
		if (type == MemoryViewType::BYTE ||
			type == MemoryViewType::BYTEHW ||
			type == MemoryViewType::WORD ||
			type == MemoryViewType::DWORD ||
			type == MemoryViewType::FLOAT)
			m_table.SetViewType(type);
	}

	auto little_endian = json.value().FindMember("littleEndian");
	if (little_endian != json.value().MemberEnd() && little_endian->value.IsBool())
		m_table.SetLittleEndian(little_endian->value.GetBool());

	repaint();

	return true;
}

void MemoryView::paintEvent(QPaintEvent* event)
{
	QPainter painter(this);

	painter.fillRect(rect(), palette().window());

	if (!cpu().isAlive())
		return;

	m_table.DrawTable(painter, this->palette(), this->height(), cpu());
}

void MemoryView::mousePressEvent(QMouseEvent* event)
{
	if (!cpu().isAlive())
		return;

	m_table.SelectAt(event->pos());
	repaint();
}

void MemoryView::openContextMenu(QPoint pos)
{
	if (!cpu().isAlive())
		return;

	QMenu* menu = new QMenu(this);
	menu->setAttribute(Qt::WA_DeleteOnClose);

	QAction* copy_action = menu->addAction(tr("Copy Address"));
	connect(copy_action, &QAction::triggered, this, [this]() {
		QApplication::clipboard()->setText(QString::number(m_table.selectedAddress, 16).toUpper());
	});

	createEventActions<DebuggerEvents::GoToAddress>(menu, [this]() {
		DebuggerEvents::GoToAddress event;
		event.address = m_table.selectedAddress;
		return std::optional(event);
	});

	QAction* go_to_address_action = menu->addAction(tr("Go to Address"));
	connect(go_to_address_action, &QAction::triggered, this, [this]() { contextGoToAddress(); });

	QAction* follow_address_action = menu->addAction(tr("Follow Address"));
	connect(follow_address_action, &QAction::triggered, this, [this]() { contextFollowAddress(); });

	menu->addSeparator();

	QAction* endian_action = menu->addAction(tr("Show as Little Endian"));
	endian_action->setCheckable(true);
	endian_action->setChecked(m_table.GetLittleEndian());
	connect(endian_action, &QAction::triggered, this, [this, endian_action]() {
		m_table.SetLittleEndian(endian_action->isChecked());
	});

	const MemoryViewType current_view_type = m_table.GetViewType();

	// View Types
	QActionGroup* view_type_group = new QActionGroup(menu);
	view_type_group->setExclusive(true);

	QAction* byte_action = menu->addAction(tr("Show as 1 byte"));
	byte_action->setCheckable(true);
	byte_action->setChecked(current_view_type == MemoryViewType::BYTE);
	connect(byte_action, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::BYTE); });
	view_type_group->addAction(byte_action);

	QAction* bytehw_action = menu->addAction(tr("Show as 2 bytes"));
	bytehw_action->setCheckable(true);
	bytehw_action->setChecked(current_view_type == MemoryViewType::BYTEHW);
	connect(bytehw_action, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::BYTEHW); });
	view_type_group->addAction(bytehw_action);

	QAction* word_action = menu->addAction(tr("Show as 4 bytes"));
	word_action->setCheckable(true);
	word_action->setChecked(current_view_type == MemoryViewType::WORD);
	connect(word_action, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::WORD); });
	view_type_group->addAction(word_action);

	QAction* dword_action = menu->addAction(tr("Show as 8 bytes"));
	dword_action->setCheckable(true);
	dword_action->setChecked(current_view_type == MemoryViewType::DWORD);
	connect(dword_action, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::DWORD); });
	view_type_group->addAction(dword_action);

	QAction* float_action = menu->addAction(tr("Show as float"));
	float_action->setCheckable(true);
	float_action->setChecked(current_view_type == MemoryViewType::FLOAT);
	connect(float_action, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::FLOAT); });
	view_type_group->addAction(float_action);

	menu->addSeparator();

	createEventActions<DebuggerEvents::AddToSavedAddresses>(menu, [this]() {
		DebuggerEvents::AddToSavedAddresses event;
		event.address = m_table.selectedAddress;
		return std::optional(event);
	});

	QAction* copy_byte_action = menu->addAction(tr("Copy Byte"));
	copy_byte_action->setEnabled(current_view_type != MemoryViewType::FLOAT);
	connect(copy_byte_action, &QAction::triggered, this, &MemoryView::contextCopyByte);

	connect(menu->addAction(tr("Copy Segment")), &QAction::triggered, this, &MemoryView::contextCopySegment);

	QAction* copy_char_action = menu->addAction(tr("Copy Character"));
	copy_char_action->setEnabled(current_view_type != MemoryViewType::FLOAT);
	connect(copy_char_action, &QAction::triggered, this, &MemoryView::contextCopyCharacter);

	connect(menu->addAction(tr("Paste")), &QAction::triggered, this, &MemoryView::contextPaste);

	menu->popup(this->mapToGlobal(pos));

	this->repaint();
	return;
}

void MemoryView::contextCopyByte()
{
	QApplication::clipboard()->setText(QString::number(cpu().read8(m_table.selectedAddress), 16).toUpper());
}

void MemoryView::contextCopySegment()
{
	if (m_table.GetViewType() == MemoryViewType::FLOAT)
	{
		u32 intVal = m_table.GetSelectedSegment(cpu()).lo;
		float val = 0;
		std::memcpy(&val, &intVal, sizeof(val));
		QApplication::clipboard()->setText(QString::number(val, 'g'));
	}
	else
	{
		QApplication::clipboard()->setText(QString::number(m_table.GetSelectedSegment(cpu()).lo, 16).toUpper());
	}
}

void MemoryView::contextCopyCharacter()
{
	QApplication::clipboard()->setText(QChar::fromLatin1(cpu().read8(m_table.selectedAddress)).toUpper());
}

void MemoryView::contextPaste()
{
	m_table.InsertAtCurrentSelection(QApplication::clipboard()->text(), cpu());
}

void MemoryView::contextGoToAddress()
{
	bool ok;
	QString targetString = QInputDialog::getText(this, tr("Go To In Memory View"), "",
		QLineEdit::Normal, "", &ok);

	if (!ok)
		return;

	u64 address = 0;
	std::string error;
	if (!cpu().evaluateExpression(targetString.toStdString().c_str(), address, error))
	{
		QMessageBox::warning(this, tr("Cannot Go To"), QString::fromStdString(error));
		return;
	}

	gotoAddress(static_cast<u32>(address));
}

void MemoryView::contextFollowAddress()
{
	bool valid;
	u32 address = cpu().read32(m_table.selectedAddress & ~3, valid);
	if (!valid)
		return;

	gotoAddress(address);
}

void MemoryView::mouseDoubleClickEvent(QMouseEvent* event)
{
}

void MemoryView::wheelEvent(QWheelEvent* event)
{
	if (event->angleDelta().y() < 0)
	{
		m_table.UpdateStartAddress(m_table.startAddress + 0x10);
	}
	else if (event->angleDelta().y() > 0)
	{
		m_table.UpdateStartAddress(m_table.startAddress - 0x10);
	}
	this->repaint();
}

void MemoryView::keyPressEvent(QKeyEvent* event)
{
	if (!m_table.KeyPress(event->key(), event->text().size() ? event->text()[0] : '\0', cpu()))
	{
		switch (event->key())
		{
			case Qt::Key_G:
				contextGoToAddress();
				break;
			case Qt::Key_C:
				if (event->modifiers() & Qt::ControlModifier)
					contextCopySegment();
				break;
			default:
				break;
		}
	}
	this->repaint();
	DebuggerView::broadcastEvent(DebuggerEvents::VMUpdate());
}

void MemoryView::gotoAddress(u32 address)
{
	m_table.UpdateStartAddress(address & ~0xF);
	m_table.selectedAddress = address;
	this->repaint();
	this->setFocus();
}
