/****************************************************************************
*                                                                           *
* Project 64 - A Nintendo 64 emulator.                                      *
* http://www.pj64-emu.com/                                                  *
* Copyright (C) 2012 Project64. All rights reserved.                        *
*                                                                           *
* License:                                                                  *
* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html                        *
*                                                                           *
****************************************************************************/
#include "stdafx.h"
#include "Debugger UI.h"

CDebugMemoryView::CDebugMemoryView(CDebugger * debugger) :
	CDebugDialog<CDebugMemoryView>(debugger), 
	m_MemoryList(NULL)
{
	if (m_MemoryList== NULL)
	{
		m_MemoryList = new CListCtrl;
		m_MemoryList->RegisterClass();
	}
}

CDebugMemoryView::~CDebugMemoryView()
{
}

LRESULT	CDebugMemoryView::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
	m_DataStartLoc = (DWORD)-1;
	m_CompareStartLoc = (DWORD)-1;
	memset(m_CompareData,0,sizeof(m_CompareData));
	memset(m_CompareValid,0,sizeof(m_CompareValid));

	HWND hScrlBar = GetDlgItem(IDC_SCRL_BAR);
	if (hScrlBar) {
		SCROLLINFO si;

		si.cbSize = sizeof(si);
		si.fMask  = SIF_RANGE | SIF_POS | SIF_PAGE;
		si.nMin   = 0;
		si.nMax   = 0xFFFF;
		si.nPos   = 0x8000;
		si.nPage  = 100;
		::SetScrollInfo(hScrlBar,SB_CTL,&si,TRUE);		
	} 

	m_MemAddr.Attach(GetDlgItem(IDC_ADDR_EDIT));
	m_MemAddr.SetDisplayType(CEditNumber::DisplayHex);
	m_MemAddr.SetValue(0x80000000,true,true);

	SendDlgItemMessage( IDC_CHK_VADDR, BM_SETCHECK, BST_CHECKED,0);

	if (m_MemoryList== NULL)
	{
		m_MemoryList = new CListCtrl;
		m_MemoryList->RegisterClass();
	}
	m_MemoryList->SubclassWindow( GetDlgItem( IDC_MEM_DETAILS ) );
	m_MemoryList->ShowHeader(false);
	m_MemoryList->SetSortEnabled(FALSE);
	m_MemoryList->AddColumn( _T( "Address" ), 90 );
	m_MemoryList->AddColumn( _T( "1" ), 20 );
	m_MemoryList->AddColumn( _T( "2" ), 20 );
	m_MemoryList->AddColumn( _T( "3" ), 20 );
	m_MemoryList->AddColumn( _T( "4" ), 20 );
	m_MemoryList->AddColumn( _T( "-" ), 10 );
	m_MemoryList->AddColumn( _T( "5" ), 20 );
	m_MemoryList->AddColumn( _T( "6" ), 20 );
	m_MemoryList->AddColumn( _T( "7" ), 20 );
	m_MemoryList->AddColumn( _T( "8" ), 20 );
	m_MemoryList->AddColumn( _T( "-" ), 10 );
	m_MemoryList->AddColumn( _T( "9" ), 20 );
	m_MemoryList->AddColumn( _T( "10" ), 20 );
	m_MemoryList->AddColumn( _T( "11" ), 20 );
	m_MemoryList->AddColumn( _T( "12" ), 20 );
	m_MemoryList->AddColumn( _T( "-" ), 10 );
	m_MemoryList->AddColumn( _T( "13" ), 20 );
	m_MemoryList->AddColumn( _T( "14" ), 20 );
	m_MemoryList->AddColumn( _T( "15" ), 20 );
	m_MemoryList->AddColumn( _T( "16" ), 35 );
	m_MemoryList->AddColumn( _T( "Memory Ascii" ), 140 );
	::SetWindowLongPtr(m_MemoryList->m_hWnd, GWL_EXSTYLE, WS_EX_CLIENTEDGE);
	RefreshMemory(false);
	int height = m_MemoryList->GetTotalHeight();
	
	RECT MemoryListRect = {0};
	::GetClientRect(GetDlgItem( IDC_MEM_DETAILS ), &MemoryListRect);

	if (height > MemoryListRect.bottom)
	{
		RECT MemoryListWindow = {0};
		GetWindowRect(&MemoryListWindow);
		SetWindowPos(NULL,0,0,MemoryListWindow.right - MemoryListWindow.left,(MemoryListWindow.bottom - MemoryListWindow.top) + (height - MemoryListRect.bottom), SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOZORDER);

		RECT DlgItemRect = {0};
		::GetWindowRect(GetDlgItem( IDC_BORDER ), &DlgItemRect);
		::SetWindowPos(GetDlgItem( IDC_BORDER ), NULL,0,0,DlgItemRect.right - DlgItemRect.left,(DlgItemRect.bottom - DlgItemRect.top) + (height - MemoryListRect.bottom), SWP_NOMOVE);

		::GetWindowRect(GetDlgItem( IDC_MEM_DETAILS ), &DlgItemRect);
		::SetWindowPos(GetDlgItem( IDC_MEM_DETAILS ), NULL,0,0,DlgItemRect.right - DlgItemRect.left,(DlgItemRect.bottom - DlgItemRect.top) + (height - MemoryListRect.bottom), SWP_NOMOVE);

		::GetWindowRect(GetDlgItem( IDC_SCRL_BAR ), &DlgItemRect);
		::SetWindowPos(GetDlgItem( IDC_SCRL_BAR ), NULL,0,0,DlgItemRect.right - DlgItemRect.left,(DlgItemRect.bottom - DlgItemRect.top) + (height - MemoryListRect.bottom), SWP_NOMOVE);
	}
	WindowCreated();
	return TRUE;
}

