// Copyright 2016 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Query.cpp: Implements the es2::Query class

#include "Query.h"

#include "main.h"
#include "Common/Thread.hpp"

namespace es2
{

Query::Query(GLuint name, GLenum type) : NamedObject(name)
{
	mQuery = nullptr;
	mStatus = GL_FALSE;
	mResult = GL_FALSE;
	mType = type;
}

Query::~Query()
{
	delete mQuery;
}

void Query::begin()
{
	if(!mQuery)
	{
		sw::Query::Type type;
		switch(mType)
		{
		case GL_ANY_SAMPLES_PASSED_EXT:
		case GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT:
			type = sw::Query::FRAGMENTS_PASSED;
			break;
		case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
			type = sw::Query::TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN;
			break;
		default:
			UNREACHABLE(mType);
			return;
		}

		mQuery = new sw::Query(type);

		if(!mQuery)
		{
			return error(GL_OUT_OF_MEMORY);
		}
	}

	Device *device = getDevice();

	mQuery->begin();
	device->addQuery(mQuery);
	switch(mType)
	{
	case GL_ANY_SAMPLES_PASSED_EXT:
	case GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT:
		device->setOcclusionEnabled(true);
		break;
	case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
		device->setTransformFeedbackQueryEnabled(true);
		break;
	default:
		ASSERT(false);
	}
}

void Query::end()
{
	if(!mQuery)
	{
		return error(GL_INVALID_OPERATION);
	}

	Device *device = getDevice();

	mQuery->end();
	device->removeQuery(mQuery);
	switch(mType)
	{
	case GL_ANY_SAMPLES_PASSED_EXT:
	case GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT:
		device->setOcclusionEnabled(false);
		break;
	case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
		device->setTransformFeedbackQueryEnabled(false);
		break;
	default:
		ASSERT(false);
	}

	mStatus = GL_FALSE;
	mResult = GL_FALSE;
}

GLuint Query::getResult()
{
	if(mQuery)
	{
		while(!testQuery())
		{
			sw::Thread::yield();
		}
	}

	return (GLuint)mResult;
}

GLboolean Query::isResultAvailable()
{
	if(mQuery)
	{
		testQuery();
	}

	return mStatus;
}

GLenum Query::getType() const
{
	return mType;
}

GLboolean Query::testQuery()
{
	if(mQuery != nullptr && mStatus != GL_TRUE)
	{
		if(!mQuery->building && mQuery->reference == 0)
		{
			unsigned int resultSum = mQuery->data;
			mStatus = GL_TRUE;

			switch(mType)
			{
			case GL_ANY_SAMPLES_PASSED_EXT:
			case GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT:
				mResult = (resultSum > 0) ? GL_TRUE : GL_FALSE;
				break;
			case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
				mResult = resultSum;
				break;
			default:
				ASSERT(false);
			}
		}

		return mStatus;
	}

	return GL_TRUE;   // Prevent blocking when query is nullptr
}
}