/***************************************************************************
**
**    $Header$
**
**    $Log$
** 
****************************************************************************/

// stepsvr.cpp : implementation file
//

#include "stdafx.h"
#include "stepdll.h"

#include "srcaddr.h"
#include "srcexp.h"
#include "srcimp.h"
#include "bptexp.h"

#include "symblsvr.h"
#include "abiextfn.h"
#include "uicom2.h"
#include "ldrexp.h"

#include "stepsvr.h"
#include "stepexp.h"

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

/////////////////////////////////////////////////////////////////////////////
// Static string

static char BASED_CODE szKeywordOver[] = "OVER";
static char BASED_CODE szKeywordOut[] = "OUT";
static char BASED_CODE szKeywordForever[] = "FOREVER";
static char BASED_CODE szTab[] = "    ";

/////////////////////////////////////////////////////////////////////////////
// Imported function

#ifdef __cplusplus
extern "C" {
#endif	// __cplusplus

BOOL WINAPI IsScall(ADDR pc, ADDR& ret);
BOOL WINAPI IsLcall(ADDR pc, ADDR& ret);

#ifdef __cplusplus
}
#endif	// __cplusplus

/////////////////////////////////////////////////////////////////////////////
// Exported function

#ifdef __cplusplus
extern "C" {
#endif	// __cplusplus

void WINAPI StepCmd(int nArgc, char* pszArgv[])
{
	// Step [[Over] [Forever]]|[OUt]
	CStepServer* pCmd = new CStepServer;
	pCmd->StepAction(nArgc, pszArgv);
	delete pCmd;
}

void WINAPI StepServer(const int nMode)
{
	// Be called from UI
	CStepServer* pCmd = new CStepServer;
	pCmd->StepAction(nMode);
	delete pCmd;
}

#ifdef __cplusplus
}
#endif	// __cplusplus

/////////////////////////////////////////////////////////////////////////////
// Global function

IsTestKey(const WORD wTestKey)
{
	// Abort by ESC
    MSG msg;
    if ( ::PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE|PM_NOYIELD) ) {
        ::PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE|PM_NOYIELD);
        if ( msg.message == WM_KEYDOWN && msg.wParam == wTestKey ) {
        	return TRUE;
        }
    }
    
    return FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// CStepServer

void CStepServer::StepAction(int nArgc, char* pszArgv[])
{
	// Step [[Over] [Forever]]|[OUt]
	switch ( nArgc ) {
	case 1:
		// Step
		m_nMode = ::step;
		break;
	case 2:
	{
		CString strArgv(pszArgv[1]);
		strArgv.MakeUpper();
		if ( ::szKeywordOver == strArgv ) {
			// Step Over
			m_nMode = ::stepOver;
		}
		else if ( ::szKeywordOut == strArgv ) {
			// Step Out
			m_nMode = ::stepOut;
		}
		else if ( ::szKeywordForever == strArgv ) {
			// Step Forever
			m_nMode = ::stepForever;
		}
	}
		break;
	case 3:
		// Step Over Forever
		m_nMode = ::stepOverForever;
		break;
	default:
		ASSERT(FALSE);
		return;
	}

	// Step kernel
	m_bInShell = TRUE;
	StepKernel();
}

void CStepServer::StepAction(const int nMode)
{
	// Valid input: step ~ stepOverForever
	if ( nMode < step || nMode > stepOverForever ) {
		ASSERT(FALSE);
		return;
	}
	m_nMode = nMode;

	// Step kernel
	m_bInShell = FALSE;
	StepKernel();
}

void CStepServer::StepKernel()
{
	// Get PC
	if ( !::SrcGetPC(m_Addr) ) {
		::SrcDisplayErrorMessage(::errGetPC, m_bInShell);
		return;
	}

	// High or Low level
	AfxGetApp()->BeginWaitCursor();
	if ( ::modSrc == ::SrcGetMode() && IsInNewSourceLine(m_Addr) ) {
		StepHighLevel();
	}
	else {
		StepLowLevel();
	}
	AfxGetApp()->EndWaitCursor();
	
	// Show the next instruction
	if ( m_bInShell ) {
		ShowNextCode();
	}

	// Update all of the windows
	::OnEmulation();
}

