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

#include "DebuggerView.h"

#include "Debugger/DebuggerWindow.h"
#include "Debugger/JsonValueWrapper.h"
#include "Debugger/Docking/DockManager.h"
#include "Debugger/Docking/DockTables.h"

#include "DebugTools/DebugInterface.h"

#include "common/Assertions.h"

DebuggerView::DebuggerView(const DebuggerViewParameters& parameters, u32 flags)
	: QWidget(parameters.parent)
	, m_id(parameters.id)
	, m_unique_name(parameters.unique_name)
	, m_cpu(parameters.cpu)
	, m_cpu_override(parameters.cpu_override)
	, m_flags(flags)
{
	updateStyleSheet();
}

DebugInterface& DebuggerView::cpu() const
{
	if (m_cpu_override.has_value())
		return DebugInterface::get(*m_cpu_override);

	pxAssertRel(m_cpu, "DebuggerView::cpu called on object with null cpu.");
	return *m_cpu;
}

QString DebuggerView::uniqueName() const
{
	return m_unique_name;
}

u64 DebuggerView::id() const
{
	return m_id;
}

QString DebuggerView::displayName() const
{
	QString name = displayNameWithoutSuffix();

	// If there are multiple debugger views with the same name, append a number
	// to the display name.
	if (m_display_name_suffix_number.has_value())
		name = tr("%1 #%2").arg(name).arg(*m_display_name_suffix_number);

	if (m_cpu_override)
		name = tr("%1 (%2)").arg(name).arg(DebugInterface::cpuName(*m_cpu_override));

	return name;
}

QString DebuggerView::displayNameWithoutSuffix() const
{
	return m_translated_display_name;
}

QString DebuggerView::customDisplayName() const
{
	return m_custom_display_name;
}

bool DebuggerView::setCustomDisplayName(QString display_name)
{
	if (display_name.size() > DockUtils::MAX_DOCK_WIDGET_NAME_SIZE)
		return false;

	m_custom_display_name = display_name;
	return true;
}

bool DebuggerView::isPrimary() const
{
	return m_is_primary;
}

void DebuggerView::setPrimary(bool is_primary)
{
	m_is_primary = is_primary;
}

bool DebuggerView::setCpu(DebugInterface& new_cpu)
{
	BreakPointCpu before = cpu().getCpuType();
	m_cpu = &new_cpu;
	BreakPointCpu after = cpu().getCpuType();
	return before == after;
}

std::optional<BreakPointCpu> DebuggerView::cpuOverride() const
{
	return m_cpu_override;
}

bool DebuggerView::setCpuOverride(std::optional<BreakPointCpu> new_cpu)
{
	BreakPointCpu before = cpu().getCpuType();
	m_cpu_override = new_cpu;
	BreakPointCpu after = cpu().getCpuType();
	return before == after;
}

bool DebuggerView::handleEvent(const DebuggerEvents::Event& event)
{
	auto [begin, end] = m_event_handlers.equal_range(typeid(event).name());
	for (auto handler = begin; handler != end; handler++)
		if (handler->second(event))
			return true;

	return false;
}

bool DebuggerView::acceptsEventType(const char* event_type)
{
	auto [begin, end] = m_event_handlers.equal_range(event_type);
	return begin != end;
}


void DebuggerView::toJson(JsonValueWrapper& json)
{
	std::string custom_display_name_str = m_custom_display_name.toStdString();
	rapidjson::Value custom_display_name;
	custom_display_name.SetString(custom_display_name_str.c_str(), custom_display_name_str.size(), json.allocator());
	json.value().AddMember("customDisplayName", custom_display_name, json.allocator());

	json.value().AddMember("isPrimary", m_is_primary, json.allocator());
}

bool DebuggerView::fromJson(const JsonValueWrapper& json)
{
	auto custom_display_name = json.value().FindMember("customDisplayName");
	if (custom_display_name != json.value().MemberEnd() && custom_display_name->value.IsString())
	{
		m_custom_display_name = QString(custom_display_name->value.GetString());
		m_custom_display_name.truncate(DockUtils::MAX_DOCK_WIDGET_NAME_SIZE);
	}

	auto is_primary = json.value().FindMember("isPrimary");
	if (is_primary != json.value().MemberEnd() && is_primary->value.IsBool())
		m_is_primary = is_primary->value.GetBool();

	return true;
}

void DebuggerView::switchToThisTab()
{
	g_debugger_window->dockManager().switchToDebuggerView(this);
}

bool DebuggerView::supportsMultipleInstances()
{
	return !(m_flags & DISALLOW_MULTIPLE_INSTANCES);
}

