#include "Debuggers.h"

#define KEYGROUPNAME "Debugger"
#define KEYNAMESPACE PdbKeys
#define KEYFILE      <ide/Debuggers/Pdb.key>
#include             <CtrlLib/key_source.h>

#ifdef PLATFORM_WIN32

#include <tlhelp32.h>

Vector<DWORD> GetChildProcessList(DWORD processId) {
	Vector<DWORD> child, all, parents;
	
	HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (hSnap == INVALID_HANDLE_VALUE)
		return child;
	
	PROCESSENTRY32 proc;
	proc.dwSize = sizeof(proc);
	
	if (!Process32First(hSnap, &proc)) {
		CloseHandle(hSnap);
		return child;
	}
	
	do {
		all << proc.th32ProcessID;
		parents << proc.th32ParentProcessID;
    } while(Process32Next(hSnap, &proc));
	
	CloseHandle(hSnap);
	
	child << processId;
	int init = 0;
	while (true) {
		int count = child.GetCount();
		if (init >= count)
			break;
		for (int cid = init; cid < count; ++cid) {
			for (int i = 0; i < all.GetCount(); ++i) {
				if (parents[i] == child[cid])
					child << all[i];
			}
		}
		init = count;
	}
	child.Remove(0);
	return child;
}

void TerminateChildProcesses(DWORD dwProcessId, UINT uExitCode) {
	Vector<DWORD> children = GetChildProcessList(dwProcessId);
	for (int i = 0; i < children.GetCount(); ++i) {
		HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, children[i]);
		TerminateProcess(hProcess, uExitCode);
		CloseHandle(hProcess);
	}
}

#pragma comment(lib, "DbgHelp.lib")
#pragma comment(lib, "psapi.lib")

#define LLOG(x) // LOG(x)

using namespace PdbKeys;

void Pdb::DebugBar(Bar& bar)
{
	bar.Add(AK_STOP, DbgImg::StopDebug(), THISBACK(Stop));
	bool b = !IdeIsDebugLock();
	bar.Separator();
	bar.Add(b, AK_STEPINTO, DbgImg::StepInto(), THISBACK1(Trace, false));
	bar.Add(b, AK_STEPOVER, DbgImg::StepOver(), THISBACK1(Trace, true));
	bar.Add(b, AK_STEPOUT, DbgImg::StepOut(), THISBACK(StepOut));
	bar.Add(b, AK_RUNTO, DbgImg::RunTo(), THISBACK(DoRunTo));
	bar.Add(b, AK_RUN, DbgImg::Run(), THISBACK(Run));
	bar.Add(b, AK_SETIP, DbgImg::SetIp(), THISBACK(SetIp));
	bar.Add(!b, AK_BREAK, DbgImg::Stop(), THISBACK(BreakRunning));
	bar.MenuSeparator();
	bar.Add(b, AK_AUTOS, THISBACK1(SetTab, 0));
	bar.Add(b, AK_LOCALS, THISBACK1(SetTab, 1));
	bar.Add(b, AK_THISS, THISBACK1(SetTab, 2));
	bar.Add(b, AK_WATCHES, THISBACK1(SetTab, 3));
	bar.Add(b, AK_CLEARWATCHES, THISBACK(ClearWatches));
	bar.Add(b, AK_ADDWATCH, [=] { AddWatch(); });
	bar.Add(b, AK_CPU, THISBACK1(SetTab, 4));
	bar.Add(b, AK_MEMORY, THISBACK1(SetTab, 5));
	bar.Add(b, AK_BTS, THISBACK1(SetTab, 6));
	bar.MenuSeparator();
	bar.Add(b, "Copy backtrace", THISBACK(CopyStack));
	bar.Add(b, "Copy backtrace of all threads", THISBACK(CopyStackAll));
	bar.Add(b, "Copy dissassembly", THISBACK(CopyDisas));
	bar.Add(b, "Copy modules", THISBACK(CopyModules));
}

void Pdb::Tab()
{
	switch(tab.Get()) {
	case TAB_AUTOS: autos.SetFocus(); break;
	case TAB_LOCALS: locals.SetFocus(); break;
	case TAB_THIS: self.SetFocus(); break;
	case TAB_WATCHES: watches.SetFocus(); break;
	case TAB_MEMORY: memory.SetFocus(); break;
	case TAB_BTS: bts.SetFocus(); break;
	}
	Data();
	SyncTreeDisas();
}

void Pdb::SyncTreeDisas()
{
	bool d = tab.Get() == TAB_CPU || IsNull(tree_exp);
	disas.Show(d);
	tree.Show(!d);
}

