/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "backends/graphics/opengl/framebuffer.h"
#include "backends/graphics/opengl/pipelines/pipeline.h"
#include "backends/graphics/opengl/texture.h"
#include "graphics/opengl/debug.h"

namespace OpenGL {

Framebuffer::Framebuffer()
	: _viewport(), _projectionMatrix(), _pipeline(nullptr), _clearColor(),
	  _blendState(kBlendModeDisabled), _scissorTestState(false), _scissorBox() {
}

void Framebuffer::activate(Pipeline *pipeline) {
	assert(pipeline);
	_pipeline = pipeline;

	applyViewport();
	applyProjectionMatrix();
	applyClearColor();
	applyBlendState();
	applyScissorTestState();
	applyScissorBox();

	activateInternal();
}

void Framebuffer::deactivate() {
	deactivateInternal();

	_pipeline = nullptr;
}

void Framebuffer::setClearColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) {
	_clearColor[0] = r;
	_clearColor[1] = g;
	_clearColor[2] = b;
	_clearColor[3] = a;

	// Directly apply changes when we are active.
	if (isActive()) {
		applyClearColor();
	}
}

void Framebuffer::enableBlend(BlendMode mode) {
	_blendState = mode;

	// Directly apply changes when we are active.
	if (isActive()) {
		applyBlendState();
	}
}

void Framebuffer::enableScissorTest(bool enable) {
	_scissorTestState = enable;

	// Directly apply changes when we are active.
	if (isActive()) {
		applyScissorTestState();
	}
}

void Framebuffer::setScissorBox(GLint x, GLint y, GLsizei w, GLsizei h) {
	_scissorBox[0] = x;
	_scissorBox[1] = y;
	_scissorBox[2] = w;
	_scissorBox[3] = h;

	// Directly apply changes when we are active.
	if (isActive()) {
		applyScissorBox();
	}
}

void Framebuffer::applyViewport() {
	GL_CALL(glViewport(_viewport[0], _viewport[1], _viewport[2], _viewport[3]));
}

void Framebuffer::applyProjectionMatrix() {
	assert(_pipeline);
	_pipeline->setProjectionMatrix(_projectionMatrix);
}

void Framebuffer::applyClearColor() {
	GL_CALL(glClearColor(_clearColor[0], _clearColor[1], _clearColor[2], _clearColor[3]));
}

void Framebuffer::applyBlendState() {
	switch (_blendState) {
		case kBlendModeDisabled:
			GL_CALL(glDisable(GL_BLEND));
			break;
		case kBlendModeTraditionalTransparency:
			GL_CALL(glEnable(GL_BLEND));
			GL_CALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
			break;
		case kBlendModePremultipliedTransparency:
			GL_CALL(glEnable(GL_BLEND));
			GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
			break;
		default:
			break;
	}
}

void Framebuffer::applyScissorTestState() {
	if (_scissorTestState) {
		GL_CALL(glEnable(GL_SCISSOR_TEST));
	} else {
		GL_CALL(glDisable(GL_SCISSOR_TEST));
	}
}

void Framebuffer::applyScissorBox() {
	GL_CALL(glScissor(_scissorBox[0], _scissorBox[1], _scissorBox[2], _scissorBox[3]));
}

void Framebuffer::copyRenderStateFrom(const Framebuffer &other, uint copyMask) {
	if (copyMask & kCopyMaskClearColor) {
		memcpy(_clearColor, other._clearColor, sizeof(_clearColor));
	}
	if (copyMask & kCopyMaskBlendState) {
		_blendState = other._blendState;
	}
	if (copyMask & kCopyMaskScissorState) {
		_scissorTestState = other._scissorTestState;
	}
	if (copyMask & kCopyMaskScissorBox) {
		memcpy(_scissorBox, other._scissorBox, sizeof(_scissorBox));
	}

	if (isActive()) {
		applyClearColor();
		applyBlendState();
		applyScissorTestState();
		applyScissorBox();
	}
}

//
// Backbuffer implementation
//

void Backbuffer::activateInternal() {
#if !USE_FORCED_GLES
	if (OpenGLContext.framebufferObjectSupported) {
		GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
	}
#endif
}

