#include <assert.h>
#include <stdio.h>

// OpenGL
#include "render/gl_shared.h"

// VERTEX AND INDEX OBJECTS
#include "render/vertexbuffer.h"
#include "render/indexbuffer.h"

#include "render/rendertarget.h"
#include "render/renderdevice.h"

RenderDevice* g_renderDevice = nullptr;

RenderDevice::RenderDevice()
{
	m_activeVB = nullptr;
	m_activeIB = nullptr;
	m_blending = false;
	m_activeReadRT = nullptr;
	m_activeWriteRT = nullptr;
	m_backfaceCull = false;
}

RenderDevice::~RenderDevice()
{
}

VertexBuffer* RenderDevice::CreateVertexBuffer(void* data, size_t size, bool isStream)
{
	return new VertexBuffer(data, size, isStream);
}

IndexBuffer* RenderDevice::CreateIndexBuffer(void* data, size_t size, bool isStream)
{
	return new IndexBuffer(data, size, isStream);
}

void RenderDevice::SetVerticesBuffer(VertexBuffer* buffer)
{
	if (buffer) {
		if (m_activeVB != buffer) {
			m_activeVB = buffer;
			m_activeVB->Bind();
		}
	} else { // unbind buffer
		glBindBuffer(GL_ARRAY_BUFFER, 0);
	}
}

void RenderDevice::SetIndicesBuffer(IndexBuffer* buffer)
{
	if (buffer) {
		if (m_activeIB != buffer) {
			m_activeIB = buffer;
			m_activeIB->Bind();
		}
	} else { // unbind buffer
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	}
}

void RenderDevice::SetDepthTest(bool enable)
{
	enable ? glEnable(GL_DEPTH_TEST) : glDisable(GL_DEPTH_TEST);
}

void RenderDevice::SetDepthWrite(bool enable)
{
	glDepthMask(enable ? GL_TRUE : GL_FALSE);
}

//void RenderDevice::setVertexFormat(VertexFormat* format)
//{
//	assert(format);
//
//	if (format->count() == 0) {
//		Core::error("RenderDevice::setVertexFormat: failed to set empty vertex format");
//	}
//
//	size_t appliedOffset = 0;
//	for (VertexAttribute* it = format->begin(); it != format->end(); ++it) {
//		
//		GLenum data_type = get_gl_vertex_attribute_type(it->m_type);
//		GLuint data_size = GLuint(get_vertex_attribute_size(it->m_type));
//
//		if (appliedOffset > 0)
//			glVertexAttribPointer(
//				GLuint(it->m_offset),
//				GLint(it->m_size),
//				data_type,
//				GL_FALSE,
//				GLsizei(format->size() * data_size),
//				(void*)(appliedOffset * sizeof(float))
//			);
//		else
//			glVertexAttribPointer(
//				GLuint(it->m_offset),
//				GLint(it->m_size),
//				data_type,
//				GL_FALSE,
//				GLsizei(format->size() * data_size),
//				(void*)0
//			);
//		
//		glEnableVertexAttribArray(GLuint(it->m_offset));
//
//		appliedOffset += it->m_size;
//	}
//}

void RenderDevice::SetBlending(bool value)
{
	if (m_blending != value) {
		m_blending = value;

		value ? glEnable(GL_BLEND) : glDisable(GL_BLEND);
	}
}

void RenderDevice::SetBlendingFunction(BlendFactor srcFactor, BlendFactor destFactor)
{
	// Switch state if one of two blending factors was changed
	if (srcFactor != m_srcBlendFactor || destFactor != m_destBlendFactor)
	{
		m_srcBlendFactor = srcFactor;
		m_destBlendFactor = destFactor;

		// push to gl state
		glBlendFunc(GetGLBlendFactor(srcFactor), GetGLBlendFactor(destFactor));
	}
}

void RenderDevice::SetReadRenderTarget(RenderTarget* renderTarget)
{
	if (renderTarget) {
		if (m_activeReadRT != renderTarget) {
			m_activeReadRT = renderTarget;
			glBindFramebuffer(GL_READ_FRAMEBUFFER, renderTarget->m_framebuffer);
		}
	}
	else { // set default rt
		glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
	}
}

void RenderDevice::SetWriteRenderTarget(RenderTarget* renderTarget)
{
	if (renderTarget) {
		if (m_activeWriteRT != renderTarget) {
			m_activeWriteRT = renderTarget;
			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, renderTarget->m_framebuffer);
		}
	}
	else { // set default rt
		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
	}
}

void RenderDevice::SetBackfaceCull(bool value)
{
	if (m_backfaceCull != value) {
		m_backfaceCull = value;

		value ? glEnable(GL_CULL_FACE) : glDisable(GL_CULL_FACE);
	}
}

void RenderDevice::SetViewport(int x, int y, int w, int h)
{
	glViewport(x, y, w, h);
}

void RenderDevice::BlitRenderTarget(int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight, TextureSurfaceType surfaceType)
{
	GLbitfield mask = 0;
	if (surfaceType & TST_COLOR)
		mask |= GL_COLOR_BUFFER_BIT;
	if (surfaceType & TST_DEPTH)
		mask |= GL_DEPTH_BUFFER_BIT;
	if (surfaceType & TST_STENCIL)
		mask |= GL_STENCIL_BUFFER_BIT;

	glBlitFramebuffer(srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight, mask, GL_LINEAR);
}

static GLenum g_glPrimitiveMode[PT_TRIANGLES + 1] = {
	GL_POINTS,
	GL_LINES,
	GL_TRIANGLES
};

void RenderDevice::DrawArrays(PrimitiveType primType, uint32_t startOf, size_t verticesCount)
{
	glDrawArrays(g_glPrimitiveMode[primType], startOf, GLsizei(verticesCount));
}

void RenderDevice::DrawElements(PrimitiveType primType, size_t elementsCount, bool is16bitIndices)
{
	glDrawElements(g_glPrimitiveMode[primType], GLsizei(elementsCount), is16bitIndices ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, NULL);
}
