#include "Debuggers.h"

#ifdef PLATFORM_WIN32

#define LLOG(x)  // DLOG(x)

Pdb::Thread& Pdb::Current()
{
	int q = threads.Find((int)~threadlist);
	if(q >= 0)
		return threads[q];

	static Thread zero;
	return zero;
}

Array<Pdb::Frame> Pdb::Backtrace(Thread& ctx, bool single_frame, bool thread_info)
{
	Array<Frame> frame;
	STACKFRAME64 stackFrame = {0};
	stackFrame.AddrPC.Mode = stackFrame.AddrFrame.Mode = stackFrame.AddrStack.Mode = AddrModeFlat;
	DWORD machineType = IMAGE_FILE_MACHINE_I386;
	void *c;
	Thread cctx = ctx;
#ifdef CPU_64
	if(win64) {
		machineType = IMAGE_FILE_MACHINE_AMD64;
		stackFrame.AddrPC.Offset = ctx.context64.Rip;
		stackFrame.AddrFrame.Offset = ctx.context64.Rbp;
		stackFrame.AddrStack.Offset = ctx.context64.Rsp;
		c = &cctx.context64;
	}
	else
#endif
	{
		machineType = IMAGE_FILE_MACHINE_I386;
		stackFrame.AddrPC.Offset = ctx.context32.Eip;
		stackFrame.AddrFrame.Offset = ctx.context32.Ebp;
		stackFrame.AddrStack.Offset = ctx.context32.Esp;
		c = &cctx.context32;
	}

	DWORD64 lastFrame = 0; // Prevent loops with optimised stackframes
	while(::StackWalk64(machineType, hProcess, ctx.hThread, &stackFrame, c,
	      0, ::SymFunctionTableAccess64, ::SymGetModuleBase64, 0)) {
	    if(stackFrame.AddrPC.Offset == 0 || lastFrame >= stackFrame.AddrFrame.Offset) {
			LLOG("Invalid stack frame");
			break;
	    }
		lastFrame = stackFrame.AddrFrame.Offset;
		LLOG("PC: " << Hex(stackFrame.AddrPC.Offset));
		Frame& f = frame.Add();
		f.pc = stackFrame.AddrPC.Offset;
		f.frame = stackFrame.AddrFrame.Offset;
		f.stack = stackFrame.AddrStack.Offset;
		f.fn = GetFnInfo(f.pc);
		if(thread_info) {
			if(frame.GetCount() > 20)
				break;
		}
		else {
			if(IsNull(f.fn.name)) {
				if(single_frame)
					return frame;
				f.text = Hex(f.pc);
				for(int i = 0; i < module.GetCount(); i++) {
					const ModuleInfo& m = module[i];
					if(f.pc >= m.base && f.pc < m.base + m.size) {
						f.text << " (" << GetFileName(m.path) << ")";
						break;
					}
				}
			}
			else {
				GetLocals(f, cctx, f.param, f.local);
				if(single_frame)
					return frame;
				f.text = f.fn.name;
				f.text << '(';
				for(int i = 0; i < f.param.GetCount(); i++) {
					if(i)
						f.text << ", ";
					f.text << f.param.GetKey(i) << "=" << Visualise(f.param[i], MEMBER).GetString();
				}
				f.text << ')';
			}
		}
	}
	return frame;
}

void Pdb::Sync0(Thread& ctx)
{
	stop = false;

	framelist.Clear();
	frame.Clear();
	current_frame.Clear();

	frame = Backtrace(ctx);
	for(const Frame& f : frame)
		framelist.Add(framelist.GetCount(), f.text);
	framelist.GoBegin();
}

static bool bts_lock;

void Pdb::UpdateBTs()
{
	if(tab.Get() != TAB_BTS) return;
	if(bts_lock) return;
	bts_lock = true;
	bts.KillCursor();
	int id = bts.Find(~~threadlist);
	if(id >= 0) {
		if(!bts.IsOpen(id))
			bts.Open(id);
		bts.FindSetCursor("#" + ~~framelist + "#" + ~~threadlist);
	}
	bts_lock = false;
}