void Backbuffer::setDimensions(uint width, uint height) {
	// Set viewport dimensions.
	_viewport[0] = 0;
	_viewport[1] = 0;
	_viewport[2] = width;
	_viewport[3] = height;

	// Setup orthogonal projection matrix.
	_projectionMatrix(0, 0) =  2.0f / width;
	_projectionMatrix(0, 1) =  0.0f;
	_projectionMatrix(0, 2) =  0.0f;
	_projectionMatrix(0, 3) =  0.0f;

	_projectionMatrix(1, 0) =  0.0f;
	_projectionMatrix(1, 1) = -2.0f / height;
	_projectionMatrix(1, 2) =  0.0f;
	_projectionMatrix(1, 3) =  0.0f;

	_projectionMatrix(2, 0) =  0.0f;
	_projectionMatrix(2, 1) =  0.0f;
	_projectionMatrix(2, 2) =  0.0f;
	_projectionMatrix(2, 3) =  0.0f;

	_projectionMatrix(3, 0) = -1.0f;
	_projectionMatrix(3, 1) =  1.0f;
	_projectionMatrix(3, 2) =  0.0f;
	_projectionMatrix(3, 3) =  1.0f;

	// Directly apply changes when we are active.
	if (isActive()) {
		applyViewport();
		applyProjectionMatrix();
	}
}

//
// Render to texture target implementation
//

#if !USE_FORCED_GLES
TextureTarget::TextureTarget()
	: _texture(new GLTexture(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE)), _glFBO(0), _needUpdate(true) {
}

TextureTarget::~TextureTarget() {
	delete _texture;
	GL_CALL_SAFE(glDeleteFramebuffers, (1, &_glFBO));
}

void TextureTarget::activateInternal() {
	// Allocate framebuffer object if necessary.
	if (!_glFBO) {
		GL_CALL(glGenFramebuffers(1, &_glFBO));
		_needUpdate = true;
	}

	// Attach destination texture to FBO.
	GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, _glFBO));

	// If required attach texture to FBO.
	if (_needUpdate) {
		GL_CALL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture->getGLTexture(), 0));
		_needUpdate = false;
	}
}

void TextureTarget::destroy() {
	GL_CALL(glDeleteFramebuffers(1, &_glFBO));
	_glFBO = 0;

	_texture->destroy();
}

void TextureTarget::create() {
	_texture->create();

	_needUpdate = true;
}

bool TextureTarget::setSize(uint width, uint height) {
	if (!_texture->setSize(width, height)) {
		return false;
	}

	const uint texWidth  = _texture->getWidth();
	const uint texHeight = _texture->getHeight();

	// Set viewport dimensions.
	_viewport[0] = 0;
	_viewport[1] = 0;
	_viewport[2] = texWidth;
	_viewport[3] = texHeight;

	// Setup orthogonal projection matrix.
	_projectionMatrix(0, 0) =  2.0f / texWidth;
	_projectionMatrix(0, 1) =  0.0f;
	_projectionMatrix(0, 2) =  0.0f;
	_projectionMatrix(0, 3) =  0.0f;

	_projectionMatrix(1, 0) =  0.0f;
	_projectionMatrix(1, 1) =  2.0f / texHeight;
	_projectionMatrix(1, 2) =  0.0f;
	_projectionMatrix(1, 3) =  0.0f;

	_projectionMatrix(2, 0) =  0.0f;
	_projectionMatrix(2, 1) =  0.0f;
	_projectionMatrix(2, 2) =  0.0f;
	_projectionMatrix(2, 3) =  0.0f;

	_projectionMatrix(3, 0) = -1.0f;
	_projectionMatrix(3, 1) = -1.0f;
	_projectionMatrix(3, 2) =  0.0f;
	_projectionMatrix(3, 3) =  1.0f;

	// Directly apply changes when we are active.
	if (isActive()) {
		applyViewport();
		applyProjectionMatrix();
	}
	return true;
}
#endif // !USE_FORCED_GLES

} // End of namespace OpenGL
