#include "HotScanner.h"
#include "WinDebugger.h"

#include "BeefySysLib/util/AllocDebug.h"

USING_NS_BF_DBG;

DbgHotScanner::DbgHotScanner(WinDebugger* debugger)
{
	mDebugger = debugger;
	mBfTypesInfoAddr = 0;
	mDbgGCData = { 0 };
}

NS_BF_DBG_BEGIN
namespace TCFake
{
	struct Span
	{
		addr_target   start;          // Starting page number
		addr_target   length;         // Number of pages in span
		addr_target   next;           // Used when in link list
		addr_target   prev;           // Used when in link list
		addr_target   objects;        // Linked list of free objects
		addr_target   freeingObjects; // BCF- waiting to be freed
		addr_target   freeingObjectsTail;  // Note: *((intptr_t*)freeingObjectsTail) is freeingCount
		unsigned int  refcount : 16;  // Number of non-free objects
		unsigned int  sizeclass : 8;  // Size-class for small objects (or 0)
		unsigned int  location : 2;   // Is the span on a freelist, and if so, which?
		unsigned int  decommitDelay : 3;
		unsigned int  sample : 1;     // Sampled object?

#undef SPAN_HISTORY
#ifdef SPAN_HISTORY
  // For debugging, we can keep a log events per span
		int nexthistory;
		char history[64];
		int value[64];
#endif

		// What freelist the span is on: IN_USE if on none, or normal or returned
		enum { IN_USE, ON_NORMAL_FREELIST, ON_RETURNED_FREELIST };
	};
}

struct Fake_BfObject_WithFlags
{
	union
	{
		addr_target mClassVData;
		struct
		{
			BfObjectFlags mObjectFlags;
			uint8 mClassVDataBytes[sizeof(addr_target) - 1];
		};
	};

	union
	{
		addr_target mAllocCheckPtr;
		addr_target mDbgAllocInfo; // Meaning depends on object flags- could be PC at allocation
	};
};

struct Fake_Type_Data
{
	int32 mSize;
	int32 mTypeId;
	int32 mBoxedType;
	int32 mTypeFlags;
};

struct Fake_Delegate_Data
{
	addr_target mFuncPtr;
	addr_target Target;
};

struct Fake_DbgRawAllocData
{
	addr_target mType;
	addr_target mMarkFunc;
	int32 mMaxStackTrace; // Only 0, 1, >1 matters
};

#define BF_OBJECTFLAG_DELETED           0x80

NS_BF_END

using namespace TCFake;

void DbgHotScanner::AddSubProgram(DbgSubprogram* subProgram, bool followInlineParent, const Beefy::StringImpl& prefix)
{
	if ((followInlineParent) && (subProgram->mInlineeInfo != NULL) && (subProgram->mInlineeInfo->mInlineParent != NULL))
		AddSubProgram(subProgram->mInlineeInfo->mInlineParent, true, prefix);

	subProgram->mCompileUnit->mDbgModule->ParseSymbolData();

	String methodName;
	addr_target offset;
	if (mDebugger->mDebugTarget->FindSymbolAt(subProgram->mBlock.mLowPC, &methodName, &offset))
	{
		if (offset == 0)
		{
			if (!prefix.IsEmpty())
				methodName.Insert(0, prefix);

			if (subProgram->mCompileUnit->mDbgModule->mHotIdx != 0)
				methodName += StrFormat("\t%d", subProgram->mCompileUnit->mDbgModule->mHotIdx);
			mDebugger->mHotResolveData->mBeefCallStackEntries.Add(methodName);
		}
	}
}

void DbgHotScanner::PopulateHotCallstacks()
{
	auto prevActiveThread = mDebugger->mActiveThread;
	for (auto threadInfo : mDebugger->mThreadList)
	{
		mDebugger->mActiveThread = threadInfo;
		mDebugger->ClearCallStack();
		mDebugger->UpdateCallStack(false);

		for (int stackFrameIdx = 0; stackFrameIdx < (int)mDebugger->mCallStack.size(); stackFrameIdx++)
			mDebugger->UpdateCallStackMethod(stackFrameIdx);

		for (int stackFrameIdx = 0; stackFrameIdx < (int)mDebugger->mCallStack.size(); stackFrameIdx++)
		{
			auto stackFrame = mDebugger->mCallStack[stackFrameIdx];
			if ((stackFrame->mSubProgram != NULL) && (stackFrame->mSubProgram->GetLanguage() == DbgLanguage_Beef))
			{
				auto subProgram = stackFrame->mSubProgram;

				if (subProgram->mHotReplaceKind == DbgSubprogram::HotReplaceKind_Replaced)
					subProgram = mDebugger->TryFollowHotJump(subProgram, stackFrame->mRegisters.GetPC());

				AddSubProgram(subProgram, false, "");
			}
		}
	}

	mDebugger->mActiveThread = prevActiveThread;
	mDebugger->ClearCallStack();
}

