#include "PapyrusDelayFunctors.h"

#include <algorithm>

#include "GameAPI.h"
#include "PapyrusVM.h"
#include "Serialization.h"

///
/// LatentSKSEDelayFunctor
///

LatentSKSEDelayFunctor::LatentSKSEDelayFunctor(UInt32 stackId) :
	stackId_( stackId )
{}

LatentSKSEDelayFunctor::LatentSKSEDelayFunctor(SerializationTag) :
	stackId_( 0 )
{}

bool LatentSKSEDelayFunctor::ShouldReschedule(SInt32& delayMSOut)
{
	return false;
}

bool LatentSKSEDelayFunctor::ShouldResumeStack(UInt32& stackIdOut)
{
	stackIdOut = stackId_;
	return true;
}

bool LatentSKSEDelayFunctor::Save(SKSESerializationInterface* intfc)
{
	using namespace Serialization;

	WriteData(intfc, &stackId_);

	//_MESSAGE("Serialized STACKID %d", stackId_);

	return true;
}

bool LatentSKSEDelayFunctor::Load(SKSESerializationInterface* intfc, UInt32 version)
{
	using namespace Serialization;

	if (! ReadData(intfc, &stackId_))
		return false;

	//_MESSAGE("De-serialized STACKID %d", stackId_);

	return true;
}

void* LatentSKSEDelayFunctor::operator new(std::size_t size)
{
	return Heap_Allocate(size);
}

void* LatentSKSEDelayFunctor::operator new(std::size_t size, const std::nothrow_t&)
{
	return Heap_Allocate(size);
}

// placement new
void* LatentSKSEDelayFunctor::operator new(std::size_t size, void* ptr)
{
	return ptr;
}

void LatentSKSEDelayFunctor::operator delete(void* ptr)
{
	Heap_Free(ptr);
}

void LatentSKSEDelayFunctor::operator delete(void* ptr, const std::nothrow_t&)
{
	Heap_Free(ptr);
}

void LatentSKSEDelayFunctor::operator delete(void*, void*)
{
	// placement delete
}

///
/// SKSEDelayFunctorQueue
///

SKSEDelayFunctorQueue::~SKSEDelayFunctorQueue()
{
	ClearAndRelease();
}

void SKSEDelayFunctorQueue::Push(ISKSEDelayFunctor* func)
{// lock_
	IScopedCriticalSection scopedLock( &lock_ );

	data_.push_back(func);
}// ~lock_

ISKSEDelayFunctor* SKSEDelayFunctorQueue::Pop()
{
	ISKSEDelayFunctor* result = NULL;

	{// lock_
		IScopedCriticalSection scopedLock( &lock_ );

		if (! data_.empty())
		{
			result = data_.front();
			data_.pop_front();
		}
	}// ~lock_			

	return result;
}

void SKSEDelayFunctorQueue::ClearAndRelease()
{
	for (DataT::iterator it = data_.begin(); it != data_.end(); ++it)
		delete *it;

	data_.clear();
}

bool SKSEDelayFunctorQueue::Save(SKSESerializationInterface* intfc)
{
	using namespace Serialization;

	// Save data
	UInt32 dataSize = data_.size();
	if (! WriteData(intfc,&dataSize))
		return false;

	for (UInt32 i=0; i<dataSize; i++)
	{
		ISKSEDelayFunctor* functor = data_[i];
	
		if (! WriteSKSEObject(intfc, functor))
			return false;
	}

	return true;
}

bool SKSEDelayFunctorQueue::Load(SKSESerializationInterface* intfc, UInt32 loadedVersion)
{
	using namespace Serialization;

	UInt32 dataSize;
	if (! ReadData(intfc,&dataSize))
		return false;

	for (UInt32 i=0; i<dataSize; i++)
	{
		ISKSEObject* obj = NULL;
		if (! ReadSKSEObject(intfc, obj))
			continue;

		// Wront type? Can't happen but who knows.
		ISKSEDelayFunctor* functor = dynamic_cast<ISKSEDelayFunctor*>(obj);
		if (functor == NULL)
		{
			// Throw the loaded object away.
			delete obj;
			continue;
		}

		data_.push_back(functor);
	}

	return true;
}

///
/// SKSEDelayFunctorWaitList
///

SKSEDelayFunctorWaitList::SKSEDelayFunctorWaitList() :
	lastTickTime_( GetPerfCounter() )
{
	LARGE_INTEGER freq;
	QueryPerformanceFrequency(&freq); 
	freq.QuadPart /= 1000;

	msToCountMult_ = freq.QuadPart;
}

