// ---------------------------------------------------------------------------
//  M88 - PC-8801 emulator
//	Copyright (C) cisc 1999, 2000.
// ---------------------------------------------------------------------------
//	$Id: codemon.cpp,v 1.8 2001/02/21 11:58:53 cisc Exp $

#include "headers.h"
#include "resource.h"
#include "pc88/pc88.h"
#include "codemon.h"
#include "misc.h"
#include "device_i.h"
#include "winvars.h"
#include "88config.h"

using namespace PC8801;

// ---------------------------------------------------------------------------
//	構築/消滅
//
CodeMonitor::CodeMonitor()
: core(0), pcfollow(true), lasta0(MemoryViewer::mainram)
{
}

CodeMonitor::~CodeMonitor()
{
}

bool CodeMonitor::Init(WinCore* wincore)
{
	core = wincore;
	// 行頭に PC マーカ/BP マーカ用の1文字を足すため、既定カラム数は
	// 42 ではなく 44 にする
	if (!MemViewMonitor::Init(MAKEINTRESOURCE(IDD_CODEMON), wincore, "MonitorCode", 44, 24))
		return false;
	if (!diag.Init(GetBus()))
		return false;

	SetLines(0x10000);
	SetUpdateTimer(500);
	return true;
}

// ---------------------------------------------------------------------------
//	PCFollow の ON/OFF を M88.ini に保存/復元する(既定 ON)
//
void CodeMonitor::LoadMonitorConfig()
{
	MemViewMonitor::LoadMonitorConfig();
	pcfollow = GetPrivateProfileInt("MonitorCode", "PCFollow", 1, PC8801::GetM88IniPath()) != 0;
}

void CodeMonitor::SaveMonitorConfig()
{
	MemViewMonitor::SaveMonitorConfig();
	WritePrivateProfileString("MonitorCode", "PCFollow", pcfollow ? "1" : "0", PC8801::GetM88IniPath());
}

// ---------------------------------------------------------------------------
//	ブレーク発生位置へスクロールする(PC追従モードが有効なときだけ)。
//	PC が既に画面内(行の先頭アドレスとして見えている)なら動かさない。
//	表示中のバンクとブレークした CPU が食い違うときは追従しない
//
void CodeMonitor::FollowPC()
{
	if (!pcfollow || !IsOpen())
		return;

	Debugger* dbg = core->GetDebugger();
	// ブレーク中は、ブレークした CPU を表示しているときだけ追従する
	// (別の CPU を見ているのに勝手にスクロールさせない)
	if (dbg->IsBroken() && dbg->GetBreakCpu() != GetShownCpu())
		return;

	ScrollToPC();
}

// ---------------------------------------------------------------------------
//	表示中の CPU の PC へスクロールする。FollowPC と違い、ブレークした CPU と
//	一致しているかどうかは見ない(バンクを手で切り替えたときに使う。
//	「別の CPU を見ている」ことがそのまま操作の意図なので、ここでは弾かない)
//
void CodeMonitor::FollowShownPC()
{
	if (!pcfollow || !IsOpen())
		return;

	ScrollToPC();
}

// ---------------------------------------------------------------------------
//	表示中の CPU の PC が既に画面内(行の先頭アドレスとして見えている)なら
//	動かさない。それ以外は画面の1/4くらいの位置に置き直す
//
void CodeMonitor::ScrollToPC()
{
	Debugger::CpuTarget shown = GetShownCpu();
	PC88::Z80* cpu = (shown == Debugger::cpu_sub) ? core->GetCPU2() : core->GetCPU1();
	uint pc = cpu->GetPC();

	for (size_t y = 0; y < lineaddr.size(); y++)
	{
		if (lineaddr[y] == pc)
		{
			// 命令境界として既に見えている。スクロールしない
			Update();
			return;
		}
	}

	// 画面外(またはジャンプ/自己書き換えで命令境界がずれた)。
	// PC を画面の1/4くらいの位置に置き直す。InstDec は完全ではないので、
	// InstInc で辿り直して pc にちょうど乗るか必ず検証する
	int rows = GetHeight();
	int k = Max(1, rows / 4);

	uint candidate = pc;
	for (int i = 0; i < k; i++)
	{
		if (candidate == 0)
			break;
		uint prev = diag.InstDec(candidate);
		if (prev >= candidate)
			break;
		candidate = prev;
	}

	bool hit = false;
	uint a = candidate;
	for (int i = 0; i < rows; i++)
	{
		if (a == pc)
		{
			hit = true;
			break;
		}
		if (a >= 0x10000)
			break;
		a = diag.InstInc(a);
	}

	SetLine(hit ? candidate : pc);
	Update();
}

// ---------------------------------------------------------------------------
//	表示中のバンクに対応する CPU
//
Debugger::CpuTarget CodeMonitor::GetShownCpu()
{
	return (GetA0() == MemoryViewer::sub) ? Debugger::cpu_sub : Debugger::cpu_main;
}