void Pdb::BTs()
{
	if(bts_lock) return;
	
	int cid = bts.GetCursor();
	Value cursor = bts.Get();
	Point sc = bts.GetScroll();

	Index<String> open;
	for(int i = 0; i < bts.GetChildCount(0); i++) {
		int id = bts.GetChild(0, i);
		if(bts.IsOpen(id))
			open.FindAdd(~bts.Get(id));
	}
	
	bts.Clear();
	bts.NoRoot();

	bts.WhenOpen = [=](int id) {
		if(bts.GetChildCount(id) == 0) {
			String thid = ~bts.Get(id);
			int ii = threads.Find(atoi(thid));
			if(ii >= 0) {
				int i = 0;
				for(const auto& f : Backtrace(threads[ii])) {
					bts.Add(id, i == 0 ? DbgImg::IpLinePtr() : DbgImg::FrameLinePtr(),
					        "#" + AsString(i) + "#" + thid, f.text);
					i++;
				}
			}
		}
	};

	for(int i = 0; i < threads.GetCount(); i++) {
		dword thid = threads.GetKey(i);
		String stid = AsString(thid);
		String info;
		bool waiting = false;
		bool cowork = false;
		for(const Frame& f : Backtrace(threads[i], false, true)) {
			if(info.GetCount() + f.fn.name.GetCount() > 160) {
				info << "...";
				break;
			}
			bool zwait = f.fn.name.StartsWith("ZwWait");
			waiting |= zwait;
			if(!f.fn.name.StartsWith("RtlSleep") && !f.fn.name.StartsWith("ZwWaitForAlertByThreadId"))
				MergeWith(info, ", ", f.fn.name);
			if(f.fn.name == "Upp::CoWork::Pool::ThreadRun") {
				cowork = true;
				break;
			}
		}
		int id = bts.Add(0, DbgImg::Thread(), stid,
		                 AttrText(Format("0x%x ", (int)thid) + info)
		                 .NormalInk(waiting ? cowork ? Blend(SLtBlue, SGray(), 200) : SGray()
		                                    : cowork ? SLtBlue() : SColorText()),
		                 true);
		if(open.Find(stid) >= 0)
			bts.Open(id);
	}
	
	bts.FindSetCursor(cursor);
	bts.ScrollTo(sc);
	
	bts.WhenBar = [=](Bar& bar) {
		bar.Add("Open All", [=] {
			for(int i = 0; i < bts.GetChildCount(0); i++)
				bts.Open(bts.GetChild(0, i));
		});
		bar.Add("Close All", [=] {
			for(int i = 0; i < bts.GetChildCount(0); i++)
				bts.Close(bts.GetChild(0, i));
		});
	};
	
	bts << [=] {
		String k = ~bts.Get();
		if(*k == '#') {
			int id = bts.GetParent(bts.GetCursor());
			String thid = ~bts.Get(id);
			int tid = atoi(thid);
			int ii = threads.Find(tid);
			if(ii >= 0) {
				int i = 0;
				for(auto& f : Backtrace(threads[ii])) {
					if(k == "#" + AsString(i) + "#" + thid) {
						int tid = atoi(thid);
						if(threadlist.HasKey(tid)) {
							bts_lock = true;
							threadlist <<= tid;
							SetThread();
							if(i < framelist.GetCount()) {
								framelist.SetIndex(i);
								SetFrame();
								bts_lock = false;
								return;
							}
							bts_lock = false;
						}
					}
					i++;
				}
			}
		}
		else {
			int tid = StrInt(k);
			if(!IsNull(tid))
			bts_lock = true;
			threadlist <<= tid;
			SetThread();
			bts_lock = false;
		}
	};
}

#endif