LRESULT CDebugMemoryView::OnDestroy ( void )
{
	if (m_MemoryList)
	{
		m_MemoryList->UnsubclassWindow();
		delete m_MemoryList;
		m_MemoryList = NULL;
	}
	return 0;
}

LRESULT CDebugMemoryView::OnClicked (WORD /*wNotifyCode*/, WORD wID, HWND , BOOL& /*bHandled*/)
{
	switch(wID)
	{
	case IDC_REFRSH_MEM:
		RefreshMemory(true);
		break;
	case IDC_CHK_VADDR:
		RefreshMemory(false);
		break;
	case IDC_DUMP_MEM:
		m_Debugger->Debug_ShowMemoryDump();
		break;
	case IDC_SEARCH_MEM:
		m_Debugger->Debug_ShowMemorySearch();
		break;
	case IDCANCEL:
		EndDialog(0);
		break;
	}
	return FALSE;
}

LRESULT CDebugMemoryView::OnMemoryModified ( LPNMHDR lpNMHDR )
{
	CListNotify *pListNotify = reinterpret_cast<CListNotify *>( lpNMHDR );
	int LineNumber = pListNotify->m_nItem;
	int Pos = ((LineNumber << 4) + (pListNotify->m_nSubItem - 1));
	if (pListNotify->m_nSubItem >= 6 && pListNotify->m_nSubItem < 10)
	{
		Pos = ((LineNumber << 4) + (pListNotify->m_nSubItem - 2));
	}
	if (pListNotify->m_nSubItem >= 11 && pListNotify->m_nSubItem < 15)
	{
		Pos = ((LineNumber << 4) + (pListNotify->m_nSubItem - 3));
	}
	if (pListNotify->m_nSubItem >= 16 && pListNotify->m_nSubItem < 20)
	{
		Pos = ((LineNumber << 4) + (pListNotify->m_nSubItem - 4));
	}

	LPCSTR strValue = m_MemoryList->GetItemText(pListNotify->m_nItem,pListNotify->m_nSubItem);
	int Finish = strlen(strValue);
	if (Finish > 8) { Finish = 8; }
	DWORD Value = 0;
	for (int i = 0; i < Finish; i++)
	{
		Value = (Value << 4);
		if (strValue[i] <= '9' && strValue[i] >= '0') 
		{ 
			Value |= strValue[i] - '0'; 
		} 
		else if (strValue[i] <= 'f' && strValue[i] >= 'a') 
		{ 
			Value |= strValue[i] - 'a' + 10; 
		} 
		else if (strValue[i] <= 'F' && strValue[i] >= 'A') 
		{ 
			Value |= strValue[i] - 'A' + 10; 
		} 
	}

	if (m_CurrentData[Pos] == Value)
	{
		return 0;
	}

	if (m_CompareStartLoc != m_DataStartLoc || 
		m_CompareVAddrr != m_DataVAddrr)
	{
		// copy current data for change comparison
		m_CompareStartLoc = m_DataStartLoc;
		m_CompareVAddrr   = m_DataVAddrr;
		memcpy(m_CompareData,m_CurrentData,sizeof(m_CurrentData));
		memcpy(m_CompareValid,m_DataValid,sizeof(m_CompareValid));
	}

	m_CompareData[Pos] = m_CurrentData[Pos];
	m_CurrentData[Pos] = (BYTE)Value;

	//sb
	if ( m_DataVAddrr ) 
	{
		if (!g_MMU->SB_VAddr(m_DataStartLoc + Pos,(BYTE)Value))
		{
			WriteTraceF(TraceError,__FUNCTION__ ": failed to store at %X",m_DataStartLoc + Pos);
		}
	} else {
		if (!g_MMU->SB_PAddr(m_DataStartLoc + Pos,(BYTE)Value))
		{
			WriteTraceF(TraceError,__FUNCTION__ ": failed to store at %X",m_DataStartLoc + Pos);
		}
	}
	Insert_MemoryLineDump(LineNumber);

	return 0;
}

