#include "BFApp.h"
#include "BFWindow.h"
#include "gfx/RenderDevice.h"
#include "FileStream.h"
#include "util/BSpline.h"
#include "util/PerfTimer.h"
#include "sound/WwiseSound.h"

#include "util/AllocDebug.h"

#pragma warning(disable:4996)

USING_NS_BF;

BFApp* Beefy::gBFApp = NULL;

BFApp::BFApp()
{
	mTitle = "Beefy Application";
	mRefreshRate = 60;	
	mLastProcessTick = BFTickCount();
	mPhysFrameTimeAcc = 0;
	mDrawEnabled = true;
	
	mUpdateFunc = NULL;
	mUpdateFFunc = NULL;
	mDrawFunc = NULL;
	
	gBFApp = this;
	mSysDialogCnt = 0;
	mCursor = CURSOR_POINTER;
	mInProcess = false;
	mUpdateCnt = 0;
	mNumPhysUpdates = 0;
    mVSynched = true;
	mMaxUpdatesPerDraw = 60; // 8?
    
    mUpdateSampleCount = 0;
    mUpdateSampleTimes = 0;

    if (gPerfManager == NULL)
        gPerfManager = new PerfManager();

	mRunning = false;
	mRenderDevice = NULL;
	mVSynched = false;
	mVSyncActive = false;
	mForceNextDraw = false;

	mUpdateCnt = 0;
	mUpdateCntF = 0;
	mClientUpdateCntF = 0;
}

BFApp::~BFApp()
{
	gBFApp = NULL;	
	delete gPerfManager;
	for (auto window : mPendingWindowDeleteList)
		delete window;	
}

void BFApp::Init()
{
}

void BFApp::Run()
{
}

void BFApp::Shutdown()
{
	mRunning = false;
}

void BFApp::SetCursor(int cursor)
{
	mCursor = cursor;
	PhysSetCursor();
}

void BFApp::Update(bool batchStart)
{
    //Beefy::DebugTimeGuard suspendTimeGuard(30, "BFApp::Update");
#ifdef BF_WWISE_ENABLED
	WWiseUpdate();
#endif
	
	mUpdateCnt++;
	gPerfManager->NextFrame();
	gPerfManager->ZoneStart("BFApp::Update");
	mUpdateFunc(batchStart);
	gPerfManager->ZoneEnd();

	for (auto window : mPendingWindowDeleteList)
		delete window;
	mPendingWindowDeleteList.clear();
}

void BFApp::UpdateF(float updatePct)
{
	mUpdateFFunc(updatePct);
}

void BFApp::Draw()
{    
	gPerfManager->ZoneStart("BFApp::Draw");
	mDrawFunc(mForceNextDraw);	
	mForceNextDraw = false;
	gPerfManager->ZoneEnd();
}

//#define PERIODIC_PERF_TIMING