void DebuggerView::retranslateDisplayName()
{
	if (!m_custom_display_name.isEmpty())
	{
		m_translated_display_name = m_custom_display_name;
	}
	else
	{
		auto description = DockTables::DEBUGGER_VIEWS.find(metaObject()->className());
		if (description != DockTables::DEBUGGER_VIEWS.end())
			m_translated_display_name = QCoreApplication::translate("DebuggerView", description->second.display_name);
		else
			m_translated_display_name = QString();
	}
}

std::optional<int> DebuggerView::displayNameSuffixNumber() const
{
	return m_display_name_suffix_number;
}

void DebuggerView::setDisplayNameSuffixNumber(std::optional<int> suffix_number)
{
	m_display_name_suffix_number = suffix_number;
}

void DebuggerView::updateStyleSheet()
{
	QString stylesheet;

	if (m_flags & MONOSPACE_FONT)
	{
		// Easiest way to handle cross platform monospace fonts
		// There are issues related to TabWidget -> Children font inheritance otherwise
#if defined(WIN32)
		stylesheet += QStringLiteral("font-family: 'Lucida Console';");
#elif defined(__APPLE__)
		stylesheet += QStringLiteral("font-family: 'Monaco';");
#else
		stylesheet += QStringLiteral("font-family: 'Monospace';");
#endif
	}

	setStyleSheet(stylesheet);
}

void DebuggerView::goToInDisassembler(u32 address, bool switch_to_tab)
{
	DebuggerEvents::GoToAddress event;
	event.address = address;
	event.filter = DebuggerEvents::GoToAddress::DISASSEMBLER;
	event.switch_to_tab = switch_to_tab;
	DebuggerView::sendEvent(std::move(event));
}

void DebuggerView::goToInMemoryView(u32 address, bool switch_to_tab)
{
	DebuggerEvents::GoToAddress event;
	event.address = address;
	event.filter = DebuggerEvents::GoToAddress::MEMORY_VIEW;
	event.switch_to_tab = switch_to_tab;
	DebuggerView::sendEvent(std::move(event));
}

void DebuggerView::sendEventImplementation(const DebuggerEvents::Event& event)
{
	if (!g_debugger_window)
		return;

	for (const auto& [unique_name, widget] : g_debugger_window->dockManager().debuggerViews())
		if (widget->isPrimary() && widget->handleEvent(event))
			return;

	for (const auto& [unique_name, widget] : g_debugger_window->dockManager().debuggerViews())
		if (!widget->isPrimary() && widget->handleEvent(event))
			return;
}

void DebuggerView::broadcastEventImplementation(const DebuggerEvents::Event& event)
{
	if (!g_debugger_window)
		return;

	for (const auto& [unique_name, widget] : g_debugger_window->dockManager().debuggerViews())
		widget->handleEvent(event);
}

std::vector<QAction*> DebuggerView::createEventActionsImplementation(
	QMenu* menu,
	u32 max_top_level_actions,
	bool skip_self,
	const char* event_type,
	const char* action_string,
	const char* action_overflow_string,
	std::function<const DebuggerEvents::Event*()> event_func)
{
	if (!g_debugger_window)
		return {};

	std::vector<DebuggerView*> receivers;
	for (const auto& [unique_name, widget] : g_debugger_window->dockManager().debuggerViews())
		if ((!skip_self || widget != this) && widget->acceptsEventType(event_type))
			receivers.emplace_back(widget);

	std::sort(receivers.begin(), receivers.end(), [&](const DebuggerView* lhs, const DebuggerView* rhs) {
		if (lhs->displayNameWithoutSuffix() == rhs->displayNameWithoutSuffix())
			return lhs->displayNameSuffixNumber() < rhs->displayNameSuffixNumber();

		return lhs->displayNameWithoutSuffix() < rhs->displayNameWithoutSuffix();
	});

	QMenu* submenu = nullptr;
	if (receivers.size() > max_top_level_actions)
	{
		submenu = new QMenu(QCoreApplication::translate("DebuggerEvents", action_overflow_string), menu);
	}

	std::vector<QAction*> actions;
	for (size_t i = 0; i < receivers.size(); i++)
	{
		DebuggerView* receiver = receivers[i];

		QAction* action;
		if (!submenu || i + 1 < max_top_level_actions)
		{
			QString title_format = QCoreApplication::translate("DebuggerEvents", action_string);
			QString title = title_format.arg(receiver->displayName());
			action = new QAction(title, menu);
			menu->addAction(action);
		}
		else
		{
			action = new QAction(receiver->displayName(), submenu);
			submenu->addAction(action);
		}

		connect(action, &QAction::triggered, receiver, [receiver, event_func]() {
			const DebuggerEvents::Event* event = event_func();
			if (event)
				receiver->handleEvent(*event);
		});

		actions.emplace_back(action);
	}

	if (submenu)
		menu->addMenu(submenu);

	return actions;
}
