// license:BSD-3-Clause
// copyright-holders:Aaron Giles, Vas Crabb
//============================================================
//
//  memorywininfo.c - Win32 debug window handling
//
//============================================================

#include "emu.h"
#include "memorywininfo.h"

#include "debugviewinfo.h"
#include "memoryviewinfo.h"
#include "uimetrics.h"

#include "winutf8.h"


memorywin_info::memorywin_info(debugger_windows_interface &debugger) :
	editwin_info(debugger, false, "Memory", nullptr),
	m_combownd(nullptr)
{
	if (!window())
		return;

	m_views[0].reset(new memoryview_info(debugger, *this, window()));
	if ((m_views[0] == nullptr) || !m_views[0]->is_valid())
	{
		m_views[0].reset();
		return;
	}

	// create the options menu
	HMENU const optionsmenu = CreatePopupMenu();
	AppendMenu(optionsmenu, MF_ENABLED, ID_1_BYTE_CHUNKS_HEX, TEXT("1-byte Chunks (Hex)\tCtrl+1"));
	AppendMenu(optionsmenu, MF_ENABLED, ID_2_BYTE_CHUNKS_HEX, TEXT("2-byte Chunks (Hex)\tCtrl+2"));
	AppendMenu(optionsmenu, MF_ENABLED, ID_4_BYTE_CHUNKS_HEX, TEXT("4-byte Chunks (Hex)\tCtrl+4"));
	AppendMenu(optionsmenu, MF_ENABLED, ID_8_BYTE_CHUNKS_HEX, TEXT("8-byte Chunks (Hex)\tCtrl+8"));
	AppendMenu(optionsmenu, MF_ENABLED, ID_1_BYTE_CHUNKS_OCT, TEXT("1-byte Chunks (Octal)\tCtrl+3"));
	AppendMenu(optionsmenu, MF_ENABLED, ID_2_BYTE_CHUNKS_OCT, TEXT("2-byte Chunks (Octal)\tCtrl+5"));
	AppendMenu(optionsmenu, MF_ENABLED, ID_4_BYTE_CHUNKS_OCT, TEXT("4-byte Chunks (Octal)\tCtrl+7"));
	AppendMenu(optionsmenu, MF_ENABLED, ID_8_BYTE_CHUNKS_OCT, TEXT("8-byte Chunks (Octal)\tCtrl+9"));
	AppendMenu(optionsmenu, MF_ENABLED, ID_FLOAT_32BIT, TEXT("32-bit Floating Point\tCtrl+Shift+F"));
	AppendMenu(optionsmenu, MF_ENABLED, ID_FLOAT_64BIT, TEXT("64-bit Floating Point\tCtrl+Shift+D"));
	AppendMenu(optionsmenu, MF_ENABLED, ID_FLOAT_80BIT, TEXT("80-bit Floating Point\tCtrl+Shift+E"));
	AppendMenu(optionsmenu, MF_DISABLED | MF_SEPARATOR, 0, TEXT(""));
	AppendMenu(optionsmenu, MF_ENABLED, ID_HEX_ADDRESSES, TEXT("Hexadecimal Addresses\tCtrl+Shift+H"));
	AppendMenu(optionsmenu, MF_ENABLED, ID_DEC_ADDRESSES, TEXT("Decimal Addresses"));
	AppendMenu(optionsmenu, MF_ENABLED, ID_OCT_ADDRESSES, TEXT("Octal Addresses\tCtrl+Shift+O"));
	AppendMenu(optionsmenu, MF_DISABLED | MF_SEPARATOR, 0, TEXT(""));
	AppendMenu(optionsmenu, MF_ENABLED, ID_LOGICAL_ADDRESSES, TEXT("Logical Addresses\tCtrl+L"));
	AppendMenu(optionsmenu, MF_ENABLED, ID_PHYSICAL_ADDRESSES, TEXT("Physical Addresses\tCtrl+Y"));
	AppendMenu(optionsmenu, MF_DISABLED | MF_SEPARATOR, 0, TEXT(""));
	AppendMenu(optionsmenu, MF_ENABLED, ID_REVERSE_VIEW, TEXT("Reverse View\tCtrl+R"));
	AppendMenu(optionsmenu, MF_DISABLED | MF_SEPARATOR, 0, TEXT(""));
	AppendMenu(optionsmenu, MF_ENABLED, ID_INCREASE_MEM_WIDTH, TEXT("Increase Bytes Per Line\tCtrl+P"));
	AppendMenu(optionsmenu, MF_ENABLED, ID_DECREASE_MEM_WIDTH, TEXT("Decrease Bytes Per Line\tCtrl+O"));
	AppendMenu(GetMenu(window()), MF_ENABLED | MF_POPUP, (UINT_PTR)optionsmenu, TEXT("Options"));

	// set up the view to track the initial expression
	downcast<memoryview_info *>(m_views[0].get())->set_expression("0");
	set_edit_defstr("0");
	set_editwnd_text("0");
	editwnd_select_all();

	// create a combo box
	m_views[0]->set_source_for_visible_cpu();
	m_combownd = m_views[0]->create_source_combobox(window(), (LONG_PTR)this);

	// set the caption
	update_caption();

	// recompute the children once to get the maxwidth
	memorywin_info::recompute_children();

	// position the window and recompute children again
	SetWindowPos(window(), HWND_TOP, 100, 100, maxwidth(), 200, SWP_SHOWWINDOW);
	memorywin_info::recompute_children();

	// mark the edit box as the default focus and set it
	editwin_info::set_default_focus();
}