bool Pdb::Key(dword key, int count)
{
	if(key >= 32 && key < 65535 && tab.Get() == TAB_LOCALS) {
		watches.DoInsertAfter();
		Ctrl* f = GetFocusCtrl();
		if(f && watches.HasChildDeep(f))
			f->Key(key, count);
		return true;
	}
	return Ctrl::Key(key, count);
}

#define CONFIGNAME "pdb debugger"

void Pdb::Serialize(Stream& s)
{
	int version = 0;
	s / version;
	memory.SerializeSettings(s);
	Splitter dummy;
	s % dummy;
	s % show_type;
}

INITBLOCK
{
	RegisterGlobalConfig(CONFIGNAME);
}

bool Pdb::Create(Host& local, const String& exefile, const String& cmdline, bool clang_)
{
	STARTUPINFO si;
	ZeroMemory(&si, sizeof(STARTUPINFO));
	si.cb = sizeof(STARTUPINFO);
	si.dwFlags = 0;
	String cl;
	if(exefile.Find(' ') >= 0)
		cl << '\"' << exefile << '\"';
	else
		cl << exefile;
	if(!IsNull(cmdline))
		cl << ' ' << ToSystemCharset(cmdline);

	clang = clang_;

	Buffer<char> cmd(cl.GetLength() + 1);
	memcpy(cmd, cl, cl.GetLength() + 1);
	PROCESS_INFORMATION pi;
	ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
	Buffer<char> env(local.environment.GetCount() + 1);
	memcpy(env, ~local.environment, local.environment.GetCount() + 1);
	bool h = CreateProcess(exefile, cmd, NULL, NULL, TRUE,
	                       /*NORMAL_PRIORITY_CLASS|CREATE_NEW_CONSOLE|*/DEBUG_ONLY_THIS_PROCESS/*|DEBUG_PROCESS*/,
	                       ~env, NULL, &si, &pi);

	if(!h) {
		Exclamation("Error creating process&[* " + DeQtf(exefile) + "]&" +
		            "Windows error: " + DeQtf(GetLastErrorMessage()));
		return false;
	}
	hProcess = pi.hProcess;
	mainThread = pi.hThread;
	hProcessId = pi.dwProcessId;
	mainThreadId = pi.dwThreadId;

#ifdef CPU_64
	BOOL _64;
	win64 = IsWow64Process(hProcess, &_64) && !_64;
	LLOG("Win64 app: " << win64);
	disas.Mode64(win64);
#else
	win64 = false;
#endif

	if(win64)
		memory.SetTotal(I64(0xffffffffffff));
	else
		memory.SetTotal(0x80000000);
	
	CloseHandle(pi.hThread);

	IdeSetRight(rpane);
	IdeSetBottom(*this);
	
	SyncTreeDisas();
	
	LoadFromGlobal(*this, CONFIGNAME);

	if(!SymInitialize(hProcess, 0, FALSE)) {
		Error();
		Exclamation("Failed to load symbols");
		return false;
	}
	SymSetOptions(SYMOPT_LOAD_LINES|SYMOPT_UNDNAME|SYMOPT_NO_UNQUALIFIED_LOADS);

	lock = 0;
	stop = false;
	refreshmodules = true;
	terminated = false;

	running = true;
	
	break_running = false;

	RunToException();
	
	return !terminated;
}

INITBLOCK {
	RegisterWorkspaceConfig("pdb-debugger");
}

void Pdb::SerializeSession(Stream& s)
{
	int version = 0;
	s / version;
	int n = watches.GetCount();
	s / n;
	if(n < 0)
		LoadingError();
	for(int i = 0; i < n; i++) {
		String w;
		if(s.IsStoring())
			w = watches.Get(i, 0);
		s % w;
		if(s.IsLoading())
			watches.Add(w);
	}
}

struct CpuRegisterDisplay : Display {
	virtual void Paint(Draw& w, const Rect& r, const Value& q, Color ink, Color paper, dword style) const
	{
		Font fnt = Courier(Ctrl::HorzLayoutZoom(12));
		static int cx1 = GetTextSize("EFLAGS12", fnt().Bold()).cx +
		                 GetTextSize("0000 0000 0000 0000", fnt).cx;
		String name;
		String value;
		String odd;
		SplitTo((String)q, '|', name, value, odd);
		w.DrawRect(r, odd != "1" || (style & CURSOR) ? paper : Blend(SColorMark, SColorPaper, 220));
		int i = value.GetLength() - 4;
		while(i > 0) {
			value.Insert(i, ' ');
			i -= 4;
		}
		Size tsz = GetTextSize(value, fnt);
		int tt = r.top + max((r.Height() - tsz.cy) / 2, 0);
		w.DrawText(r.left, tt, name, fnt().Bold(), ink);
		w.DrawText(r.left + cx1 - tsz.cx, tt, value, fnt, ink);
	}
};