void CStepServer::StepLowLevel()
{
	// Low level Step
	do {
		// Step Out
		if ( m_nMode == ::stepOut ) {
			// Get PC
			if ( !::SrcGetPC(m_Addr) ) {
				::SrcDisplayErrorMessage(::errGetPC, m_bInShell);
				return;
			}
			if ( IsMatchRet(m_Addr) ) {
				m_nMode = ::step;
			}
		}
		
		// Send STEP command
		if ( m_nMode == ::step || m_nMode == ::stepForever ) {
			if ( ICE_OK != ::emuStepOne() ) {
				::SrcDisplayErrorMessage(::errStep, m_bInShell);
				return;
			}
		}
		else {
			// Get PC
			if ( !::SrcGetPC(m_Addr) ) {
				::SrcDisplayErrorMessage(::errGetPC, m_bInShell);
				return;
			}
			CSourceAddr AddrLib, AddrNext;
			if ( IsMatchCall(m_Addr, AddrLib, AddrNext) ) {
				// Goto next address
				::ADDR addr1, addr2;
				addr1.addrType = 0;
				addr2.addr = AddrNext.GetAddr();
				addr2.addrType = AddrNext.GetType();
					
				if ( ICE_OK != ::emuGo(::NORMAL_RUN, addr1, addr2) ) {
					::SrcDisplayErrorMessage(::errStep, m_bInShell);
					return;
				}
			}
			else {
				if ( ICE_OK != ::emuStepOne() ) {
					::SrcDisplayErrorMessage(::errStep, m_bInShell);
					return;
				}
			}
		}

		// Check EP status
		if ( !IsEPStop() ) {
			return;
		}
		
		// Step by step
		if ( m_nMode == ::stepForever || m_nMode == ::stepOverForever || m_nMode == ::stepOut ) {
			// Update all of the windows
			::OnEmulation();
		}
	} while ( m_nMode == ::stepForever || m_nMode == ::stepOverForever || m_nMode == ::stepOut );
}

void CStepServer::StepHighLevel()
{
	// High level Step
	do {
		// Step Out
		if ( m_nMode == ::stepOut && IsMatchRet(m_Addr) ) {
			m_nMode = ::step;
		}

		// Send ABI command
		CSourceAddr AddrLib, AddrNext;
		if ( IsMatchCall(m_Addr, AddrLib, AddrNext) ) {
			if ( m_nMode == ::stepOver || m_nMode == ::stepOverForever || m_nMode == ::stepOut || !IsInNewSourceLine(AddrLib, FALSE) ) {
 				if ( (m_nMode == ::step || m_nMode == ::stepForever) && IsInRootBank(AddrLib) ) {
					// Bank switch: root bank
					do {
						if ( ICE_OK != ::emuStepOne() ) {
							::SrcDisplayErrorMessage(::errStep, m_bInShell);
							return;
						}
						
						// Check EP status
						if ( !IsEPStop() ) {
							return;
						}
						
						// Get PC
						if ( !::SrcGetPC(m_Addr) ) {
							::SrcDisplayErrorMessage(::errGetPC, m_bInShell);
							return;
						}
						
						// End if Step OK or Match BP
						if ( IsInNewSourceLine(m_Addr) || ::BptIsBreakpoint(m_Addr) ) {
							if ( m_nMode == ::stepForever ) {
								// Update all of the windows
								::OnEmulation();
							}
							break;
						}
					} while (TRUE);

					// Step Forever
					continue;
				}
				else {
					// Goto next address
					::ADDR addr1, addr2;
					addr1.addrType = 0;
					addr2.addr = AddrNext.GetAddr();
					addr2.addrType = AddrNext.GetType();
					
					if ( ICE_OK != ::emuGo(::NORMAL_RUN, addr1, addr2) ) {
						::SrcDisplayErrorMessage(::errStep, m_bInShell);
						return;
					}
				}
			}
			else {
				if ( ICE_OK != ::emuStepOne() ) {
					::SrcDisplayErrorMessage(::errStep, m_bInShell);
					return;
				}
			}
		}
		else {
			if ( ICE_OK != ::emuStepOne() ) {
				::SrcDisplayErrorMessage(::errStep, m_bInShell);
				return;
			}
		}

		// Check EP status
		if ( !IsEPStop() ) {
			return;
		}
		
		// Get PC
		if ( !::SrcGetPC(m_Addr) ) {
			::SrcDisplayErrorMessage(::errGetPC, m_bInShell);
			return;
		}
		
		// End if Step OK or Match BP
		if ( IsInNewSourceLine(m_Addr) || ::BptIsBreakpoint(m_Addr) ) {
			// Step by step
			if ( m_nMode == ::stepForever || m_nMode == ::stepOverForever || m_nMode == ::stepOut ) {
				// Update all of the windows
				::OnEmulation();
			}
			else {
				return;
			}
		}
	} while (TRUE);
}