// ---------------------------------------------------------------------------
//	表示する CPU を切り替える。main へ戻すときは、sub へ切り替える前に
//	見ていたバンクを復元する
//
void CodeMonitor::ShowCpu(Debugger::CpuTarget cpu)
{
	if (!IsOpen() || GetShownCpu() == cpu)
		return;

	if (cpu == Debugger::cpu_sub)
	{
		lasta0 = GetA0();			// main 側で見ていたバンクを覚えておく
		SetA0(MemoryViewer::sub);
	}
	else
	{
		SetA0(lasta0);
	}
	SetBank();						// 中で Update() まで済む
}

// ---------------------------------------------------------------------------
//	表示中の CPU / バンクをタイトルバーに出す
//	(ブレークに連動して自動で切り替わるので、いま何を見ているのかが
//	 分かるようにしておく)
//
void CodeMonitor::UpdateCaption()
{
	if (!IsOpen())
		return;

	const char* bank;
	switch (GetA0())
	{
	case MemoryViewer::sub:		bank = "SUB"; break;
	case MemoryViewer::n88rom:	bank = "MAIN - N88 ROM"; break;
	case MemoryViewer::nrom:	bank = "MAIN - N ROM"; break;
	case MemoryViewer::eram0:	bank = "MAIN - ERAM0"; break;
	case MemoryViewer::eram1:	bank = "MAIN - ERAM1"; break;
	case MemoryViewer::eram2:	bank = "MAIN - ERAM2"; break;
	case MemoryViewer::eram3:	bank = "MAIN - ERAM3"; break;
	default:					bank = "MAIN - RAM"; break;
	}

	char buf[64];
	wsprintf(buf, "Code  [%s]", bank);
	SetWindowText(GetHWnd(), buf);
}

void CodeMonitor::SetBank()
{
	MemViewMonitor::SetBank();
	UpdateCaption();
}

// ---------------------------------------------------------------------------
//	ダイアログ処理
//
BOOL CodeMonitor::DlgProc(HWND hdlg, UINT msg, WPARAM wp, LPARAM lp)
{
	POINT p;

	switch (msg)
	{
	case WM_COMMAND:
		switch (LOWORD(wp))
		{
		case IDM_MEM_SAVEIMAGE:
			DumpImage();
			break;

		case IDM_CODE_PCFOLLOW:
			pcfollow = !pcfollow;
			break;
		}
		break;

	case WM_INITMENU:
		CheckMenuItem((HMENU) wp, IDM_CODE_PCFOLLOW, pcfollow ? MF_CHECKED : MF_UNCHECKED);
		break;

	case WM_LBUTTONDOWN:
		p.x = LOWORD(lp);
		p.y = HIWORD(lp);
		if (GetTextPos(&p))
			ToggleBreakAt(p);
		break;
	}

	BOOL r = MemViewMonitor::DlgProc(hdlg, msg, wp, lp);

	// $0000 のバンクを手で切り替えたときは、止まっていれば表示中の CPU の
	// PC を見せる。切り替えただけでは表示位置が前のままで、PC の行が
	// 画面外にいると緑の行が見えない
	if (msg == WM_COMMAND && core->IsStopped())
	{
		switch (LOWORD(wp))
		{
		case IDM_MEM_0_RAM:   case IDM_MEM_0_N88:   case IDM_MEM_0_N:
		case IDM_MEM_0_SUB:
		case IDM_MEM_0_ERAM0: case IDM_MEM_0_ERAM1:
		case IDM_MEM_0_ERAM2: case IDM_MEM_0_ERAM3:
			FollowShownPC();
			break;
		}
	}
	return r;
}

// ---------------------------------------------------------------------------
//	クリックされた行のアドレスの実行ブレークポイントをトグルする。
//	表示中のバンク(&0000 が Sub かどうか)に応じて、対象 CPU を切り替える
//
void CodeMonitor::ToggleBreakAt(POINT p)
{
	if (p.y < 0 || size_t(p.y) >= lineaddr.size())
		return;

	uint addr = lineaddr[p.y];
	if (addr >= 0x10000)
		return;

	Debugger::CpuTarget cpu = (GetA0() == MemoryViewer::sub) ? Debugger::cpu_sub : Debugger::cpu_main;

	core->Lock();
	Debugger* dbg = core->GetDebugger();
	dbg->SetExecBreak(cpu, addr, !dbg->IsExecBreak(cpu, addr));
	core->Unlock();

	Update();
}