SKSEDelayFunctorWaitList::~SKSEDelayFunctorWaitList()
{
	ClearAndRelease();
}

void SKSEDelayFunctorWaitList::Add(SInt32 delayMS, ISKSEDelayFunctor* func)
{// inLock_
	IScopedCriticalSection scopedLock( &inLock_ );

	WaitEntryT t( msToCountMult_ * delayMS, func );
	inData_.push_back(t);
}// ~inLock_

void SKSEDelayFunctorWaitList::Update()
{
	// Move items from thread-safe input buffer to non-thread safe main queue

	{// inLock_
		IScopedCriticalSection scopedLock( &inLock_ );

		waitData_.insert(waitData_.end(), inData_.begin(), inData_.end());
		inData_.clear();
	}// ~inLock

	SInt64 curTime = GetPerfCounter();
	SInt64 dt      = curTime - lastTickTime_;

	lastTickTime_ = curTime;

	if (dt <= 0)
		return;

	// Decrement wait times by time delta
	for (WaitDataT::iterator it = waitData_.begin(); it != waitData_.end(); ++it)
		if (it->first > 0)
			it->first -= dt;

	struct IsStillWaiting_
	{
		bool operator()(const WaitEntryT& e) { return e.first > 0; }
	};
	IsStillWaiting_ pred;

	// Swap all ready entries to end of the vector#1, add them to vector#2, and truncate #1
	WaitDataT::iterator r = std::partition(waitData_.begin(), waitData_.end(), pred);

	for (WaitDataT::iterator it = r; it != waitData_.end(); ++it)
		readyData_.push_back(it->second );

	waitData_.resize(std::distance(waitData_.begin(), r));
}

ISKSEDelayFunctor* SKSEDelayFunctorWaitList::PopReady()
{
	ISKSEDelayFunctor* result = NULL;

	if (! readyData_.empty())
	{
		result = readyData_.back();
		readyData_.pop_back();
	}			

	return result;
}

void SKSEDelayFunctorWaitList::ClearAndRelease()
{
	for (WaitDataT::iterator it = inData_.begin(); it != inData_.end(); ++it)
		delete it->second;

	inData_.clear();

	for (WaitDataT::iterator it = waitData_.begin(); it != waitData_.end(); ++it)
		delete it->second;

	waitData_.clear();

	for (ReadyDataT::iterator it = readyData_.begin(); it != readyData_.end(); ++it)
		delete *it;

	readyData_.clear();

	// Avoid interval spanning two sessions
	lastTickTime_= GetPerfCounter();
}

bool SKSEDelayFunctorWaitList::Save(SKSESerializationInterface* intfc)
{
	using namespace Serialization;

	// inData_
	UInt32 inDataSize = inData_.size();
	if (! WriteData(intfc,&inDataSize))
		return false;

	for (UInt32 i=0; i<inDataSize; i++)
	{
		SInt64				delay	= inData_[i].first;
		ISKSEDelayFunctor*	functor = inData_[i].second;
	
		if (! WriteSKSEObject(intfc, functor))
			return false;

		if (! WriteData(intfc, &delay))
			return false;
	}

	// waitData_
	UInt32 waitDataSize = waitData_.size();
	if (! WriteData(intfc,&waitDataSize))
		return false;

	for (UInt32 i=0; i<waitDataSize; i++)
	{
		SInt64				delay	= waitData_[i].first;
		ISKSEDelayFunctor*	functor = waitData_[i].second;
	
		if (! WriteSKSEObject(intfc, functor))
			return false;

		if (! WriteData(intfc, &delay))
			return false;
	}

	// readyData_
	UInt32 readyDataSize = readyData_.size();
	if (! WriteData(intfc,&readyDataSize))
		return false;

	for (UInt32 i=0; i<readyDataSize; i++)
	{
		ISKSEDelayFunctor* functor = readyData_[i];
	
		if (! WriteSKSEObject(intfc, functor))
			return false;
	}

	return true;
}

