// 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.

// ResourceManager.cpp: Implements the ResourceManager class, which tracks and
// retrieves objects which may be shared by multiple Contexts.

#include "ResourceManager.h"

#include "Buffer.h"
#include "Fence.h"
#include "Program.h"
#include "Renderbuffer.h"
#include "Sampler.h"
#include "Shader.h"
#include "Texture.h"

namespace es2
{
ResourceManager::ResourceManager()
{
	mRefCount = 1;
}

ResourceManager::~ResourceManager()
{
	while(!mBufferNameSpace.empty())
	{
		deleteBuffer(mBufferNameSpace.firstName());
	}

	while(!mProgramNameSpace.empty())
	{
		deleteProgram(mProgramNameSpace.firstName());
	}

	while(!mShaderNameSpace.empty())
	{
		deleteShader(mShaderNameSpace.firstName());
	}

	while(!mRenderbufferNameSpace.empty())
	{
		deleteRenderbuffer(mRenderbufferNameSpace.firstName());
	}

	while(!mTextureNameSpace.empty())
	{
		deleteTexture(mTextureNameSpace.firstName());
	}

	while(!mSamplerNameSpace.empty())
	{
		deleteSampler(mSamplerNameSpace.firstName());
	}

	while(!mFenceSyncNameSpace.empty())
	{
		deleteFenceSync(mFenceSyncNameSpace.firstName());
	}
}

void ResourceManager::addRef()
{
	mRefCount++;
}

void ResourceManager::release()
{
	if(--mRefCount == 0)
	{
		delete this;
	}
}

// Returns an unused buffer name
GLuint ResourceManager::createBuffer()
{
	return mBufferNameSpace.allocate();
}

// Returns an unused shader name
GLuint ResourceManager::createShader(GLenum type)
{
	GLuint name = mProgramShaderNameSpace.allocate();

	if(type == GL_VERTEX_SHADER)
	{
		mShaderNameSpace.insert(name, new VertexShader(this, name));
	}
	else if(type == GL_FRAGMENT_SHADER)
	{
		mShaderNameSpace.insert(name, new FragmentShader(this, name));
	}
	else UNREACHABLE(type);

	return name;
}

// Returns an unused program name
GLuint ResourceManager::createProgram()
{
	GLuint name = mProgramShaderNameSpace.allocate();

	mProgramNameSpace.insert(name, new Program(this, name));

	return name;
}

// Returns an unused texture name
GLuint ResourceManager::createTexture()
{
	return mTextureNameSpace.allocate();
}

// Returns an unused renderbuffer name
GLuint ResourceManager::createRenderbuffer()
{
	return mRenderbufferNameSpace.allocate();
}

// Returns an unused sampler name
GLuint ResourceManager::createSampler()
{
	return mSamplerNameSpace.allocate();
}

// Returns the next unused fence name, and allocates the fence
GLuint ResourceManager::createFenceSync(GLenum condition, GLbitfield flags)
{
	GLuint name = mFenceSyncNameSpace.allocate();

	FenceSync *fenceSync = new FenceSync(name, condition, flags);
	fenceSync->addRef();

	mFenceSyncNameSpace.insert(name, fenceSync);

	return name;
}

void ResourceManager::deleteBuffer(GLuint buffer)
{
	Buffer *bufferObject = mBufferNameSpace.remove(buffer);

	if(bufferObject)
	{
		bufferObject->release();
	}
}

void ResourceManager::deleteShader(GLuint shader)
{
	Shader *shaderObject = mShaderNameSpace.find(shader);

	if(shaderObject)
	{
		if(shaderObject->getRefCount() == 0)
		{
			delete shaderObject;
			mShaderNameSpace.remove(shader);
			mProgramShaderNameSpace.remove(shader);
		}
		else
		{
			shaderObject->flagForDeletion();
		}
	}
}

void ResourceManager::deleteProgram(GLuint program)
{
	Program *programObject = mProgramNameSpace.find(program);

	if(programObject)
	{
		if(programObject->getRefCount() == 0)
		{
			delete programObject;
			mProgramNameSpace.remove(program);
			mProgramShaderNameSpace.remove(program);
		}
		else
		{
			programObject->flagForDeletion();
		}
	}
}

void ResourceManager::deleteTexture(GLuint texture)
{
	Texture *textureObject = mTextureNameSpace.remove(texture);

	if(textureObject)
	{
		textureObject->release();
	}
}

void ResourceManager::deleteRenderbuffer(GLuint renderbuffer)
{
	Renderbuffer *renderbufferObject = mRenderbufferNameSpace.remove(renderbuffer);

	if(renderbufferObject)
	{
		renderbufferObject->release();
	}
}

void ResourceManager::deleteSampler(GLuint sampler)
{
	Sampler *samplerObject = mSamplerNameSpace.remove(sampler);

	if(samplerObject)
	{
		samplerObject->release();
	}
}

void ResourceManager::deleteFenceSync(GLuint fenceSync)
{
	FenceSync *fenceObject = mFenceSyncNameSpace.remove(fenceSync);

	if(fenceObject)
	{
		fenceObject->release();
	}
}

Buffer *ResourceManager::getBuffer(unsigned int handle)
{
	return mBufferNameSpace.find(handle);
}

Shader *ResourceManager::getShader(unsigned int handle)
{
	return mShaderNameSpace.find(handle);
}

Texture *ResourceManager::getTexture(unsigned int handle)
{
	return mTextureNameSpace.find(handle);
}

Program *ResourceManager::getProgram(unsigned int handle)
{
	return mProgramNameSpace.find(handle);
}

Renderbuffer *ResourceManager::getRenderbuffer(unsigned int handle)
{
	return mRenderbufferNameSpace.find(handle);
}

Sampler *ResourceManager::getSampler(unsigned int handle)
{
	return mSamplerNameSpace.find(handle);
}

FenceSync *ResourceManager::getFenceSync(unsigned int handle)
{
	return mFenceSyncNameSpace.find(handle);
}

void ResourceManager::checkBufferAllocation(unsigned int buffer)
{
	if(buffer != 0 && !getBuffer(buffer))
	{
		Buffer *bufferObject = new Buffer(buffer);
		bufferObject->addRef();

		mBufferNameSpace.insert(buffer, bufferObject);
	}
}

void ResourceManager::checkTextureAllocation(GLuint texture, TextureType type)
{
	if(!getTexture(texture) && texture != 0)
	{
		Texture *textureObject;

		if(type == TEXTURE_2D)
		{
			textureObject = new Texture2D(texture);
		}
		else if(type == TEXTURE_CUBE)
		{
			textureObject = new TextureCubeMap(texture);
		}
		else if(type == TEXTURE_EXTERNAL)
		{
			textureObject = new TextureExternal(texture);
		}
		else if(type == TEXTURE_3D)
		{
			textureObject = new Texture3D(texture);
		}
		else if(type == TEXTURE_2D_ARRAY)
		{
			textureObject = new Texture2DArray(texture);
		}
		else
		{
			UNREACHABLE(type);
			return;
		}

		textureObject->addRef();

		mTextureNameSpace.insert(texture, textureObject);
	}
}

void ResourceManager::checkRenderbufferAllocation(GLuint handle)
{
	if(handle != 0 && !getRenderbuffer(handle))
	{
		Renderbuffer *renderbufferObject = new Renderbuffer(handle, new Colorbuffer(0, 0, GL_RGBA4_OES, 0));
		renderbufferObject->addRef();

		mRenderbufferNameSpace.insert(handle, renderbufferObject);
	}
}

void ResourceManager::checkSamplerAllocation(GLuint sampler)
{
	if(sampler != 0 && !getSampler(sampler))
	{
		Sampler *samplerObject = new Sampler(sampler);
		samplerObject->addRef();

		mSamplerNameSpace.insert(sampler, samplerObject);
	}
}

bool ResourceManager::isSampler(GLuint sampler)
{
	return mSamplerNameSpace.isReserved(sampler);
}

}
