#pragma once

#include <stack>

#include "../CommonDef.h"

#include "gc/GarbageCollector.h"
#include "vm/Exception.h"
#include "vm/StackTrace.h"

#include "../metadata/MetadataUtil.h"
#include "../RuntimeConfig.h"

#include "InterpreterDefs.h"
#include "MemoryUtil.h"
#include "MethodBridge.h"


//#if DEBUG
//#define PUSH_STACK_FRAME(method) do { \
//	Il2CppStackFrameInfo stackFrameInfo = { method, (uintptr_t)method->methodPointer }; \
//	il2cpp::vm::StackTrace::PushFrame(stackFrameInfo); \
//} while(0)
//
//#define POP_STACK_FRAME() do { il2cpp::vm::StackTrace::PopFrame(); } while(0)
//
//#else 
#define PUSH_STACK_FRAME(method)
#define POP_STACK_FRAME() 
//#endif

namespace hybridclr
{
namespace interpreter
{

	class MachineState
	{
	public:
		MachineState()
		{
			_stackSize = -1;
			_stackBase = nullptr;
			_stackTopIdx = 0;
			_localPoolBottomIdx = -1;

			_frameBase = nullptr;
			_frameCount = -1;
			_frameTopIdx = 0;

			_exceptionFlowBase = nullptr;
			_exceptionFlowCount = -1;
			_exceptionFlowTopIdx = 0;
		}

		~MachineState()
		{
			if (_stackBase)
			{
				//il2cpp::gc::GarbageCollector::FreeFixed(_stackBase);
				il2cpp::gc::GarbageCollector::UnregisterDynamicRoot(this);
				HYBRIDCLR_FREE(_stackBase);
			}
			if (_frameBase)
			{
				HYBRIDCLR_FREE(_frameBase);
			}
			if (_exceptionFlowBase)
			{
				HYBRIDCLR_FREE(_exceptionFlowBase);
			}
		}

		static std::pair<char*, size_t> GetGCRootData(void* root)
		{
			MachineState* machineState = (MachineState*)root;
			if (machineState->_stackBase && machineState->_stackTopIdx > 0)
			{
				return std::make_pair((char*)machineState->_stackBase, machineState->_stackTopIdx * sizeof(StackObject));
			}
			else
			{
				return std::make_pair(nullptr, 0);
			}
		}

		StackObject* AllocArgments(int32_t argCount)
		{
			return AllocStackSlot(argCount);
		}

		StackObject* GetStackBasePtr() const
		{
			return _stackBase;
		}

		int32_t GetStackTop() const
		{
			return _stackTopIdx;
		}

		StackObject* AllocStackSlot(int32_t slotNum)
		{
			if (_stackTopIdx + slotNum > _localPoolBottomIdx)
			{
				if (!_stackBase)
				{
					InitEvalStack();
				}
				if (_stackTopIdx + slotNum > _localPoolBottomIdx)
				{
					il2cpp::vm::Exception::Raise(il2cpp::vm::Exception::GetStackOverflowException("AllocStackSlot"));
				}
			}
			StackObject* dataPtr = _stackBase + _stackTopIdx;
			_stackTopIdx += slotNum;
#if DEBUG
			std::memset(dataPtr, 0xEA, slotNum * sizeof(StackObject));
#endif
			return dataPtr;
		}

		void* AllocLocalloc(size_t size)
		{
			IL2CPP_ASSERT(size % 8 == 0);
			int32_t slotNum = (int32_t)(size / 8);
			IL2CPP_ASSERT(slotNum > 0);
			if (_stackTopIdx + slotNum > _localPoolBottomIdx)
			{
				if (!_stackBase)
				{
					InitEvalStack();
				}
				if (_stackTopIdx + slotNum > _localPoolBottomIdx)
				{
					il2cpp::vm::Exception::Raise(il2cpp::vm::Exception::GetStackOverflowException("AllocLocalloc"));
				}
			}
			_localPoolBottomIdx -= slotNum;
			return _stackBase + _localPoolBottomIdx;
		}