memorywin_info::~memorywin_info()
{
}


bool memorywin_info::handle_key(WPARAM wparam, LPARAM lparam)
{
	if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
	{
		if (GetAsyncKeyState(VK_SHIFT))
		{
			switch (wparam)
			{
			case 'F':
				SendMessage(window(), WM_COMMAND, ID_FLOAT_32BIT, 0);
				return true;

			case 'D':
				SendMessage(window(), WM_COMMAND, ID_FLOAT_64BIT, 0);
				return true;

			case 'E':
				SendMessage(window(), WM_COMMAND, ID_FLOAT_80BIT, 0);
				return true;

			case 'H':
				SendMessage(window(), WM_COMMAND, ID_HEX_ADDRESSES, 0);
				return true;

			case 'O':
				SendMessage(window(), WM_COMMAND, ID_OCT_ADDRESSES, 0);
				return true;
			}
		}
		else
		{
			switch (wparam)
			{
			case '1':
				SendMessage(window(), WM_COMMAND, ID_1_BYTE_CHUNKS_HEX, 0);
				return true;

			case '2':
				SendMessage(window(), WM_COMMAND, ID_2_BYTE_CHUNKS_HEX, 0);
				return true;

			case '4':
				SendMessage(window(), WM_COMMAND, ID_4_BYTE_CHUNKS_HEX, 0);
				return true;

			case '8':
				SendMessage(window(), WM_COMMAND, ID_8_BYTE_CHUNKS_HEX, 0);
				return true;

			case '3':
				SendMessage(window(), WM_COMMAND, ID_1_BYTE_CHUNKS_OCT, 0);
				return true;

			case '5':
				SendMessage(window(), WM_COMMAND, ID_2_BYTE_CHUNKS_OCT, 0);
				return true;

			case '7':
				SendMessage(window(), WM_COMMAND, ID_4_BYTE_CHUNKS_OCT, 0);
				return true;

			case '9':
				SendMessage(window(), WM_COMMAND, ID_8_BYTE_CHUNKS_OCT, 0);
				return true;

			case 'L':
				SendMessage(window(), WM_COMMAND, ID_LOGICAL_ADDRESSES, 0);
				return true;

			case 'Y':
				SendMessage(window(), WM_COMMAND, ID_PHYSICAL_ADDRESSES, 0);
				return true;

			case 'R':
				SendMessage(window(), WM_COMMAND, ID_REVERSE_VIEW, 0);
				return true;

			case 'P':
				SendMessage(window(), WM_COMMAND, ID_INCREASE_MEM_WIDTH, 0);
				return true;

			case 'O':
				SendMessage(window(), WM_COMMAND, ID_DECREASE_MEM_WIDTH, 0);
				return true;
			}
		}
	}
	return editwin_info::handle_key(wparam, lparam);
}