bool SKSEDelayFunctorWaitList::Load(SKSESerializationInterface* intfc, UInt32 loadedVersion)
{
	using namespace Serialization;

	// inData_
	UInt32 inDataSize;
	if (! ReadData(intfc,&inDataSize))
		return false;

	inData_.reserve(inDataSize);

	for (UInt32 i=0; i<inDataSize; i++)
	{
		ISKSEObject* obj = NULL;
		if (! ReadSKSEObject(intfc, obj))
			continue;

		// Wront type? Can't happen but who knows.
		ISKSEDelayFunctor* functor = dynamic_cast<ISKSEDelayFunctor*>(obj);
		if (functor == NULL)
		{
			// Throw the loaded object away.
			delete obj;
			continue;
		}

		SInt64 delay;
		if (! ReadData(intfc, &delay))
			return false;
		
		WaitEntryT t( delay, functor );
		inData_.push_back(t);
	}

	// waitData_
	UInt32 waitDataSize;
	if (! ReadData(intfc,&waitDataSize))
		return false;

	waitData_.reserve(waitDataSize);

	for (UInt32 i=0; i<waitDataSize; i++)
	{
		ISKSEObject* obj = NULL;
		if (! ReadSKSEObject(intfc, obj))
			continue;

		// Wront type? Can't happen but who knows.
		ISKSEDelayFunctor* functor = dynamic_cast<ISKSEDelayFunctor*>(obj);
		if (functor == NULL)
		{
			// Throw the loaded object away.
			delete obj;
			continue;
		}

		SInt64 delay;
		if (! ReadData(intfc, &delay))
			return false;
		
		WaitEntryT t( delay, functor );
		waitData_.push_back(t);
	}

	// readyData_
	UInt32 readyDataSize;
	if (! ReadData(intfc,&readyDataSize))
		return false;

	readyData_.reserve(readyDataSize);

	for (UInt32 i=0; i<readyDataSize; i++)
	{
		ISKSEObject* obj = NULL;
		if (! ReadSKSEObject(intfc, obj))
			continue;

		// Wront type? Can't happen but who knows.
		ISKSEDelayFunctor* functor = dynamic_cast<ISKSEDelayFunctor*>(obj);
		if (functor == NULL)
		{
			// Throw the loaded object away.
			delete obj;
			continue;
		}

		readyData_.push_back(functor);
	}

	return true;
}

///
/// SKSEDelayFunctorManager
///

SKSEDelayFunctorManager::SKSEDelayFunctorManager()
{
	LARGE_INTEGER t;
	QueryPerformanceFrequency(&t);
	budgetFreqScale_ = double(t.QuadPart) * 0.001;
}

void SKSEDelayFunctorManager::Enqueue(ISKSEDelayFunctor* func, SInt32 delayMS)
{
	if (delayMS < 0)
		queue_.Push(func);
	else
		waitList_.Add(delayMS, func);
}

void SKSEDelayFunctorManager::OnPreTick()
{
	waitList_.Update();

	ISKSEDelayFunctor* func;
	while ((func = waitList_.PopReady()) != NULL)
		queue_.Push(func);
}

void SKSEDelayFunctorManager::OnTick(SInt64 startTime, float budget)
{
	SInt64 budgetTime = budget * budgetFreqScale_;

	do
	{
		ISKSEDelayFunctor* functor = queue_.Pop();
		if (functor == NULL)
			break;
	
		VMValue result;
		functor->Run(result);

		SInt32 delayMS;
		if (functor->ShouldReschedule(delayMS))
		{
			if (delayMS > 0)
				waitList_.Add(delayMS, functor);
			else
				queue_.Push(functor);
			continue;
		}

		UInt32 stackId;
		if (functor->ShouldResumeStack(stackId))
		{
			(*g_skyrimVM)->GetClassRegistry()->ResumeStack(stackId, &result);
		}

		delete functor;
	}
	while (GetPerfCounter() - startTime <= budgetTime);
}

void SKSEDelayFunctorManager::OnRevert()
{
	queue_.ClearAndRelease();
	waitList_.ClearAndRelease();
}

bool SKSEDelayFunctorManager::Save(SKSESerializationInterface* intfc)
{
	using namespace Serialization;

	if (! SaveClassHelper(intfc, 'FUNQ', queue_))
		return false;

	if (! SaveClassHelper(intfc, 'WLST', waitList_))
		return false;

	intfc->OpenRecord('____', 1);

	return true;
}

bool SKSEDelayFunctorManager::Load(SKSESerializationInterface* intfc, UInt32 loadedVersion)
{
	using namespace Serialization;

	UInt32 type, length, version;

	while (intfc->GetNextRecordInfo(&type, &version, &length))
	{
		switch (type)
		{
		case 'FUNQ':
			if (! queue_.Load(intfc, version))
				return false;
			break;

		case 'WLST':
			if (! waitList_.Load(intfc, version))
				return false;
			break;

		// Done
		case '____':	
			return true;

		default:
			_MESSAGE("Error loading unexpected chunk type %08X (%.4s)", type, &type);
			break;
		}
	}

	_MESSAGE("Missing record data for SKSEDelayFunctorManager");
	return false;
}

///
/// Global instances
///

SKSEDelayFunctorManager& SKSEDelayFunctorManagerInstance()
{
	static SKSEDelayFunctorManager instance;
	return instance;
}