BOOL CStepServer::IsInRootBank(const CSourceAddr& Addr) const
{
	// Is in root bank
	DWORD dwCallAddr, dwRetAddr;
	if ( ::GetBankAddr(dwCallAddr, dwRetAddr) ) {
//		if ( Addr >= dwCallAddr && Addr <= dwRetAddr ) {
		if ( Addr == dwCallAddr ) {
			return TRUE;
		}
	}
	
	return FALSE;
}

BOOL CStepServer::IsInNewSourceLine(const CSourceAddr& Addr, const BOOL bAfterRun /* = TRUE */)
{
	// Test Addr
	int nLine, nStmt;
	SYM_DESCRIPTOR dwModuleDesc;
	if ( ::SrcIsInSourceLine(Addr, nLine, nStmt, dwModuleDesc) ) {
		if ( !bAfterRun ) {
			return TRUE;
		}
		if ( dwModuleDesc != m_dwModuleDesc || nLine != m_nLine || nStmt != m_nStmt ) {
			// Save current Module & Line & Stmt
			m_nLine = nLine;
			m_nStmt = nStmt;
			m_dwModuleDesc = dwModuleDesc;
			
			return TRUE;
		}
	}

	return FALSE;
}

BOOL CStepServer::IsMatchCall(const CSourceAddr& Addr, CSourceAddr& AddrLib, CSourceAddr& AddrNext) const
{
	// Is SCALL or LCALL?
	::ADDR addr1, addr2;
	addr1.addr = Addr.GetAddr();
	addr1.addrType = Addr.GetType();
	
	if ( ::IsScall(addr1, addr2) ) {
		AddrLib = CSourceAddr(addr2.addr, addr2.addrType);
		AddrNext = Addr + 2;
	}
	else if ( ::IsLcall(addr1, addr2) ) {
		AddrLib = CSourceAddr(addr2.addr, addr2.addrType);
		AddrNext = Addr + 3;
	}
	else {
		return FALSE;
	}
	
	return TRUE;
}

BOOL CStepServer::IsMatchRet(const CSourceAddr& Addr) const
{
	// Get 1 byte memory
	::ADDR addr;
	addr.addrType = Addr.GetType();
	addr.addr = Addr.GetAddr();

	BYTE bCode[2];
	::RET_ADDR RetAddr;
	
	if ( ICE_OK != ::emuGetMemN(addr, 1, bCode, &RetAddr) ) {
		return FALSE;
	}
	
	// Ret: 0xF0
	return (0xF0 == bCode[0]);
}

BOOL CStepServer::IsEPStop() const
{
	// Waiting if EP is running
	DWORD dwStatus;
	do {
		// ESC to abort
		if ( ::IsTestKey(VK_ESCAPE) ) {
			if ( (dwStatus & 0x10) ) {
				::emuAbort();
			}
			return FALSE;
		}

		// Check EP status
		if ( ICE_OK != ::emuGetCpuStatus(&dwStatus) ) {
			::SrcDisplayErrorMessage(::errCpuStatus, m_bInShell);
			return FALSE;
		}

		// ESC to abort
		if ( ::IsTestKey(VK_ESCAPE) ) {
			if ( (dwStatus & 0x10) ) {
				::emuAbort();
			}
			return FALSE;
		}
	} while	( dwStatus & 0x10 );
	
	return TRUE;
}

void CStepServer::ShowNextCode() const
{
	// Get PC
	CSourceAddr AddrPC;
	if ( !::SrcGetPC(AddrPC) ) {
		return;
	}

	// Allocate list
	CStringList* pCodeList = new CStringList;
	CStringList* pInstList = new CStringList;
	CStringList* pSymList  = new CStringList;
	CStringList* pAddrList = new CStringList;

	// Call DAD server to get 1 instruction
	if ( ::SrcGetAsmCodeLine(AddrPC, 1, pCodeList, pInstList, pSymList, pAddrList) ) {
		CString strLine = pAddrList->GetHead() + ::szTab + pCodeList->GetHead() + ::szTab + pInstList->GetHead();
		::SrcShowLine(strLine);
	}

	// Delete object
	pCodeList->RemoveAll();
	delete pCodeList;
	
	pInstList->RemoveAll();
	delete pInstList;

	pSymList->RemoveAll();
	delete pSymList;

	pAddrList->RemoveAll();
	delete pAddrList;
}