void memorywin_info::recompute_children()
{
	// compute a client rect
	RECT bounds;
	bounds.top = bounds.left = 0;
	bounds.right = m_views[0]->prefwidth() + (2 * EDGE_WIDTH);
	bounds.bottom = 200;
	AdjustWindowRectEx(&bounds, DEBUG_WINDOW_STYLE, FALSE, DEBUG_WINDOW_STYLE_EX);

	// clamp the min/max size
	set_maxwidth(bounds.right - bounds.left);

	// get the parent's dimensions
	RECT parent;
	GetClientRect(window(), &parent);

	// edit box gets half of the width
	RECT editrect;
	editrect.top = parent.top + EDGE_WIDTH;
	editrect.bottom = editrect.top + metrics().debug_font_height() + 4;
	editrect.left = parent.left + EDGE_WIDTH;
	editrect.right = parent.left + ((parent.right - parent.left) / 2) - EDGE_WIDTH;

	// combo box gets the other half of the width
	RECT comborect;
	comborect.top = editrect.top;
	comborect.bottom = editrect.bottom;
	comborect.left = editrect.right + (2 * EDGE_WIDTH);
	comborect.right = parent.right - EDGE_WIDTH;

	// memory view gets the rest
	RECT memrect;
	memrect.top = editrect.bottom + (2 * EDGE_WIDTH);
	memrect.bottom = parent.bottom - EDGE_WIDTH;
	memrect.left = parent.left + EDGE_WIDTH;
	memrect.right = parent.right - EDGE_WIDTH;

	// set the bounds of things
	m_views[0]->set_bounds(memrect);
	set_editwnd_bounds(editrect);
	smart_set_window_bounds(m_combownd, window(), comborect);
}