void DbgHotScanner::ScanSpan(TCFake::Span* span, int expectedStartPage, int memKind)
{
	if (span->location != TCFake::Span::IN_USE)
		return;

	if (span->start != expectedStartPage)
	{
		return;
	}

	intptr pageSize = (intptr)1 << kPageShift;
	int spanSize = pageSize * span->length;
	addr_target spanStart = ((addr_target)span->start << kPageShift);

	if (spanSize > mScanData.size())
	{
		mScanData.Resize(spanSize);
	}
	void* spanPtr = &mScanData[0];
	mDebugger->ReadMemory(spanStart, spanSize, spanPtr);
	void* spanEnd = (void*)((intptr)spanPtr + spanSize);

	//BF_LOGASSERT((spanStart >= TCFake::PageHeap::sAddressStart) && (spanEnd <= TCFake::PageHeap::sAddressEnd));

	int elementSize = mDbgGCData.mSizeClasses[span->sizeclass];
	if (elementSize == 0)
		elementSize = spanSize;
	//BF_LOGASSERT(elementSize >= sizeof(bf::System::Object));

	auto _MarkTypeUsed = [&](int typeId, intptr size)
	{
		if (typeId < 0)
			return;
		while (mDebugger->mHotResolveData->mTypeData.size() <= typeId)
			mDebugger->mHotResolveData->mTypeData.Add(DbgHotResolveData::TypeData());
		auto& typeData = mDebugger->mHotResolveData->mTypeData[typeId];
		typeData.mSize += size;
		typeData.mCount++;
	};

	int objectSize = ((mDbgGCData.mDbgFlags & BfRtFlags_ObjectHasDebugFlags) != 0) ? sizeof(addr_target)*2 : sizeof(addr_target);

	mDebugger->mDebugTarget->GetCompilerSettings();
	if (mDebugger->mDebugTarget->mBfObjectSize != 0)
		objectSize = mDebugger->mDebugTarget->mBfObjectSize;

	while (spanPtr <= (uint8*)spanEnd - elementSize)
	{
		addr_target classVDataAddr = 0;
		if (memKind == 0)
		{
			// Obj
			Fake_BfObject_WithFlags* obj = (Fake_BfObject_WithFlags*)spanPtr;
			if (obj->mAllocCheckPtr != 0)
			{
				int objectFlags = obj->mObjectFlags;
				if ((objectFlags & BF_OBJECTFLAG_DELETED) == 0)
				{
					classVDataAddr = obj->mClassVData & ~0xFF;
				}
			}
		}
		else
		{
			// Raw
			addr_target rawAllocDataAddr = *(addr_target*)((uint8*)spanPtr + elementSize - sizeof(addr_target));
			if (rawAllocDataAddr != 0)
			{
				if (rawAllocDataAddr == mDbgGCData.mRawObjectSentinel)
				{
					classVDataAddr = *((addr_target*)spanPtr);
					if ((mDbgGCData.mDbgFlags & BfRtFlags_ObjectHasDebugFlags) != 0)
						classVDataAddr = classVDataAddr & ~0xFF;
				}
				else
				{
					int* rawTypeIdPtr = NULL;
					if (mFoundRawAllocDataAddrs.TryAdd(rawAllocDataAddr, NULL, &rawTypeIdPtr))
					{
						*rawTypeIdPtr = -1;
						Fake_DbgRawAllocData rawAllocData = mDebugger->ReadMemory<Fake_DbgRawAllocData>(rawAllocDataAddr);
						if (rawAllocData.mType != NULL)
						{
							int* typeAddrIdPtr = NULL;
							if (mFoundTypeAddrs.TryAdd(rawAllocData.mType, NULL, &typeAddrIdPtr))
							{
								*typeAddrIdPtr = -1;
								Fake_Type_Data typeData;
								if (mDebugger->ReadMemory(rawAllocData.mType + objectSize, sizeof(typeData), &typeData))
								{
									*typeAddrIdPtr = typeData.mTypeId;
									*rawTypeIdPtr = typeData.mTypeId;
									_MarkTypeUsed(typeData.mTypeId, elementSize);
								}
							}
							else
							{
								_MarkTypeUsed(*typeAddrIdPtr, elementSize);
							}
						}
					}
					else
					{
						_MarkTypeUsed(*rawTypeIdPtr, elementSize);
					}
				}
			}
		}

		if (classVDataAddr != 0)
		{
			int* typeIdPtr = NULL;
			if (mFoundClassVDataAddrs.TryAdd(classVDataAddr, NULL, &typeIdPtr))
			{
				if (mBfTypesInfoAddr > 0)
				{
					addr_target typeId = mDebugger->ReadMemory<int32>(classVDataAddr);
					addr_target arrayAddr = mBfTypesInfoAddr + typeId * sizeof(addr_target);
					addr_target typeAddr = mDebugger->ReadMemory<addr_target>(arrayAddr);
					Fake_Type_Data typeData;
					mDebugger->ReadMemory(typeAddr + objectSize, sizeof(typeData), &typeData);

					*typeIdPtr = typeData.mTypeId;
					_MarkTypeUsed(typeData.mTypeId, elementSize);
					if ((typeData.mTypeFlags & BfTypeFlags_Delegate) != 0)
					{
						Fake_Delegate_Data* dlg = (Fake_Delegate_Data*)((uint8*)spanPtr + objectSize);
						if (mFoundFuncPtrs.Add(dlg->mFuncPtr))
						{
							auto subProgram = mDebugger->mDebugTarget->FindSubProgram(dlg->mFuncPtr, DbgOnDemandKind_None);
							if ((subProgram != NULL) && (subProgram->GetLanguage() == DbgLanguage_Beef))
								AddSubProgram(subProgram, true, "D ");
						}
					}
				}
			}
			else
			{
				_MarkTypeUsed(*typeIdPtr, elementSize);
			}
		}

		spanPtr = (void*)((intptr)spanPtr + elementSize);
	}
}