// ---------------------------------------------------------------------------
//	スクロールバー処理
//
int CodeMonitor::VerticalScroll(int msg)
{
	int addr = GetLine();
	
	switch (msg)
	{
		int i;

	case SB_LINEUP:
		addr = diag.InstDec(addr);
		if (addr >= 0)
			ScrollDown();
		break;

	case SB_LINEDOWN:
		addr = diag.InstInc(addr);
		if (addr < 0x10000)
			ScrollUp();
		break;

	case SB_PAGEUP:
		for (i=1; i<GetHeight() && addr > 0; i++)
			addr = diag.InstDec(addr);
		break;

	case SB_PAGEDOWN:
		for (i=1; i<GetHeight() && addr < 0x10000; i++)
			addr = diag.InstInc(addr);
		break;

	case SB_TOP:
		addr = 0;
		break;

	case SB_BOTTOM:
		addr = 0x10000-1;
		break;

	case SB_THUMBTRACK:
	case SB_THUMBPOSITION:
		addr = GetScrPos(msg == SB_THUMBTRACK);
		break;
	}
	return Limit(addr, 0xffff, 0);
}

static inline void ToHex(char** p, uint d)
{
	static const char hex[] = "0123456789abcdef";

	*(*p)++ = hex[d >> 4];
	*(*p)++ = hex[d & 15];
}

void CodeMonitor::UpdateText()
{
	char buf[128];
	int a = GetLine();

	// PC マーカ。表示中のバンクに対応する CPU の PC だけをマークする。
	// 別の CPU の PC を重ねると、その行には全く関係ないコードが表示されて
	// いるのに緑になってしまう。マーカはポーズ中だけ出す (実行中に出すと
	// 「止まっている位置」と紛らわしい)
	bool subshown = (GetShownCpu() == Debugger::cpu_sub);
	int pc = -1;
	if (core->IsPaused())
		pc = (subshown ? core->GetCPU2() : core->GetCPU1())->GetPC();

	int rows = GetHeight();
	lineaddr.resize(rows);

	for (int y=0; y<rows; y++)
	{
		if (a < 0x10000)
		{
			lineaddr[y] = uint(a);

			bool hasbp = core->GetDebugger()->IsExecBreak(Debugger::cpu_main, a) ||
						 core->GetDebugger()->IsExecBreak(Debugger::cpu_sub, a);

			char mark;
			if (pc == a)
			{
				mark = subshown ? 'S' : 'M';
				SetBkCol(RGB(0, 0x60, 0));
			}
			else if (hasbp)
			{
				mark = '*';
				SetBkCol(RGB(0x60, 0, 0));
			}
			else
			{
				mark = ' ';
				// 実行回数に基づいて色をつけてみる
				SetBkCol(RGB(StatExec(a), 0, 0x20));
			}

			int next = diag.Disassemble(a, buf+8);
			char* ptr = buf;
			int c, d = next-a;
			for (c=0; c<d; c++)
				ToHex(&ptr, GetBus()->Read8((a+c) & 0xffff));
			for (; c<4; c++)
				*ptr++ = ' ', *ptr++ = ' ';
			Putf("%c %.4x: %.8s   %s\n", mark, a, buf, buf+8);
			a = next;
		}
		else
		{
			lineaddr[y] = uint(-1);
			Puts("\n");
		}
	}
	StatClear();
}

bool CodeMonitor::Dump(FILE* fp, int from, int to)
{
	char buf[128];
	for (int a=from; a<to; a)
	{
		int next = diag.Disassemble(a, buf+8);
		
		char* ptr = buf;
		int c, d = next-a;
		for (c=0; c<d; c++)
			ToHex(&ptr, GetBus()->Read8((a+c) & 0xffff));
		for (; c<4; c++)
			*ptr++ = ' ', *ptr++ = ' ';

		fprintf(fp, "%.4x %.8s\t\t%s\n", a, buf, buf+8);
		a = next;
	}
	return true;
}

// ----------------------------------------------------------------------------
//	総ダンプを書き込む
//	
bool CodeMonitor::DumpImage()
{
	// ダイアログ
	OFNV5 ofn;
	memset(&ofn, 0, sizeof(ofn));
	ofn.lStructSize = WINVAR(OFNSIZE);
	ofn.FlagsEx = OFN_EX_NOPLACESBAR;

	char filename[MAX_PATH];
	filename[0] = 0;
	
	ofn.hwndOwner = GetHWnd();
	ofn.lpstrFilter = "assembly file (*.asm)\0*.asm\0"
					  "All Files (*.*)\0*.*\0";
	ofn.lpstrFile = filename;
	ofn.nMaxFile = MAX_PATH;
	ofn.Flags = OFN_CREATEPROMPT;
	ofn.lpstrDefExt = "asm";
	ofn.lpstrTitle = 0;
	
	if (!GetSaveFileName(&ofn))
		return false;

	// 書き込み
	FILE* fp = fopen(filename, "w");
	if (fp)
	{
		Dump(fp, 0, 0x10000);
		fclose(fp);
		return true;
	}
	return false;
}