void memorywin_info::update_menu()
{
	editwin_info::update_menu();

	auto *const memview = downcast<memoryview_info *>(m_views[0].get());
	HMENU const menu = GetMenu(window());

	CheckMenuItem(menu, ID_1_BYTE_CHUNKS_HEX, MF_BYCOMMAND | ((memview->data_format() == debug_view_memory::data_format::HEX_8BIT) ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(menu, ID_2_BYTE_CHUNKS_HEX, MF_BYCOMMAND | ((memview->data_format() == debug_view_memory::data_format::HEX_16BIT) ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(menu, ID_4_BYTE_CHUNKS_HEX, MF_BYCOMMAND | ((memview->data_format() == debug_view_memory::data_format::HEX_32BIT) ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(menu, ID_8_BYTE_CHUNKS_HEX, MF_BYCOMMAND | ((memview->data_format() == debug_view_memory::data_format::HEX_64BIT) ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(menu, ID_1_BYTE_CHUNKS_OCT, MF_BYCOMMAND | ((memview->data_format() == debug_view_memory::data_format::OCTAL_8BIT) ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(menu, ID_2_BYTE_CHUNKS_OCT, MF_BYCOMMAND | ((memview->data_format() == debug_view_memory::data_format::OCTAL_16BIT) ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(menu, ID_4_BYTE_CHUNKS_OCT, MF_BYCOMMAND | ((memview->data_format() == debug_view_memory::data_format::OCTAL_32BIT) ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(menu, ID_8_BYTE_CHUNKS_OCT, MF_BYCOMMAND | ((memview->data_format() == debug_view_memory::data_format::OCTAL_64BIT) ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(menu, ID_FLOAT_32BIT, MF_BYCOMMAND | ((memview->data_format() == debug_view_memory::data_format::FLOAT_32BIT) ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(menu, ID_FLOAT_64BIT, MF_BYCOMMAND | ((memview->data_format() == debug_view_memory::data_format::FLOAT_64BIT) ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(menu, ID_FLOAT_80BIT, MF_BYCOMMAND | ((memview->data_format() == debug_view_memory::data_format::FLOAT_80BIT) ? MF_CHECKED : MF_UNCHECKED));

	CheckMenuItem(menu, ID_HEX_ADDRESSES, MF_BYCOMMAND | ((memview->address_radix() == 16) ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(menu, ID_DEC_ADDRESSES, MF_BYCOMMAND | ((memview->address_radix() == 10) ? MF_CHECKED : MF_UNCHECKED));
	CheckMenuItem(menu, ID_OCT_ADDRESSES, MF_BYCOMMAND | ((memview->address_radix() == 8) ? MF_CHECKED : MF_UNCHECKED));

	CheckMenuItem(menu, ID_LOGICAL_ADDRESSES, MF_BYCOMMAND | (memview->physical() ? MF_UNCHECKED : MF_CHECKED));
	CheckMenuItem(menu, ID_PHYSICAL_ADDRESSES, MF_BYCOMMAND | (memview->physical() ? MF_CHECKED : MF_UNCHECKED));

	CheckMenuItem(menu, ID_REVERSE_VIEW, MF_BYCOMMAND | (memview->reverse() ? MF_CHECKED : MF_UNCHECKED));

	EnableMenuItem(menu, ID_DECREASE_MEM_WIDTH, MF_BYCOMMAND | ((memview->chunks_per_row() > 1) ? MF_ENABLED : MF_GRAYED));
}


bool memorywin_info::handle_command(WPARAM wparam, LPARAM lparam)
{
	auto *const memview = downcast<memoryview_info *>(m_views[0].get());
	switch (HIWORD(wparam))
	{
	// combo box selection changed
	case CBN_SELCHANGE:
		{
			int const sel = SendMessage((HWND)lparam, CB_GETCURSEL, 0, 0);
			if (sel != CB_ERR)
			{
				memview->set_source_index(sel);
				update_caption();

				// reset the focus
				set_default_focus();
				return true;
			}
			break;
		}

	// menu selections
	case 0:
		switch (LOWORD(wparam))
		{
		case ID_1_BYTE_CHUNKS_HEX:
			memview->set_data_format(debug_view_memory::data_format::HEX_8BIT);
			return true;

		case ID_2_BYTE_CHUNKS_HEX:
			memview->set_data_format(debug_view_memory::data_format::HEX_16BIT);
			return true;

		case ID_4_BYTE_CHUNKS_HEX:
			memview->set_data_format(debug_view_memory::data_format::HEX_32BIT);
			return true;

		case ID_8_BYTE_CHUNKS_HEX:
			memview->set_data_format(debug_view_memory::data_format::HEX_64BIT);
			return true;

		case ID_1_BYTE_CHUNKS_OCT:
			memview->set_data_format(debug_view_memory::data_format::OCTAL_8BIT);
			return true;

		case ID_2_BYTE_CHUNKS_OCT:
			memview->set_data_format(debug_view_memory::data_format::OCTAL_16BIT);
			return true;

		case ID_4_BYTE_CHUNKS_OCT:
			memview->set_data_format(debug_view_memory::data_format::OCTAL_32BIT);
			return true;

		case ID_8_BYTE_CHUNKS_OCT:
			memview->set_data_format(debug_view_memory::data_format::OCTAL_64BIT);
			return true;

		case ID_FLOAT_32BIT:
			memview->set_data_format(debug_view_memory::data_format::FLOAT_32BIT);
			return true;

		case ID_FLOAT_64BIT:
			memview->set_data_format(debug_view_memory::data_format::FLOAT_64BIT);
			return true;

		case ID_FLOAT_80BIT:
			memview->set_data_format(debug_view_memory::data_format::FLOAT_80BIT);
			return true;

		case ID_HEX_ADDRESSES:
			memview->set_address_radix(16);
			return true;

		case ID_DEC_ADDRESSES:
			memview->set_address_radix(10);
			return true;

		case ID_OCT_ADDRESSES:
			memview->set_address_radix(8);
			return true;

		case ID_LOGICAL_ADDRESSES:
			memview->set_physical(false);
			return true;

		case ID_PHYSICAL_ADDRESSES:
			memview->set_physical(true);
			return true;

		case ID_REVERSE_VIEW:
			memview->set_reverse(!memview->reverse());
			return true;

		case ID_INCREASE_MEM_WIDTH:
			memview->set_chunks_per_row(memview->chunks_per_row() + 1);
			recompute_children();
			return true;

		case ID_DECREASE_MEM_WIDTH:
			memview->set_chunks_per_row(memview->chunks_per_row() - 1);
			recompute_children();
			return true;
		}
		break;
	}
	return editwin_info::handle_command(wparam, lparam);
}


void memorywin_info::draw_contents(HDC dc)
{
	editwin_info::draw_contents(dc);
	if (m_combownd)
		draw_border(dc, m_combownd);
}


void memorywin_info::process_string(const std::string &string)
{
	// set the string to the memory view
	downcast<memoryview_info *>(m_views[0].get())->set_expression(string);

	// select everything in the edit text box
	editwnd_select_all();

	// update the default string to match
	set_edit_defstr(string);
}


void memorywin_info::update_caption()
{
	win_set_window_text_utf8(window(), std::string("Memory: ").append(m_views[0]->source_name()).c_str());
}