		void SetStackTop(int32_t oldTop)
		{
			_stackTopIdx = oldTop;
		}

		uint32_t GetFrameTopIdx() const
		{
			return _frameTopIdx;
		}

		int32_t GetLocalPoolBottomIdx() const
		{
			return _localPoolBottomIdx;
		}

		void SetLocalPoolBottomIdx(int32_t idx)
		{
			_localPoolBottomIdx = idx;
		}

		InterpFrame* PushFrame()
		{
			if (_frameTopIdx >= _frameCount)
			{
				if (!_frameBase)
				{
					InitFrames();
				}
				else
				{
					il2cpp::vm::Exception::Raise(il2cpp::vm::Exception::GetStackOverflowException("AllocFrame"));
				}
			}
			return _frameBase + _frameTopIdx++;
		}

		void PopFrame()
		{
			IL2CPP_ASSERT(_frameTopIdx > 0);
			--_frameTopIdx;
		}

		void PopFrameN(int32_t count)
		{
			IL2CPP_ASSERT(count > 0 && _frameTopIdx >= count);
			_frameTopIdx -= count;
		}

		InterpFrame* GetTopFrame() const
		{
			if (_frameTopIdx > 0)
			{
				return _frameBase + _frameTopIdx - 1;
			}
			else
			{
				return nullptr;
			}
		}

		ExceptionFlowInfo* AllocExceptionFlow(int32_t count)
		{
			if (_exceptionFlowTopIdx + count >= _exceptionFlowCount)
			{
				if (!_exceptionFlowBase)
				{
					InitExceptionFlows();
				}
				if (_exceptionFlowTopIdx + count >= _exceptionFlowCount)
				{
					il2cpp::vm::Exception::Raise(il2cpp::vm::Exception::GetExecutionEngineException("AllocExceptionFlowZero"));
				}
			}
			ExceptionFlowInfo* efi = _exceptionFlowBase + _exceptionFlowTopIdx;
			_exceptionFlowTopIdx += count;
			return efi;
		}

		uint32_t GetExceptionFlowTopIdx() const
		{
			return _exceptionFlowTopIdx;
		}

		void SetExceptionFlowTopIdx(uint32_t exTopIdx)
		{
			_exceptionFlowTopIdx = exTopIdx;
		}

		void SetExceptionFlowTop(ExceptionFlowInfo* top)
		{
			_exceptionFlowTopIdx = (int32_t)(top - _exceptionFlowBase);
			IL2CPP_ASSERT(_exceptionFlowTopIdx >= 0 && _exceptionFlowTopIdx <= _exceptionFlowCount);
		}

		void PushExecutingImage(const Il2CppImage* image)
		{
			_executingImageStack.push(image);
		}

		void PopExecutingImage()
		{
			_executingImageStack.pop();
		}

		const Il2CppImage* GetTopExecutingImage() const
		{
			if (_executingImageStack.empty())
			{
				return nullptr;
			}
			else
			{
				return _executingImageStack.top();
			}
		}

		void CollectFrames(il2cpp::vm::StackFrames* stackFrames)
		{
			if (_frameTopIdx <= 0)
			{
				return;
			}
			stackFrames->insert(stackFrames->begin(), _frameTopIdx, Il2CppStackFrameInfo());
			for (int32_t i = 0; i < _frameTopIdx; i++)
			{
				InterpFrame* frame = _frameBase + i;
				const MethodInfo* method = frame->method->method;
				(*stackFrames)[i] = {
					method
#if HYBRIDCLR_UNITY_2020_OR_NEW
					, (uintptr_t)method->methodPointer
#endif
				};
			}
		}

	private:


		void InitEvalStack()
		{
			_stackSize = (int32_t)RuntimeConfig::GetInterpreterThreadObjectStackSize();
			_stackBase = (StackObject*)HYBRIDCLR_MALLOC_ZERO(RuntimeConfig::GetInterpreterThreadObjectStackSize() * sizeof(StackObject));
			_stackTopIdx = 0;
			_localPoolBottomIdx = _stackSize;
			il2cpp::gc::GarbageCollector::RegisterDynamicRoot(this, GetGCRootData);
		}

