#include "BsD3D9TimerQuery.h"
#include "BsD3D9RenderAPI.h"
#include "BsRenderStats.h"
#include "BsDebug.h"

namespace BansheeEngine
{
	D3D9TimerQuery::D3D9TimerQuery()
		:mFinalized(false), mBeginQuery(nullptr), mFreqQuery(nullptr), mQueryIssued(false),
		mEndQuery(nullptr), mDisjointQuery(nullptr), mTimeDelta(0.0f), mDevice(nullptr)
	{
		createQuery();

		BS_INC_RENDER_STAT_CAT(ResCreated, RenderStatObject_Query);
	}

	D3D9TimerQuery::~D3D9TimerQuery()
	{
		releaseQuery();

		BS_INC_RENDER_STAT_CAT(ResDestroyed, RenderStatObject_Query);
	}

	void D3D9TimerQuery::createQuery()
	{
		mDevice = D3D9RenderAPI::getActiveD3D9Device();

		HRESULT hr = mDevice->CreateQuery(D3DQUERYTYPE_TIMESTAMPDISJOINT, &mDisjointQuery);
		if (hr != S_OK)
		{
			BS_EXCEPT(RenderingAPIException, "Failed to create a timer query.");
		}

		hr = mDevice->CreateQuery(D3DQUERYTYPE_TIMESTAMPFREQ, &mFreqQuery);
		if (hr != S_OK)
		{
			BS_EXCEPT(RenderingAPIException, "Failed to create a timer query.");
		}

		hr = mDevice->CreateQuery(D3DQUERYTYPE_TIMESTAMP, &mBeginQuery);
		if (hr != S_OK)
		{
			BS_EXCEPT(RenderingAPIException, "Failed to create a timer query.");
		}

		hr = mDevice->CreateQuery(D3DQUERYTYPE_TIMESTAMP, &mEndQuery);
		if (hr != S_OK)
		{
			BS_EXCEPT(RenderingAPIException, "Failed to create a timer query.");
		}
	}

	void D3D9TimerQuery::releaseQuery()
	{
		SAFE_RELEASE(mBeginQuery);
		SAFE_RELEASE(mEndQuery);
		SAFE_RELEASE(mDisjointQuery);
		SAFE_RELEASE(mFreqQuery);
	}

	bool D3D9TimerQuery::isQueryValid() const
	{
		return mDisjointQuery != nullptr && mBeginQuery != nullptr && 
			mEndQuery != nullptr && mFreqQuery != nullptr;
	}

	void D3D9TimerQuery::begin()
	{
		mQueryIssued = false;

		if (isQueryValid())
		{
			mDisjointQuery->Issue(D3DISSUE_BEGIN);
			mFreqQuery->Issue(D3DISSUE_END);
			mBeginQuery->Issue(D3DISSUE_END);
		}

		setActive(true);
	}

	void D3D9TimerQuery::end()
	{
		if (isQueryValid())
		{
			mEndQuery->Issue(D3DISSUE_END);
			mDisjointQuery->Issue(D3DISSUE_END);
		}

		mQueryIssued = true;
		mFinalized = false;
	}

	bool D3D9TimerQuery::isReady() const
	{
		if (!isQueryValid()) // Possibly device reset, in which case query is considered done if issued
			return mQueryIssued;

		if (!mQueryIssued)
			return false;

		BOOL queryData;
		return mDisjointQuery->GetData(&queryData, sizeof(BOOL), 0) == S_OK;
	}

	float D3D9TimerQuery::getTimeMs()
	{
		if(!mFinalized && isReady())
		{
			finalize();
		}

		return mTimeDelta;
	}

	void D3D9TimerQuery::finalize()
	{
		mFinalized = true;

		if (!isQueryValid())
		{
			mTimeDelta = 0.0f;
			return;
		}

		BOOL disjoint;
		mDisjointQuery->GetData(&disjoint, sizeof(BOOL), 0);

		if(!disjoint)
		{
			UINT64 frequency;
			mFreqQuery->GetData(&frequency, sizeof(UINT64), 0);

			UINT64 timeStart, timeEnd;
			mBeginQuery->GetData(&timeStart, sizeof(UINT64), 0);
			mEndQuery->GetData(&timeEnd, sizeof(UINT64), 0);

			UINT64 delta = timeEnd - timeStart;
			mTimeDelta = (delta/(float)frequency) * 1000.0f;
		}
		else
		{
			LOGWRN("Unrealiable GPU timer query detected.");
		}
	}

	void D3D9TimerQuery::notifyOnDeviceCreate(IDirect3DDevice9* d3d9Device)
	{
		if (d3d9Device == mDevice)
			createQuery();
	}

	void D3D9TimerQuery::notifyOnDeviceDestroy(IDirect3DDevice9* d3d9Device)
	{
		if (d3d9Device == mDevice)
			releaseQuery();
	}

	void D3D9TimerQuery::notifyOnDeviceLost(IDirect3DDevice9* d3d9Device)
	{
		if (d3d9Device == mDevice)
			releaseQuery();
	}

	void D3D9TimerQuery::notifyOnDeviceReset(IDirect3DDevice9* d3d9Device)
	{
		if (d3d9Device == mDevice)
			createQuery();
	}
}