void DbgHotScanner::ScanRoot(addr_target rootPtr, int memKind)
{
	mDebugger->ReadMemory(rootPtr, sizeof(mRoot), &mRoot);

#ifdef BF_DBG_32
	for (int rootIdx = 0; rootIdx < PageHeap::PageMap::ROOT_LENGTH; rootIdx++)
	{
		addr_target nodeAddr = mRoot.ptrs[rootIdx];
		if (nodeAddr == 0)
			continue;
		mDebugger->ReadMemory(nodeAddr, sizeof(mNode), &mNode);
		for (int leafIdx = 0; leafIdx < PageHeap::PageMap::LEAF_LENGTH; leafIdx++)
		{
			addr_target spanAddr = mNode.ptrs[leafIdx];
			if (spanAddr == 0)
				continue;

			int expectedStartPage = (rootIdx * PageHeap::PageMap::LEAF_LENGTH) + leafIdx;
			TCFake::Span span;
			mDebugger->ReadMemory(spanAddr, sizeof(span), &span);
			ScanSpan(&span, expectedStartPage, memKind);
		}
	}
#else
	int bits = PageHeap::PageMap::BITS;
	int interiorLen = PageHeap::PageMap::INTERIOR_LENGTH;
	int leafLen = PageHeap::PageMap::LEAF_LENGTH;

	for (int pageIdx1 = 0; pageIdx1 < PageHeap::PageMap::INTERIOR_LENGTH; pageIdx1++)
	{
		addr_target node1Addr = mRoot.ptrs[pageIdx1];
		if (node1Addr == 0)
			continue;
		mDebugger->ReadMemory(node1Addr, sizeof(mNode1), &mNode1);
		for (int pageIdx2 = 0; pageIdx2 < PageHeap::PageMap::INTERIOR_LENGTH; pageIdx2++)
		{
			addr_target node2Addr = mNode1.ptrs[pageIdx2];
			if (node2Addr == 0)
				continue;
			mDebugger->ReadMemory(node2Addr, sizeof(mNode2), &mNode2);
			for (int pageIdx3 = 0; pageIdx3 < PageHeap::PageMap::LEAF_LENGTH; pageIdx3++)
			{
				addr_target spanAddr = mNode2.ptrs[pageIdx3];
				if (spanAddr == 0)
					continue;

				int expectedStartPage = ((pageIdx1 * PageHeap::PageMap::INTERIOR_LENGTH) + pageIdx2) * PageHeap::PageMap::LEAF_LENGTH + pageIdx3;
				TCFake::Span span;
				mDebugger->ReadMemory(spanAddr, sizeof(span), &span);
				ScanSpan(&span, expectedStartPage, memKind);
			}
		}
	}
#endif
}