Pdb::Pdb()
:	visual_display(this) {
	hWnd = NULL;
	hProcess = INVALID_HANDLE_VALUE;
	current_frame = NULL;

	autos.NoHeader();
	autos.AddColumn("", 1);
	autos.AddColumn("", 6).SetDisplay(visual_display);
	autos.WhenEnterRow = THISBACK1(SetTreeA, &autos);
	autos.WhenBar = [=](Bar& bar) { DataMenu(autos, bar); };
	autos.EvenRowColor();
	autos.WhenLeftDouble << [=] { AddWatch(autos.GetKey()); };

	locals.NoHeader();
	locals.AddColumn("", 1);
	locals.AddColumn("", 6).SetDisplay(visual_display);
	locals.WhenEnterRow = THISBACK1(SetTreeA, &locals);
	locals.WhenBar = [=](Bar& bar) { DataMenu(locals, bar); };
	locals.EvenRowColor();
	locals.WhenLeftDouble << [=] { AddWatch(locals.GetKey()); };

	self.NoHeader();
	self.AddColumn("", 1);
	self.AddColumn("", 6).SetDisplay(visual_display);
	self.WhenEnterRow = THISBACK1(SetTreeA, &self);
	self.WhenBar = [=](Bar& bar) { DataMenu(self, bar); };
	self.EvenRowColor();
	self.WhenLeftDouble << [=] { AddWatch(self.GetKey()); };

	watches.NoHeader();
	watches.AddColumn("", 1).Edit(watchedit);
	watches.AddColumn("", 6).SetDisplay(visual_display);
	watches.Moving();
	watches.WhenEnterRow = THISBACK1(SetTreeA, &watches);
	watches.WhenBar = THISBACK(WatchesMenu);
	watches.WhenAcceptEdit = THISBACK(Data);
	watches.WhenDrop = THISBACK(DropWatch);
	watches.EvenRowColor();

	tab.Add(autos.SizePos(), "Autos");
	tab.Add(locals.SizePos(), "Locals");
	tab.Add(self.SizePos(), "this");
	tab.Add(watches.SizePos(), "Watches");
	tab.Add(cpu.SizePos(), "CPU");
	tab.Add(memory.SizePos(), "Memory");
	tab.Add(bts.SizePos(), "Threads");

	cpu.Columns(4);
	cpu.ItemHeight(Courier(Ctrl::HorzLayoutZoom(12)).GetCy());
	cpu.SetDisplay(Single<CpuRegisterDisplay>());

	memory.pdb = this;

	dlock = "  Running..";
	dlock.SetFrame(BlackFrame());
	dlock.SetInk(Red);
	dlock.NoTransparent();
	dlock.Hide();
	framelist.Ctrl::Add(dlock.SizePos());

	pane.Add(tab.SizePos());
	pane.Add(threadlist.LeftPosZ(370, 60).TopPos(2, EditField::GetStdHeight()));
	int bcx = min(EditField::GetStdHeight(), DPI(16));
	pane.Add(framelist.HSizePos(Zx(434), 2 * bcx).TopPos(2, EditField::GetStdHeight()));
	pane.Add(frame_up.RightPos(bcx, bcx).TopPos(2, EditField::GetStdHeight()));
	frame_up.SetImage(DbgImg::FrameUp());
	frame_up << [=] { FrameUpDown(-1); };
	frame_up.Tip("Previous Frame");
	pane.Add(frame_down.RightPos(0, bcx).TopPos(2, EditField::GetStdHeight()));
	frame_down.SetImage(DbgImg::FrameDown());
	frame_down << [=] { FrameUpDown(1); };
	frame_down.Tip("Next Frame");

	Add(pane.SizePos());

	disas.WhenCursor = THISBACK(DisasCursor);
	disas.WhenFocus = THISBACK(DisasFocus);

	memory.WhenGoto = THISBACK(MemoryGoto);

	tab <<= THISBACK(Tab);

	framelist <<= THISBACK(SetFrame);
	threadlist <<= THISBACK(SetThread);

	tree.WhenOpen = THISBACK(TreeExpand);
	tree.WhenBar = THISFN(TreeMenu);
	tree.WhenLeftDouble = THISFN(TreeWatch);
	
	rpane.Add(disas.SizePos());
	rpane.Add(tree.SizePos());

	FileIn in(ConfigFile("TreeTypes.txt"));
	while(!in.IsEof()) {
		String type = in.GetLine();
		String desc = in.GetLine();
		treetype.Add(type, desc);
	}
	StringStream ss(WorkspaceConfigData("pdb-debugger"));
	Load(callback(this, &Pdb::SerializeSession), ss);
}