void CDebugMemoryView::ShowAddress(DWORD Address, bool VAddr)
{
	if (m_hWnd == NULL)
	{
		return;
	}

	SendDlgItemMessage( IDC_CHK_VADDR, BM_SETCHECK, VAddr ? BST_CHECKED : BST_UNCHECKED,0);
	m_MemAddr.SetValue(Address,true,true); 
	RefreshMemory (true);
}

void CDebugMemoryView::Insert_MemoryLineDump ( int LineNumber ) 
{
	if (m_MemoryList == NULL || m_MemoryList->GetColumnCount() == 0)
	{
		return;
	}
	char Output[20], Hex[60], Ascii[20], AsciiAddOn[15];
	sprintf(Output,"0x%08X",m_DataStartLoc + (LineNumber << 4));
	if (m_MemoryList->GetItemCount() <= LineNumber)
	{
		HFONT hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
		m_MemoryList->AddItemAt( LineNumber, Output);
		for (int i = 0; i < m_MemoryList->GetColumnCount(); i++)
		{
			m_MemoryList->SetItemFont( LineNumber, i,  hFont );
			if (i == 5 || i == 10 || i == 15)
			{
				m_MemoryList->SetItemText(LineNumber,i,"-");
			}
		}
	} else {
		if ( strcmp( Output, m_MemoryList->GetItemText(LineNumber, 0) ) != 0 ) 
		{
			m_MemoryList->SetItemText(LineNumber,0,Output);
		}
	}

	Hex[0]   = 0;
	Ascii[0] = 0;
	int CompareStartPos = m_DataStartLoc - m_CompareStartLoc;

	for (int i = 0, col = 1; i < 0x10; i ++, col ++) 
	{
		int Pos = ((LineNumber << 4) + i);
		if (m_DataValid[Pos])
		{
			int ComparePos = CompareStartPos + Pos;
			bool Changed = false;

			if (ComparePos >= 0 && ComparePos < MemoryToDisplay &&
				m_DataVAddrr == m_CompareVAddrr && 
				m_DataValid[ComparePos] &&
				m_CurrentData[Pos] != m_CompareData[ComparePos])
			{
				Changed = true;
			}
			sprintf(Hex,"%02X",m_CurrentData[Pos]);
			m_MemoryList->SetItemText(LineNumber,col,Hex);
			m_MemoryList->SetItemFormat( LineNumber,col, ITEM_FORMAT_EDIT, ITEM_FLAGS_EDIT_HEX );
			m_MemoryList->SetItemMaxEditLen( LineNumber,col , 2);
			m_MemoryList->SetItemColours( LineNumber, col, GetSysColor( COLOR_WINDOW ), 
				Changed ? RGB( 255, 0, 0 ) : GetSysColor( COLOR_WINDOWTEXT ) );
			m_MemoryList->SetItemHighlightColours( LineNumber, col, 
				Changed ? RGB( 255, 0, 0 ) : GetSysColor( COLOR_HIGHLIGHTTEXT ) );
			if (m_CurrentData[Pos] < 30)
			{
				strcat(Ascii,".");
			} else {
				sprintf(AsciiAddOn,"%c",m_CurrentData[Pos]);
				strcat(Ascii,AsciiAddOn);
			}
		} else {
			m_MemoryList->SetItemText(LineNumber,col,"**");
			m_MemoryList->SetItemFormat( LineNumber,col, ITEM_FORMAT_NONE, ITEM_FLAGS_NONE );
			m_MemoryList->SetItemColours( LineNumber, col, GetSysColor( COLOR_WINDOW ), GetSysColor( COLOR_WINDOWTEXT ) );
			strcat(Ascii,"*");
		}
		if (i != 0xF)
		{
			if ((i & 3) == 3)
			{
				col += 1;
			}
		}
	}
	
	if ( strcmp( Ascii, m_MemoryList->GetItemText(LineNumber, 20) ) != 0 ) 
	{
		m_MemoryList->SetItemText(LineNumber,20,Ascii);
	}
}