		void InitFrames()
		{
			_frameBase = (InterpFrame*)HYBRIDCLR_CALLOC(RuntimeConfig::GetInterpreterThreadFrameStackSize(), sizeof(InterpFrame));
			_frameCount = (int32_t)RuntimeConfig::GetInterpreterThreadFrameStackSize();
			_frameTopIdx = 0;
		}

		void InitExceptionFlows()
		{
			_exceptionFlowBase = (ExceptionFlowInfo*)HYBRIDCLR_CALLOC(RuntimeConfig::GetInterpreterThreadExceptionFlowSize(), sizeof(ExceptionFlowInfo));
			_exceptionFlowCount = (int32_t)RuntimeConfig::GetInterpreterThreadExceptionFlowSize();
			_exceptionFlowTopIdx = 0;
		}

		StackObject* _stackBase;
		int32_t _stackSize;
		int32_t _stackTopIdx;
		int32_t _localPoolBottomIdx;

		InterpFrame* _frameBase;
		int32_t _frameTopIdx;
		int32_t _frameCount;

		ExceptionFlowInfo* _exceptionFlowBase;
		int32_t _exceptionFlowTopIdx;
		int32_t _exceptionFlowCount;


		std::stack<const Il2CppImage*> _executingImageStack;
	};

	class ExecutingInterpImageScope
	{
	public:
		ExecutingInterpImageScope(MachineState& state, const Il2CppImage* image) : _state(state)
		{
			_state.PushExecutingImage(image);
		}

		~ExecutingInterpImageScope()
		{
			_state.PopExecutingImage();
		}
		
	private:
		MachineState& _state;
	};

	class InterpFrameGroup
	{
	public:
		InterpFrameGroup(MachineState& ms) : _machineState(ms), _stackBaseIdx(ms.GetStackTop()), _frameBaseIdx(ms.GetFrameTopIdx())
		{

		}

		void CleanUpFrames()
		{
			IL2CPP_ASSERT(_machineState.GetFrameTopIdx() >= _frameBaseIdx);
			uint32_t n = _machineState.GetFrameTopIdx() - _frameBaseIdx;
			if (n > 0)
			{
				for (uint32_t i = 0; i < n; i++)
				{
					LeaveFrame();
				}
			}
		}

		InterpFrame* EnterFrameFromInterpreter(const InterpMethodInfo* imi, StackObject* argBase);


		InterpFrame* EnterFrameFromNative(const InterpMethodInfo* imi, StackObject* argBase);

		InterpFrame* LeaveFrame();

		void* AllocLoc(size_t originSize, bool fillZero)
		{
			if (originSize == 0)
			{
				return nullptr;
			}
			size_t size = (originSize + 7) & ~(size_t)7;
			void* data = _machineState.AllocLocalloc(size);
			if (fillZero)
			{
				std::memset(data, 0, size);
			}
			return data;
 		}

		size_t GetFrameCount() const { return _machineState.GetFrameTopIdx() - _frameBaseIdx; }
	private:
		MachineState& _machineState;
		int32_t _stackBaseIdx;
		uint32_t _frameBaseIdx;
	};

	class StackObjectAllocScope
	{
	private:
		MachineState& _state;
		const int32_t _originStackTop;
		const int32_t _count;
		StackObject* _data;
	public:
		StackObjectAllocScope(MachineState& state, int32_t count) : _state(state), _count(count), _originStackTop(_state.GetStackTop())
		{
			_data = state.AllocStackSlot(count);
		}

		~StackObjectAllocScope()
		{
			IL2CPP_ASSERT(_state.GetStackTop() > _originStackTop);
			_state.SetStackTop(_originStackTop);
		}

		StackObject* GetData() const { return _data; }
	};
}
}