void Pdb::CopyStack()
{
	String s;
	for(int i = 0; i < framelist.GetCount(); i++) {
		s << framelist.GetValue(i);
		FilePos fp = GetFilePos(frame[i].pc);
		if(fp)
			s << " at " << fp.path << " " << fp.line + 1;
		s << "\n";
	}
	WriteClipboardText(s);
}

void Pdb::CopyStackAll()
{
	String s;
	for(int i = 0; i < threads.GetCount(); i++) {
		int thid = threads.GetKey(i);
		s << "----------------------------------\r\n"
		  << "Thread ID: " << Format("0x%x", thid) << "\r\n\r\n";
		for(const auto& f : Backtrace(threads[i]))
			s << f.text << "\r\n";
		s << "\r\n";
	}
	WriteClipboardText(s);
}

void Pdb::CopyDisas()
{
	disas.WriteClipboard();
}

void Pdb::CopyModules()
{
	String s;
	for(const ModuleInfo& f : module)
		s << f.path << ", base 0x" << FormatIntHex((void *)f.base)
		  << ", size: 0x" << FormatIntHex(f.size) << "\n";
	WriteClipboardText(s);
}

void Pdb::Stop()
{
	if(!terminated) {
		terminated = true;
		SaveTree();
		String fn = ConfigFile("TreeTypes.txt");
		FileOut out(fn);
		for(int i = 0; i < treetype.GetCount(); i++)
			out << treetype.GetKey(i) << "\r\n" << treetype[i] << "\r\n";
		StringStream ss;
		Store(callback(this, &Pdb::SerializeSession), ss);
		WorkspaceConfigData("pdb-debugger") = ss;
		if(hProcess != INVALID_HANDLE_VALUE) {
			for(int i = 0; i < 10; i++) {
				if(DebugActiveProcessStop(processid))
					break;
				Sleep(100);
			}
			TerminateChildProcesses(hProcessId, 0);
			TerminateProcess(hProcess, 0);
			dword exitcode = STILL_ACTIVE;
			int start = msecs();
			while(GetExitCodeProcess(hProcess, &exitcode) && exitcode == STILL_ACTIVE && msecs(start) < 1000)
				Sleep(100);
			if(exitcode == STILL_ACTIVE)
				Exclamation("Unable to kill debugee. Please restart theide.");
			while(threads.GetCount())
				RemoveThread(threads.GetKey(0)); // To CloseHandle
			UnloadModuleSymbols();
			SymCleanup(hProcess);
			CloseHandle(hProcess);
		}
		StoreToGlobal(*this, CONFIGNAME);
		IdeRemoveBottom(*this);
		IdeRemoveRight(rpane);
	}
}

bool Pdb::IsFinished()
{
	return terminated;
}

Pdb::~Pdb()
{
	Stop();
}

One<Debugger> PdbCreate(Host& host, const String& exefile, const String& cmdline, bool clang)
{
	One<Debugger> dbg;
	if(!dbg.Create<Pdb>().Create(host, exefile, cmdline, clang))
		dbg.Clear();
	return dbg;
}

#define LAYOUTFILE <ide/Debuggers/Pdb.lay>
#include <CtrlCore/lay.h>

#define TOPICFILE <ide/Debuggers/app.tpp/all.i>
#include <Core/topic_group.h>

struct PDBExpressionDlg : WithEditPDBExpressionLayout<TopWindow> {
	Pdb *pdb;

	void Sync();

	typedef PDBExpressionDlg CLASSNAME;

	PDBExpressionDlg(const char *title, String& brk, Pdb *pdb);
};

void PDBExpressionDlg::Sync()
{
	if(pdb)
		value <<= RawPickToValue(pick(pdb->Visualise(~text)));
}

PDBExpressionDlg::PDBExpressionDlg(const char *title, String& brk, Pdb *pdb)
:	pdb(pdb)
{
	CtrlLayoutOKCancel(*this, title);
	help.SetQTF(GetTopic("ide/Debuggers/app/PDBExpressions_en-us"));
	help.Background(White());
	help.SetFrame(ViewFrame());
	text <<= brk;
	text <<= THISBACK(Sync);
	value.SetDisplay(pdb->visual_display);
	value.Show(pdb);
	value_lbl.Show(pdb);
	Sync();
}

bool EditPDBExpression(const char *title, String& brk, Pdb *pdb)
{
	PDBExpressionDlg dlg(title, brk, pdb);
	if(dlg.Execute() != IDOK)
		return false;
	brk = ~dlg.text;
	return true;
}

#endif