void BFApp::Process()
{
    //Beefy::DebugTimeGuard suspendTimeGuard(30, "BFApp::Process");
    
	RenderWindow* headRenderWindow = NULL;

 	float physRefreshRate = 0;
	if ((mRenderDevice != NULL) && (!mRenderDevice->mRenderWindowList.IsEmpty()))
	{
		headRenderWindow = mRenderDevice->mRenderWindowList[0];
		physRefreshRate = headRenderWindow->GetRefreshRate();
	}

	if (physRefreshRate <= 0)
		physRefreshRate = 60.0f;

	float ticksPerFrame = 1;
	float physTicksPerFrame = 1000.0f / physRefreshRate;

	if (mInProcess)
		return; // No reentry
	mInProcess = true;
	
	uint32 tickNow = BFTickCount();
	const int vSyncTestingPeriod = 250;
		
	bool didVBlankWait = false;
		
	if (mVSyncActive)
	{		
		// Have a time limit in the cases we miss the vblank
		if (mVSyncEvent.WaitFor((int)(physTicksPerFrame + 1)))
			didVBlankWait = true;
	}

	if (mRefreshRate > 0)
		ticksPerFrame = 1000.0f / mRefreshRate;
	int ticksSinceLastProcess = tickNow - mLastProcessTick;

    mUpdateSampleCount++;
    mUpdateSampleTimes += ticksSinceLastProcess;
    //TODO: Turn off mVSynched based on error calculations - (?)

	// Two VSync failures in a row means we set mVSyncFailed and permanently disable it
	if (mUpdateSampleTimes >= vSyncTestingPeriod)
	{
		int expectedFrames = (int)(mUpdateSampleTimes / ticksPerFrame);
		if (mUpdateSampleCount > expectedFrames * 1.5)			
		{
			if (!mVSynched)
				mVSyncFailed = true;				
			mVSynched = false;
		}
		else
		{
			if (!mVSyncFailed)
				mVSynched = true;
		}
			
		mUpdateSampleCount = 0;
		mUpdateSampleTimes = 0;
	}
        		
	mPhysFrameTimeAcc += tickNow - mLastProcessTick;
        	
	if (didVBlankWait)
	{
		// Try to keep time synced with vblank
		if (mPhysFrameTimeAcc < physTicksPerFrame * 2)
		{
			float timeAdjust = physTicksPerFrame - mPhysFrameTimeAcc + 0.001f;
			mPhysFrameTimeAcc += timeAdjust;				
		}
	}

    /*if (updates > 2)
        OutputDebugStrF("Updates: %d  TickDelta: %d\n", updates, tickNow - mLastProcessTick);*/	
    
	// Compensate for "slow start" by limiting the number of catchup-updates we can do when starting the app
	int maxUpdates = BF_MIN(mNumPhysUpdates + 1, mMaxUpdatesPerDraw);

	while (mPhysFrameTimeAcc >= physTicksPerFrame)
	{
		mPhysFrameTimeAcc -= physTicksPerFrame;
		mUpdateCntF += physTicksPerFrame / ticksPerFrame;
	}
    	
	static uint32 lastUpdate = BFTickCount();	
		
#ifdef PERIODIC_PERF_TIMING
	bool perfTime = (tickNow - lastUpdate >= 5000) && (updates > 0);	
	if (perfTime)
	{
		updates = 1;		
		lastUpdate = tickNow;
				
		if (perfTime)
			gPerfManager->StartRecording();	
	}
#endif

		
	int didUpdateCnt = 0;
	
	if (mUpdateCntF - mClientUpdateCntF > physRefreshRate / 2)
	{
		// Too large of a difference, just sync
		mClientUpdateCntF = mUpdateCntF - 1;
	}

	while ((mRunning) && ((int)mClientUpdateCntF < (int)mUpdateCntF))
	{
		Update(didUpdateCnt == 0);
		didUpdateCnt++;		
		mClientUpdateCntF = (int)mClientUpdateCntF + 1.000001;
		if (didUpdateCnt >= maxUpdates)
			break;
	}

	// Only attempt UpdateF updates if our rates aren't nearly the same
	if ((mRunning) && (mRefreshRate != 0) && (fabs(physRefreshRate - mRefreshRate) / (float)mRefreshRate > 0.1f))
	{
		float updateFAmt = (float)(mUpdateCntF - mClientUpdateCntF);
		if ((updateFAmt > 0.05f) && (updateFAmt < 1.0f) && (didUpdateCnt < maxUpdates))
		{
			UpdateF(updateFAmt);
			didUpdateCnt++;
			mClientUpdateCntF = mUpdateCntF;
		}
	}

	if (didUpdateCnt > 0)
		mNumPhysUpdates++;

	if ((mRunning) && (didUpdateCnt == 0))
	{
		BfpThread_Sleep(1);				
	}
	
	if ((mRunning) && 
		((didUpdateCnt != 0) || (mForceNextDraw)))
		Draw();

#ifdef PERIODIC_PERF_TIMING
	if (perfTime)
	{
		gPerfManager->StopRecording();	
		gPerfManager->DbgPrint();
	}
#endif

	mLastProcessTick = tickNow;
	mInProcess = false;
}

void BFApp::RemoveWindow(BFWindow* window)
{
	AutoCrit autoCrit(mCritSect);

	auto itr = std::find(mWindowList.begin(), mWindowList.end(), window);
	if (itr == mWindowList.end()) // Allow benign failure (double removal)
		return; 
	mWindowList.erase(itr);

	while (window->mChildren.size() > 0)
		RemoveWindow(window->mChildren.front());

	if (window->mParent != NULL)
	{		
		window->mParent->mChildren.erase(std::find(window->mParent->mChildren.begin(), window->mParent->mChildren.end(), window));

		if (window->mFlags & BFWINDOW_MODAL)
		{	
			bool hasModal = false;

			for (auto childWindow : window->mParent->mChildren)			
			{
				if (childWindow->mFlags & BFWINDOW_MODAL)
					hasModal = true;				
			}

			if (!hasModal)
				window->mParent->ModalsRemoved();
		}	
	}

	window->mClosedFunc(window);
	mRenderDevice->RemoveRenderWindow(window->mRenderWindow);	
	window->Destroy();
	mPendingWindowDeleteList.push_back(window);
}

FileStream* BFApp::OpenBinaryFile(const StringImpl& fileName)
{
	FILE* fP = fopen(fileName.c_str(), "rb");
	if (fP == NULL)
		return NULL;

	FileStream* fileStream = new FileStream();
	fileStream->mFP = fP;
	return fileStream;
}