void CDebugMemoryView::OnAddrChanged( UINT /*Code*/, int /*id*/, HWND /*ctl*/ )
{
	RefreshMemory(false);
}

void CDebugMemoryView::OnVScroll(int request, short Pos, HWND ctrl )
{
	if (ctrl != GetDlgItem(IDC_SCRL_BAR))
	{
		return;
	}
	DWORD Location = m_MemAddr.GetValue();
	switch (request) 
	{			
	case SB_LINEDOWN:
		m_MemAddr.SetValue(Location < 0xFFFFFFEF ? Location + 0x10 : 0xFFFFFFFF,true,true);
		break;
	case SB_LINEUP:
		m_MemAddr.SetValue(Location > 0x10 ? Location - 0x10 : 0,true,true);
		break;
	case SB_PAGEDOWN:				
		m_MemAddr.SetValue(Location < 0xFFFFFEFF ? Location + 0x100 : 0xFFFFFFFF,true,true);
		break;			
	case SB_PAGEUP:
		m_MemAddr.SetValue(Location > 0x100 ? Location - 0x100 : 0,true,true);
		break;
	case SB_THUMBPOSITION:
		m_MemAddr.SetValue((DWORD)Pos << 0x10,true,true);
		break;
	default:
		break;
	}
}

void CDebugMemoryView::RefreshMemory ( bool ResetCompare )
{
	if (m_MemoryList && m_MemoryList->GetHasEditItem())
	{
		m_MemoryList->SetFocus();
	}

	DWORD NewAddress = m_MemAddr.GetValue();
	if (NewAddress != m_DataStartLoc)
	{
		HWND hScrlBar = GetDlgItem(IDC_SCRL_BAR);
		if (hScrlBar) 
		{
			SCROLLINFO si;

			si.cbSize = sizeof(si);
			si.fMask  = SIF_POS;
			si.nPos   = NewAddress >> 0x10;
			::SetScrollInfo(hScrlBar,SB_CTL,&si,TRUE);		
		}
	} 

	if (ResetCompare)
	{
		// copy current data for change comparison
		m_CompareStartLoc = m_DataStartLoc;
		m_CompareVAddrr   = m_DataVAddrr;
		memcpy(m_CompareData,m_CurrentData,sizeof(m_CurrentData));
		memcpy(m_CompareValid,m_DataValid,sizeof(m_CompareValid));
	}
	
	m_DataStartLoc = m_MemAddr.GetValue();
	if (m_DataStartLoc > 0xFFFFFF00) { m_DataStartLoc = 0xFFFFFF00; }
	int WritePos = 0;

	m_DataVAddrr = (SendDlgItemMessage( IDC_CHK_VADDR, BM_GETCHECK, 0,0) & BST_CHECKED) != 0;

	if ((m_DataStartLoc & 3) != 0)
	{
		MIPS_WORD word;
		bool ValidData = true;
	
		if ( m_DataVAddrr ) 
		{
			if (!g_MMU->LW_VAddr(m_DataStartLoc & ~3, word.UW)) 
			{ 
				ValidData = false;
			}
		} else {
			if (!g_MMU->LW_PAddr(m_DataStartLoc & ~3, word.UW)) 
			{ 
				ValidData = false;
			}
		}

		int Offset = (m_DataStartLoc & 3);
		for (int i = 0; i < (4 - Offset); i++)
		{
			if (WritePos >= MemoryToDisplay)
			{
				break;
			}
			m_DataValid[WritePos + i] = ValidData;
			if (ValidData)
			{
				m_CurrentData[WritePos + i] = word.UB[3 - (i + Offset)];
			}
		}
		WritePos = 4 - Offset; 
		
	} 

	for (DWORD Pos = ((m_DataStartLoc + 3) & ~3); Pos < (m_DataStartLoc + MemoryToDisplay); WritePos += 4, Pos += 4)
	{
		MIPS_WORD word;
		bool ValidData = true;
	
		if ( m_DataVAddrr ) 
		{
			if (!g_MMU->LW_VAddr(Pos, word.UW)) 
			{ 
				ValidData = false;
			}
		} else {
			if (!g_MMU->LW_PAddr(Pos, word.UW)) 
			{ 
				ValidData = false;
			}
		}

		for (int i = 0; i < 4; i++)
		{
			if ((WritePos + i)>= MemoryToDisplay)
			{
				break;
			}
			m_DataValid[WritePos + i] = ValidData;
			if (ValidData)
			{
				m_CurrentData[WritePos + i] = word.UB[3 - i];
			}
		}
	}

	for (int count = 0 ; count < 16;count ++ ){
		Insert_MemoryLineDump ( count );
	}
}