void DbgHotScanner::Scan(DbgHotResolveFlags flags)
{
	auto prevRunState = mDebugger->mRunState;
	if ((mDebugger->mRunState == RunState_Running) && ((flags & DbgHotResolveFlag_KeepThreadState) == 0))
	{
		mDebugger->ThreadRestorePause(NULL, NULL);
		mDebugger->mRunState = RunState_Paused;
	}

	if ((flags & DbgHotResolveFlag_ActiveMethods) != 0)
		PopulateHotCallstacks();

	if ((flags & DbgHotResolveFlag_Allocations) != 0)
	{
		addr_target gcDbgDataAddr = 0;

		for (auto module : mDebugger->mDebugTarget->mDbgModules)
		{
			if ((module->mFilePath.Contains("Beef")) && (module->mFilePath.Contains("Dbg")))
			{
				module->ParseTypeData();
				module->ParseSymbolData();
				auto entry = module->mSymbolNameMap.Find("gGCDbgData");
				if ((entry != NULL) && (entry->mValue != NULL))
					gcDbgDataAddr = entry->mValue->mAddress;
			}
		}

		auto module = mDebugger->mDebugTarget->mTargetBinary;
		if (module->mBfTypesInfoAddr == 0)
		{
			module->mBfTypesInfoAddr = -1;
			auto typeTypeEntry = module->FindType("System.Type", DbgLanguage_Beef);
			if ((typeTypeEntry != NULL) && (typeTypeEntry->mValue != NULL))
			{
				auto typeType = typeTypeEntry->mValue;
				module->mBfTypeType = typeType;
				if (typeType->mNeedsGlobalsPopulated)
					typeType->mCompileUnit->mDbgModule->PopulateTypeGlobals(typeType);

				for (auto member : typeType->mMemberList)
				{
					if ((member->mIsStatic) && (member->mName != NULL) && (strcmp(member->mName, "sTypes") == 0) && (member->mLocationData != NULL))
					{
						DbgAddrType addrType;
						module->mBfTypesInfoAddr = member->mCompileUnit->mDbgModule->EvaluateLocation(NULL, member->mLocationData, member->mLocationLen, NULL, &addrType);
					}
				}

				if (module->mBfTypesInfoAddr <= 0)
				{
					auto entry = module->mSymbolNameMap.Find(
#ifdef BF_DBG_64
						"?sTypes@Type@System@bf@@2PEAPEAV123@A"
#else
						"?sTypes@Type@System@bf@@2PAPAV123@A"
#endif
					);

					if (entry)
						module->mBfTypesInfoAddr = entry->mValue->mAddress;
				}
			}
		}
		mBfTypesInfoAddr = module->mBfTypesInfoAddr;

		if (gcDbgDataAddr == 0)
			return;

		bool success = mDebugger->ReadMemory(gcDbgDataAddr, sizeof(DbgGCData), &mDbgGCData);
		if (!success)
		{
			BF_ASSERT("Failed to read DbgGCData");
			success = mDebugger->ReadMemory(gcDbgDataAddr, sizeof(DbgGCData), &mDbgGCData);
		}
		BF_ASSERT(mDbgGCData.mObjRootPtr != NULL);
		BF_ASSERT(mDbgGCData.mRawRootPtr != NULL);
		if (mDbgGCData.mObjRootPtr != NULL)
			ScanRoot(mDbgGCData.mObjRootPtr, 0);
		if (mDbgGCData.mRawRootPtr != NULL)
			ScanRoot(mDbgGCData.mRawRootPtr, 1);
	}

	if ((prevRunState == RunState_Running) && ((flags & DbgHotResolveFlag_KeepThreadState) == 0))
	{
		mDebugger->ThreadRestoreUnpause();
		mDebugger->mRunState = prevRunState;
	}
}