/* 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 "engines/wintermute/ad/ad_block.h"
#include "engines/wintermute/ad/ad_generic.h"
#include "engines/wintermute/ad/ad_walkplane.h"
#include "engines/wintermute/base/base_game.h"
#include "engines/wintermute/base/gfx/base_image.h"
#include "engines/wintermute/base/gfx/3dcamera.h"

#include "graphics/opengl/system_headers.h"

#include "common/config-manager.h"

#include "math/glmath.h"

#if defined(USE_OPENGL_SHADERS)

#include "engines/wintermute/base/gfx/opengl/base_render_opengl3d_shader.h"
#include "engines/wintermute/base/gfx/opengl/base_surface_opengl3d.h"
#include "engines/wintermute/base/gfx/opengl/mesh3ds_opengl_shader.h"
#include "engines/wintermute/base/gfx/opengl/meshx_opengl_shader.h"
#include "engines/wintermute/base/gfx/opengl/shadow_volume_opengl_shader.h"

namespace Wintermute {

BaseRenderer3D *makeOpenGL3DShaderRenderer(BaseGame *inGame) {
	return new BaseRenderOpenGL3DShader(inGame);

}

struct SpriteVertexShader {
	float x;
	float y;
	float u;
	float v;
	float r;
	float g;
	float b;
	float a;
};

BaseRenderOpenGL3DShader::BaseRenderOpenGL3DShader(BaseGame *inGame)
	: BaseRenderer3D(inGame), _spriteBatchMode(false), _flatShadowMaskShader(nullptr) {
	(void)_spriteBatchMode; // silence warning
}

BaseRenderOpenGL3DShader::~BaseRenderOpenGL3DShader() {
	glDeleteBuffers(1, &_spriteVBO);
	glDeleteTextures(1, &_flatShadowRenderTexture);
	glDeleteRenderbuffers(1, &_flatShadowDepthBuffer);
	glDeleteFramebuffers(1, &_flatShadowFrameBuffer);
}

void BaseRenderOpenGL3DShader::setSpriteBlendMode(Graphics::TSpriteBlendMode blendMode) {
	switch (blendMode) {
	case Graphics::BLEND_NORMAL:
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		break;

	case Graphics::BLEND_ADDITIVE:
		glBlendFunc(GL_SRC_ALPHA, GL_ONE);
		break;

	case Graphics::BLEND_SUBTRACTIVE:
		// wme3d takes the color value here
		glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
		break;

	default:
		warning("BaseRenderOpenGL3DShader::setSpriteBlendMode unsupported blend mode %i", blendMode);
	}
}

void BaseRenderOpenGL3DShader::setAmbientLight() {
	byte a = RGBCOLGetA(_ambientLightColor);
	byte r = RGBCOLGetR(_ambientLightColor);
	byte g = RGBCOLGetG(_ambientLightColor);
	byte b = RGBCOLGetB(_ambientLightColor);

	if (!_overrideAmbientLightColor) {
		uint32 color = _gameRef->getAmbientLightColor();

		a = RGBCOLGetA(color);
		r = RGBCOLGetR(color);
		g = RGBCOLGetG(color);
		b = RGBCOLGetB(color);
	}

	Math::Vector4d value;
	value.x() = r / 255.0f;
	value.y() = g / 255.0f;
	value.z() = b / 255.0f;
	value.w() = a / 255.0f;

	_xmodelShader->use();
	_xmodelShader->setUniform("ambientLight", value);
}

int BaseRenderOpenGL3DShader::maximumLightsCount() {
	return 8;
}

void BaseRenderOpenGL3DShader::enableLight(int index) {
	_xmodelShader->use();
	Common::String uniform = Common::String::format("lights[%i].enabled", index);
	_xmodelShader->setUniform1f(uniform.c_str(), 1.0f);
}

void BaseRenderOpenGL3DShader::disableLight(int index) {
	_xmodelShader->use();
	Common::String uniform = Common::String::format("lights[%i].enabled", index);
	_xmodelShader->setUniform1f(uniform.c_str(), -1.0f);
}

void BaseRenderOpenGL3DShader::setLightParameters(int index, const Math::Vector3d &position,
                                                  const Math::Vector3d &direction,
                                                  const Math::Vector4d &diffuse, bool spotlight) {
	Math::Vector4d position4d;
	position4d.x() = position.x();
	position4d.y() = position.y();
	position4d.z() = position.z();
	position4d.w() = 1.0f;

	Math::Vector4d direction4d;
	direction4d.x() = direction.x();
	direction4d.y() = direction.y();
	direction4d.z() = direction.z();
	direction4d.w() = 0.0f;

	if (spotlight) {
		direction4d.w() = -1.0f;
	}

	_xmodelShader->use();

	Common::String uniform = Common::String::format("lights[%i]._position", index);
	_xmodelShader->setUniform(uniform.c_str(), position4d);

	uniform = Common::String::format("lights[%i]._direction", index);
	_xmodelShader->setUniform(uniform.c_str(), direction4d);

	uniform = Common::String::format("lights[%i]._color", index);
	_xmodelShader->setUniform(uniform.c_str(), diffuse);
}

void BaseRenderOpenGL3DShader::enableCulling() {
	glEnable(GL_CULL_FACE);
}

void BaseRenderOpenGL3DShader::disableCulling() {
	glDisable(GL_CULL_FACE);
}

bool BaseRenderOpenGL3DShader::enableShadows() {
	if (_flatShadowMaskShader == nullptr) {
		_flatShadowColor = Math::Vector4d(0.0f, 0.0f, 0.0f, 0.5f);

		_shadowTextureWidth = 512;
		_shadowTextureHeight = 512;

		float nearPlane = 1.0f;
		float farPlane = 10000.0f;
		float fovy = static_cast<float>(M_PI / 4.0f);

		float top = nearPlane *  tanf(fovy * 0.5f);
		float bottom = -top;
		float right = top;
		float left = -right;

		float deltaX = (-0.5f * (right - left)) / _shadowTextureWidth;
		float deltaY = (0.5f * (top - bottom)) / _shadowTextureHeight;

		Math::Matrix4 lightProjection = Math::makeFrustumMatrix(left + deltaX, right + deltaX, bottom + deltaY, top + deltaY, nearPlane, farPlane);

		_flatShadowXModelShader->use();
		_flatShadowXModelShader->setUniform("projMatrix", lightProjection);

		glGenTextures(1, &_flatShadowRenderTexture);
		glBindTexture(GL_TEXTURE_2D, _flatShadowRenderTexture);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _shadowTextureWidth, _shadowTextureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

		glGenRenderbuffers(1, &_flatShadowDepthBuffer);
		glBindRenderbuffer(GL_RENDERBUFFER, _flatShadowDepthBuffer);
		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, _shadowTextureWidth, _shadowTextureHeight);

		glGenFramebuffers(1, &_flatShadowFrameBuffer);
		glBindFramebuffer(GL_FRAMEBUFFER, _flatShadowFrameBuffer);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _flatShadowRenderTexture, 0);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _flatShadowDepthBuffer);

		glBindFramebuffer(GL_FRAMEBUFFER, 0);

		float flatShadowMaskVertices[12];
		flatShadowMaskVertices[0] = -250.0f;
		flatShadowMaskVertices[1] = 0.0f;
		flatShadowMaskVertices[2] = -250.0f;

		flatShadowMaskVertices[3] = -250.0f;
		flatShadowMaskVertices[4] = 0.0f;
		flatShadowMaskVertices[5] = 250.0f;

		flatShadowMaskVertices[6] = 250.0f;
		flatShadowMaskVertices[7] = 0.0f;
		flatShadowMaskVertices[8] = -250.0f;

		flatShadowMaskVertices[9] = 250.0f;
		flatShadowMaskVertices[10] = 0.0f;
		flatShadowMaskVertices[11] = 250.0f;

		glGenBuffers(1, &_flatShadowMaskVBO);
		glBindBuffer(GL_ARRAY_BUFFER, _flatShadowMaskVBO);
		glBufferData(GL_ARRAY_BUFFER, 4 * 12, flatShadowMaskVertices, GL_STATIC_DRAW);

		static const char *flatShadowMaskAttributes[] = { "position", nullptr };
		_flatShadowMaskShader = OpenGL::Shader::fromFiles("wme_flat_shadow_mask", flatShadowMaskAttributes);
		_flatShadowMaskShader->enableVertexAttribute("position", _flatShadowMaskVBO, 3, GL_FLOAT, false, 12, 0);

		_flatShadowMaskShader->use();
		_flatShadowMaskShader->setUniform("lightProjMatrix", lightProjection);

		_gameRef->_supportsRealTimeShadows = true;
	}

	return true;
}

bool BaseRenderOpenGL3DShader::disableShadows() {
	warning("BaseRenderOpenGL3DShader::disableShadows not implemented yet");
	return true;
}

void BaseRenderOpenGL3DShader::displayShadow(BaseObject *object, const Math::Vector3d &lightPos, bool lightPosRelative) {
	if (_flatShadowMaskShader) {
		if (object->_shadowType <= SHADOW_SIMPLE) {
			// TODO: Display simple shadow here
			return;
		}

		Math::Vector3d position = lightPos;
		Math::Vector3d target = object->_posVector;

		if (lightPosRelative) {
			position = object->_posVector + lightPos;
		}

		Math::Matrix4 lightViewMatrix = Math::makeLookAtMatrix(position, target, Math::Vector3d(0.0f, 1.0f, 0.0f));
		Math::Matrix4 translation;
		translation.setPosition(-position);
		translation.transpose();
		lightViewMatrix = translation * lightViewMatrix;

		_flatShadowXModelShader->use();
		_flatShadowXModelShader->setUniform("viewMatrix", lightViewMatrix);

		Math::Matrix4 tmp = object->_worldMatrix;
		tmp.transpose();
		_flatShadowXModelShader->setUniform("modelMatrix", tmp);

		byte a = RGBCOLGetA(object->_shadowColor);
		byte r = RGBCOLGetR(object->_shadowColor);
		byte g = RGBCOLGetG(object->_shadowColor);
		byte b = RGBCOLGetB(object->_shadowColor);

		_flatShadowColor.x() = r / 255.0f;
		_flatShadowColor.y() = g / 255.0f;
		_flatShadowColor.z() = b / 255.0f;
		_flatShadowColor.w() = a / 255.0f;
		_flatShadowXModelShader->setUniform("shadowColor", _flatShadowColor);

		glBindFramebuffer(GL_FRAMEBUFFER, _flatShadowFrameBuffer);

		GLint currentViewport[4];
		glGetIntegerv(GL_VIEWPORT, currentViewport);
		glViewport(1, 1, _shadowTextureWidth - 2, _shadowTextureHeight - 2);

		glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		object->_xmodel->renderFlatShadowModel();

		glBindFramebuffer(GL_FRAMEBUFFER, 0);

		glViewport(currentViewport[0], currentViewport[1], currentViewport[2], currentViewport[3]);

		glDisable(GL_DEPTH_WRITEMASK);

		Math::Matrix4 shadowPosition;
		shadowPosition.setToIdentity();
		shadowPosition.setPosition(object->_posVector);
		shadowPosition.transpose();

		_flatShadowMaskShader->use();
		_flatShadowMaskShader->setUniform("lightViewMatrix", lightViewMatrix);
		_flatShadowMaskShader->setUniform("worldMatrix", shadowPosition);
		_flatShadowMaskShader->setUniform("viewMatrix", _lastViewMatrix);
		_flatShadowMaskShader->setUniform("projMatrix", _projectionMatrix3d);
		_flatShadowMaskShader->setUniform("shadowColor", _flatShadowColor);

		glBindBuffer(GL_ARRAY_BUFFER, _flatShadowMaskVBO);
		glBindTexture(GL_TEXTURE_2D, _flatShadowRenderTexture);

		glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

		glBindTexture(GL_TEXTURE_2D, 0);

		glEnable(GL_DEPTH_WRITEMASK);
	}
}

bool BaseRenderOpenGL3DShader::stencilSupported() {
	// assume that we have a stencil buffer
	return true;
}

BaseImage *BaseRenderOpenGL3DShader::takeScreenshot() {
	BaseImage *screenshot = new BaseImage();
	Graphics::Surface *surface = new Graphics::Surface();
#ifdef SCUMM_BIG_ENDIAN
	Graphics::PixelFormat format(4, 8, 8, 8, 8, 24, 16, 8, 0);
#else
	Graphics::PixelFormat format(4, 8, 8, 8, 8, 0, 8, 16, 24);
#endif
	surface->create(_viewportRect.width(), _viewportRect.height(), format);

	glReadPixels(_viewportRect.left, g_system->getHeight() - _viewportRect.bottom, _viewportRect.width(), _viewportRect.height(),
	             GL_RGBA, GL_UNSIGNED_BYTE, surface->getPixels());
	flipVertical(surface);
	Graphics::Surface *converted = surface->convertTo(getPixelFormat());
	screenshot->copyFrom(converted);
	delete surface;
	delete converted;
	return screenshot;
}

void BaseRenderOpenGL3DShader::setWindowed(bool windowed) {
	ConfMan.setBool("fullscreen", !windowed);
	g_system->beginGFXTransaction();
	g_system->setFeatureState(OSystem::kFeatureFullscreenMode, !windowed);
	g_system->endGFXTransaction();
}

void BaseRenderOpenGL3DShader::fadeToColor(byte r, byte g, byte b, byte a) {
	setProjection2D();

	Math::Vector4d color;
	color.x() = r / 255.0f;
	color.y() = g / 255.0f;
	color.z() = b / 255.0f;
	color.w() = a / 255.0f;

	glDisable(GL_DEPTH_TEST);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glBindTexture(GL_TEXTURE_2D, 0);
	glBindBuffer(GL_ARRAY_BUFFER, _fadeVBO);

	_fadeShader->use();
	_fadeShader->setUniform("color", color);
	_fadeShader->setUniform("projMatrix", _projectionMatrix2d);

	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	setup2D(true);
}

bool BaseRenderOpenGL3DShader::fill(byte r, byte g, byte b, Common::Rect *rect) {
	glClearColor(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	return true;
}

bool BaseRenderOpenGL3DShader::setViewport(int left, int top, int right, int bottom) {
	_viewportRect.setRect(left, top, right, bottom);
	glViewport(left, _height - bottom, right - left, bottom - top);
	return true;
}

bool BaseRenderOpenGL3DShader::drawLine(int x1, int y1, int x2, int y2, uint32 color) {
	glBindBuffer(GL_ARRAY_BUFFER, _lineVBO);

	float lineCoords[4];

	lineCoords[0] = x1;
	lineCoords[1] = _height - y1;
	lineCoords[2] = x2;
	lineCoords[3] = _height - y2;

	glBufferSubData(GL_ARRAY_BUFFER, 0, 2 * 8, lineCoords);

	byte a = RGBCOLGetA(color);
	byte r = RGBCOLGetR(color);
	byte g = RGBCOLGetG(color);
	byte b = RGBCOLGetB(color);

	Math::Vector4d colorValue;
	colorValue.x() = r / 255.0f;
	colorValue.y() = g / 255.0f;
	colorValue.z() = b / 255.0f;
	colorValue.w() = a / 255.0f;

	_lineShader->use();
	_lineShader->setUniform("color", colorValue);
	_lineShader->setUniform("projMatrix", _projectionMatrix2d);

	glDrawArrays(GL_LINES, 0, 2);

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	return true;
}

bool BaseRenderOpenGL3DShader::setProjection() {
	// is the viewport already set here?
	float viewportWidth = _viewportRect.right - _viewportRect.left;
	float viewportHeight = _viewportRect.bottom - _viewportRect.top;

	float verticalViewAngle = _fov;
	float aspectRatio = viewportWidth / viewportHeight;

	float scaleMod = _height / viewportHeight;

	float top = _nearPlane * tanf(verticalViewAngle * 0.5f);

	_projectionMatrix3d = Math::makeFrustumMatrix(-top * aspectRatio, top * aspectRatio, -top, top, _nearPlane, _farPlane);

	_projectionMatrix3d(0, 0) *= scaleMod;
	_projectionMatrix3d(1, 1) *= scaleMod;
	return true;
}

bool BaseRenderOpenGL3DShader::setProjection2D() {
	float nearPlane = -1.0f;
	float farPlane = 100.0f;

	_projectionMatrix2d.setToIdentity();

	_projectionMatrix2d(0, 0) = 2.0f / _width;
	_projectionMatrix2d(1, 1) = 2.0f / _height;
	_projectionMatrix2d(2, 2) = 2.0f / (farPlane - nearPlane);

	_projectionMatrix2d(3, 0) = -1.0f;
	_projectionMatrix2d(3, 1) = -1.0f;
	_projectionMatrix2d(3, 2) = -(farPlane + nearPlane) / (farPlane - nearPlane);

	_shadowMaskShader->use();
	_shadowMaskShader->setUniform("projMatrix", _projectionMatrix2d);
	return true;
}

void BaseRenderOpenGL3DShader::setWorldTransform(const Math::Matrix4 &transform) {
	Math::Matrix4 tmp = transform;
	tmp.transpose();

	Math::Matrix4 newInvertedTranspose = tmp * _lastViewMatrix;
	newInvertedTranspose.inverse();
	newInvertedTranspose.transpose();

	_xmodelShader->use();
	_xmodelShader->setUniform("modelMatrix", tmp);
	_xmodelShader->setUniform("normalMatrix", newInvertedTranspose);

	_shadowVolumeShader->use();
	_shadowVolumeShader->setUniform("modelMatrix", tmp);
}

bool BaseRenderOpenGL3DShader::windowedBlt() {
	flip();
	return true;
}

void Wintermute::BaseRenderOpenGL3DShader::onWindowChange() {
	_windowed = !g_system->getFeatureState(OSystem::kFeatureFullscreenMode);
}

bool BaseRenderOpenGL3DShader::initRenderer(int width, int height, bool windowed) {
	glGenBuffers(1, &_spriteVBO);
	glBindBuffer(GL_ARRAY_BUFFER, _spriteVBO);
	glBufferData(GL_ARRAY_BUFFER, 4 * sizeof(SpriteVertexShader), nullptr, GL_DYNAMIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	static const char *spriteAttributes[] = {"position", "texcoord", "color", nullptr};
	_spriteShader = OpenGL::Shader::fromFiles("wme_sprite", spriteAttributes);

	_spriteShader->enableVertexAttribute("position", _spriteVBO, 2, GL_FLOAT, false, sizeof(SpriteVertexShader), 0);
	_spriteShader->enableVertexAttribute("texcoord", _spriteVBO, 2, GL_FLOAT, false, sizeof(SpriteVertexShader), 8);
	_spriteShader->enableVertexAttribute("color", _spriteVBO, 4, GL_FLOAT, false, sizeof(SpriteVertexShader), 16);

	static const char *geometryAttributes[] = { "position", nullptr };
	_geometryShader = OpenGL::Shader::fromFiles("wme_geometry", geometryAttributes);

	static const char *shadowVolumeAttributes[] = { "position", nullptr };
	_shadowVolumeShader = OpenGL::Shader::fromFiles("wme_shadow_volume", shadowVolumeAttributes);

	static const char *shadowMaskAttributes[] = { "position", nullptr };
	_shadowMaskShader = OpenGL::Shader::fromFiles("wme_shadow_mask", shadowMaskAttributes);

	_transformStack.push_back(Math::Matrix4());
	_transformStack.back().setToIdentity();

	static const char *XModelAttributes[] = {"position", "texcoord", "normal", nullptr};
	_xmodelShader = OpenGL::Shader::fromFiles("wme_modelx", XModelAttributes);

	setDefaultAmbientLightColor();

	for (int i = 0; i < maximumLightsCount(); ++i) {
		setLightParameters(i, Math::Vector3d(0, 0, 0), Math::Vector3d(0, 0, 0), Math::Vector4d(0, 0, 0, 0), false);
		disableLight(i);
	}

	_windowed = !ConfMan.getBool("fullscreen");
	_width = width;
	_height = height;

	_nearPlane = 90.0f;
	_farPlane = 10000.0f;

	setViewport(0, 0, width, height);

	float fadeVertexCoords[8];

	fadeVertexCoords[0 * 2 + 0] = _viewportRect.left;
	fadeVertexCoords[0 * 2 + 1] = _viewportRect.bottom;
	fadeVertexCoords[1 * 2 + 0] = _viewportRect.left;
	fadeVertexCoords[1 * 2 + 1] = _viewportRect.top;
	fadeVertexCoords[2 * 2 + 0] = _viewportRect.right;
	fadeVertexCoords[2 * 2 + 1] = _viewportRect.bottom;
	fadeVertexCoords[3 * 2 + 0] = _viewportRect.right;
	fadeVertexCoords[3 * 2 + 1] = _viewportRect.top;

	glGenBuffers(1, &_fadeVBO);
	glBindBuffer(GL_ARRAY_BUFFER, _fadeVBO);
	glBufferData(GL_ARRAY_BUFFER, 4 * 8, fadeVertexCoords, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	static const char *fadeAttributes[] = { "position", nullptr };
	_fadeShader = OpenGL::Shader::fromFiles("wme_fade", fadeAttributes);
	_fadeShader->enableVertexAttribute("position", _fadeVBO, 2, GL_FLOAT, false, 8, 0);

	glGenBuffers(1, &_lineVBO);
	glBindBuffer(GL_ARRAY_BUFFER, _lineVBO);
	glBufferData(GL_ARRAY_BUFFER, 2 * 8, nullptr, GL_DYNAMIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	static const char *lineAttributes[] = { "position", nullptr };
	_lineShader = OpenGL::Shader::fromFiles("wme_line", lineAttributes);
	_lineShader->enableVertexAttribute("position", _lineVBO, 2, GL_FLOAT, false, 8, 0);

	static const char *flatShadowXModelAttributes[] = { "position", nullptr };
	_flatShadowXModelShader = OpenGL::Shader::fromFiles("wme_flat_shadow_modelx", flatShadowXModelAttributes);

	_active = true;
	// setup a proper state
	setup2D(true);
	return true;
}

bool Wintermute::BaseRenderOpenGL3DShader::flip() {
	g_system->updateScreen();
	return true;
}

bool BaseRenderOpenGL3DShader::indicatorFlip() {
	flip();
	return true;
}

bool BaseRenderOpenGL3DShader::forcedFlip() {
	flip();
	return true;
}

bool BaseRenderOpenGL3DShader::setup2D(bool force) {
	if (_renderState != RSTATE_2D || force) {
		_renderState = RSTATE_2D;

		// some states are still missing here

		glDisable(GL_DEPTH_TEST);
		glDisable(GL_STENCIL_TEST);

		glEnable(GL_CULL_FACE);
		glFrontFace(GL_CCW);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		glViewport(0, 0, _width, _height);

		setProjection2D();
	}

	return true;
}

bool BaseRenderOpenGL3DShader::setup3D(Camera3D *camera, bool force) {
	if (_renderState != RSTATE_3D || force) {
		_renderState = RSTATE_3D;

		glEnable(GL_DEPTH_TEST);
		glEnable(GL_BLEND);

		setAmbientLight();

		if (camera) {
			_fov = camera->_fov;

			if (camera->_nearClipPlane >= 0.0f) {
				_nearPlane = camera->_nearClipPlane;
			}

			if (camera->_farClipPlane >= 0.0f) {
				_farPlane = camera->_farClipPlane;
			}

			Math::Matrix4 viewMatrix;
			camera->getViewMatrix(&viewMatrix);
			Math::Matrix4 cameraTranslate;
			cameraTranslate.setPosition(-camera->_position);
			cameraTranslate.transpose();
			viewMatrix = cameraTranslate * viewMatrix;
			_lastViewMatrix = viewMatrix;
		}

		bool fogEnabled;
		uint32 fogColor;
		float fogStart, fogEnd;

		_gameRef->getFogParams(&fogEnabled, &fogColor, &fogStart, &fogEnd);
		if (fogEnabled) {
			// TODO: Implement fog
			GLfloat color[4];
			color[0] = RGBCOLGetR(fogColor) / 255.0f;
			color[1] = RGBCOLGetG(fogColor) / 255.0f;
			color[2] = RGBCOLGetB(fogColor) / 255.0f;
			color[3] = RGBCOLGetA(fogColor) / 255.0f;
			debug(5, "BaseRenderOpenGL3DShader::setup3D fog not yet implemented! [%f %f %f %f]", color[0], color[1], color[2], color[3]);
		} else {
			// TODO: Disable fog in shader
		}

		glViewport(_viewportRect.left, _height - _viewportRect.bottom, _viewportRect.width(), _viewportRect.height());
		_viewport3dRect = _viewportRect;

		setProjection();
	}

	_xmodelShader->use();
	_xmodelShader->setUniform("viewMatrix", _lastViewMatrix);
	_xmodelShader->setUniform("projMatrix", _projectionMatrix3d);
	// this is 8 / 255, since 8 is the value used by wme (as a DWORD)
	_xmodelShader->setUniform1f("alphaRef", 0.031f);

	_geometryShader->use();
	_geometryShader->setUniform("viewMatrix", _lastViewMatrix);
	_geometryShader->setUniform("projMatrix", _projectionMatrix3d);

	_shadowVolumeShader->use();
	_shadowVolumeShader->setUniform("viewMatrix", _lastViewMatrix);
	_shadowVolumeShader->setUniform("projMatrix", _projectionMatrix3d);

	return true;
}

bool BaseRenderOpenGL3DShader::setupLines() {
	if (_renderState != RSTATE_LINES) {
		_renderState = RSTATE_LINES;

		glDisable(GL_DEPTH_TEST);
		glEnable(GL_BLEND);
		glBindTexture(GL_TEXTURE_2D, 0);
	}

	return true;
}

BaseSurface *Wintermute::BaseRenderOpenGL3DShader::createSurface() {
	return new BaseSurfaceOpenGL3D(_gameRef, this);
}

bool BaseRenderOpenGL3DShader::drawSpriteEx(BaseSurfaceOpenGL3D &tex, const Wintermute::Rect32 &rect,
	                                    const Wintermute::Vector2 &pos, const Wintermute::Vector2 &rot,
	                                    const Wintermute::Vector2 &scale, float angle, uint32 color,
	                                    bool alphaDisable, Graphics::TSpriteBlendMode blendMode,
	                                    bool mirrorX, bool mirrorY) {
	// original wme has a batch mode for sprites, we ignore this for the moment

	if (_forceAlphaColor != 0) {
		color = _forceAlphaColor;
	}

	float width = (rect.right - rect.left) * scale.x;
	float height = (rect.bottom - rect.top) * scale.y;

	glBindTexture(GL_TEXTURE_2D, tex.getTextureName());

	// for sprites we clamp to the edge, to avoid line fragments at the edges
	// this is not done by wme, though
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	int texWidth = tex.getGLTextureWidth();
	int texHeight = tex.getGLTextureHeight();

	float texLeft = (float)rect.left / (float)texWidth;
	float texTop = (float)rect.top / (float)texHeight;
	float texRight = (float)rect.right / (float)texWidth;
	float texBottom = (float)rect.bottom / (float)texHeight;

	float offset = _height / 2.0f;
	float correctedYPos = (pos.y - offset) * -1.0f + offset;

	if (mirrorX) {
		SWAP(texLeft, texRight);
	}

	if (mirrorY) {
		SWAP(texTop, texBottom);
	}

	SpriteVertexShader vertices[4] = {};

	// texture coords
	vertices[0].u = texLeft;
	vertices[0].v = texTop;

	vertices[1].u = texLeft;
	vertices[1].v = texBottom;

	vertices[2].u = texRight;
	vertices[2].v = texTop;

	vertices[3].u = texRight;
	vertices[3].v = texBottom;

	// position coords
	vertices[0].x = pos.x;
	vertices[0].y = correctedYPos;

	vertices[1].x = pos.x;
	vertices[1].y = correctedYPos - height;

	vertices[2].x = pos.x + width;
	vertices[2].y = correctedYPos;

	vertices[3].x = pos.x + width;
	vertices[3].y = correctedYPos - height;

	// not exactly sure about the color format, but this seems to work
	byte a = RGBCOLGetA(color);
	byte r = RGBCOLGetR(color);
	byte g = RGBCOLGetG(color);
	byte b = RGBCOLGetB(color);

	for (int i = 0; i < 4; ++i) {
		vertices[i].r = r / 255.0f;
		vertices[i].g = g / 255.0f;
		vertices[i].b = b / 255.0f;
		vertices[i].a = a / 255.0f;
	}

	Math::Matrix3 transform;
	transform.setToIdentity();

	if (angle != 0) {
		Vector2 correctedRot(rot.x, (rot.y - offset) * -1.0f + offset);
		transform = build2dTransformation(correctedRot, angle);
		transform.transpose();
	}

	_spriteShader->use();
	_spriteShader->setUniform("alphaTest", !alphaDisable);
	_spriteShader->setUniform("projMatrix", _projectionMatrix2d);
	_spriteShader->setUniform("transform", transform);

	glBindBuffer(GL_ARRAY_BUFFER, _spriteVBO);
	glBufferSubData(GL_ARRAY_BUFFER, 0, 4 * sizeof(SpriteVertexShader), vertices);

	setSpriteBlendMode(blendMode);

	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	return true;
}

void BaseRenderOpenGL3DShader::renderSceneGeometry(const BaseArray<AdWalkplane *> &planes, const BaseArray<AdBlock *> &blocks,
	                                           const BaseArray<AdGeneric *> &generics, const BaseArray<Light3D *> &lights, Camera3D *camera) {
	// don't render scene geometry, as OpenGL ES 2 has no wireframe rendering and we don't have a shader alternative yet
}

void BaseRenderOpenGL3DShader::renderShadowGeometry(const BaseArray<AdWalkplane *> &planes, const BaseArray<AdBlock *> &blocks,
                                                    const BaseArray<AdGeneric *> &generics, Camera3D *camera) {
	setup3D(camera, true);

	// disable color write
	glBlendFunc(GL_ZERO, GL_ONE);

	glFrontFace(GL_CCW);
	glBindTexture(GL_TEXTURE_2D, 0);

	// render walk planes
	for (uint i = 0; i < planes.size(); i++) {
		if (planes[i]->_active && planes[i]->_receiveShadows) {
			planes[i]->_mesh->render();
		}
	}

	// render blocks
	for (uint i = 0; i < blocks.size(); i++) {
		if (blocks[i]->_active && blocks[i]->_receiveShadows) {
			blocks[i]->_mesh->render();
		}
	}

	// render generic objects
	for (uint i = 0; i < generics.size(); i++) {
		if (generics[i]->_active && generics[i]->_receiveShadows) {
			generics[i]->_mesh->render();
		}
	}

	setSpriteBlendMode(Graphics::BLEND_NORMAL);
}

Mesh3DS *BaseRenderOpenGL3DShader::createMesh3DS() {
	return new Mesh3DSOpenGLShader(_geometryShader);
}

XMesh *BaseRenderOpenGL3DShader::createXMesh() {
	return new XMeshOpenGLShader(_gameRef, _xmodelShader, _flatShadowXModelShader);
}

ShadowVolume *BaseRenderOpenGL3DShader::createShadowVolume() {
	return new ShadowVolumeOpenGLShader(_gameRef, _shadowVolumeShader, _shadowMaskShader);
}

} // namespace Wintermute

#endif // defined(USE_OPENGL_SHADERS)
