#include <algorithm>
#include <assert.h>
#include <Log.h>
#include <Config.h>
#include <Graphics/Parameters.h>
#include "opengl_ContextImpl.h"
#include "opengl_BufferedDrawer.h"
#include "opengl_UnbufferedDrawer.h"
#include "opengl_ColorBufferReaderWithPixelBuffer.h"
#include "opengl_ColorBufferReaderWithBufferStorage.h"
#ifdef OS_ANDROID
#include "opengl_ColorBufferReaderWithEGLImage.h"
#endif
#include "opengl_ColorBufferReaderWithReadPixels.h"
#include "opengl_Utils.h"
#include "GLSL/glsl_CombinerProgramBuilderAccurate.h"
#include "GLSL/glsl_CombinerProgramBuilderFast.h"
#include "GLSL/glsl_SpecialShadersFactory.h"
#include "GLSL/glsl_ShaderStorage.h"

#ifndef GL_EXT_texture_filter_anisotropic
#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
#endif

#ifdef OS_ANDROID
#include <Graphics/OpenGLContext/GraphicBuffer/GraphicBufferWrapper.h>
#endif

using namespace opengl;

ContextImpl::ContextImpl()
	: m_clampMode(graphics::ClampMode::ClippingEnabled)
{

}

ContextImpl::~ContextImpl()
{
}

void ContextImpl::init()
{
	m_clampMode = graphics::ClampMode::ClippingEnabled;
	m_glInfo.init();

	if (m_glInfo.isGLES2) {
		// Correct buffer target parameters, since GLES2 knows only GL_FRAMEBUFFER
		graphics::bufferTarget::DRAW_FRAMEBUFFER = graphics::bufferTarget::FRAMEBUFFER;
		graphics::bufferTarget::READ_FRAMEBUFFER = graphics::bufferTarget::FRAMEBUFFER;
	}

	if (!m_cachedFunctions)
		m_cachedFunctions.reset(new CachedFunctions(m_glInfo));

	{
		TextureManipulationObjectFactory textureObjectsFactory(m_glInfo, *m_cachedFunctions.get());
		m_createTexture.reset(textureObjectsFactory.getCreate2DTexture());
		m_init2DTexture.reset(textureObjectsFactory.getInit2DTexture());
		m_update2DTexture.reset(textureObjectsFactory.getUpdate2DTexture());
		m_set2DTextureParameters.reset(textureObjectsFactory.getSet2DTextureParameters());
	}

	{
		BufferManipulationObjectFactory bufferObjectFactory(m_glInfo, *m_cachedFunctions.get());
		m_fbTexFormats.reset(bufferObjectFactory.getFramebufferTextureFormats());
		m_createFramebuffer.reset(bufferObjectFactory.getCreateFramebufferObject());
		m_createRenderbuffer.reset(bufferObjectFactory.getCreateRenderbuffer());
		m_initRenderbuffer.reset(bufferObjectFactory.getInitRenderbuffer());
		m_addFramebufferRenderTarget.reset(bufferObjectFactory.getAddFramebufferRenderTarget());
		m_createPixelReadBuffer.reset(bufferObjectFactory.createPixelReadBuffer());
		m_blitFramebuffers.reset(bufferObjectFactory.getBlitFramebuffers());
	}

	{
#ifndef FORCE_UNBUFFERED_DRAWER
		if (!m_glInfo.isGLESX || (m_glInfo.bufferStorage && m_glInfo.drawElementsBaseVertex))
			m_graphicsDrawer.reset(new BufferedDrawer(m_glInfo, m_cachedFunctions->getCachedVertexAttribArray(), m_cachedFunctions->getCachedBindBuffer()));
		else
#endif
			m_graphicsDrawer.reset(new UnbufferedDrawer(m_glInfo, m_cachedFunctions->getCachedVertexAttribArray()));
	}

	resetCombinerProgramBuilder();
}

void ContextImpl::destroy()
{
	m_createTexture.reset();
	m_init2DTexture.reset();
	m_set2DTextureParameters.reset();

	m_createFramebuffer.reset();
	m_createRenderbuffer.reset();
	m_initRenderbuffer.reset();
	m_addFramebufferRenderTarget.reset();
	m_graphicsDrawer.reset();
	m_combinerProgramBuilder.reset();

	m_cachedFunctions.reset();
}

void ContextImpl::setClampMode(graphics::ClampMode _mode)
{
	if (!m_glInfo.isGLESX) {
		switch (_mode) {
		case graphics::ClampMode::ClippingEnabled:
			m_cachedFunctions->getCachedEnable(graphics::enable::DEPTH_CLAMP)->enable(false);
			m_cachedFunctions->getCachedEnable(graphics::enable::CLIP_DISTANCE0)->enable(false);
			break;
		case graphics::ClampMode::NoNearPlaneClipping:
			m_cachedFunctions->getCachedEnable(graphics::enable::DEPTH_CLAMP)->enable(true);
			m_cachedFunctions->getCachedEnable(graphics::enable::CLIP_DISTANCE0)->enable(true);
			break;
		case graphics::ClampMode::NoClipping:
			m_cachedFunctions->getCachedEnable(graphics::enable::DEPTH_CLAMP)->enable(true);
			m_cachedFunctions->getCachedEnable(graphics::enable::CLIP_DISTANCE0)->enable(false);
			break;
		}
	}
	m_clampMode = _mode;
}

graphics::ClampMode ContextImpl::getClampMode()
{
	return m_clampMode;
}

void ContextImpl::enable(graphics::EnableParam _parameter, bool _enable)
{
	m_cachedFunctions->getCachedEnable(_parameter)->enable(_enable);
}

u32 ContextImpl::isEnabled(graphics::EnableParam _parameter)
{
	return m_cachedFunctions->getCachedEnable(_parameter)->get();
}

void ContextImpl::cullFace(graphics::CullModeParam _mode)
{
	m_cachedFunctions->getCachedCullFace()->setCullFace(_mode);
}

void ContextImpl::enableDepthWrite(bool _enable)
{
	m_cachedFunctions->getCachedDepthMask()->setDepthMask(_enable);
}

void ContextImpl::setDepthCompare(graphics::CompareParam _mode)
{
	m_cachedFunctions->getCachedDepthCompare()->setDepthCompare(_mode);
}

void ContextImpl::setViewport(s32 _x, s32 _y, s32 _width, s32 _height)
{
	m_cachedFunctions->getCachedViewport()->setViewport(_x, _y, _width, _height);
}

void ContextImpl::setScissor(s32 _x, s32 _y, s32 _width, s32 _height)
{
	m_cachedFunctions->getCachedScissor()->setScissor(_x, _y, _width, _height);
}

void ContextImpl::setBlending(graphics::BlendParam _sfactor, graphics::BlendParam _dfactor)
{
	m_cachedFunctions->getCachedBlending()->setBlending(_sfactor, _dfactor);
	m_cachedFunctions->getCachedBlendingSeparate()->reset();
}

void ContextImpl::setBlendingSeparate(graphics::BlendParam _sfactorcolor, graphics::BlendParam _dfactorcolor, graphics::BlendParam _sfactoralpha, graphics::BlendParam _dfactoralpha)
{
	m_cachedFunctions->getCachedBlendingSeparate()->setBlendingSeparate(_sfactorcolor, _dfactorcolor, _sfactoralpha, _dfactoralpha);
	m_cachedFunctions->getCachedBlending()->reset();
}

void ContextImpl::setBlendColor(f32 _red, f32 _green, f32 _blue, f32 _alpha)
{
	m_cachedFunctions->getCachedBlendColor()->setBlendColor(_red, _green, _blue, _alpha);
}

void ContextImpl::clearColorBuffer(f32 _red, f32 _green, f32 _blue, f32 _alpha)
{
	CachedEnable * enableScissor = m_cachedFunctions->getCachedEnable(graphics::enable::SCISSOR_TEST);
	enableScissor->enable(false);

	if (m_glInfo.isGLES2) {
		m_cachedFunctions->getCachedClearColor()->setClearColor(_red, _green, _blue, _alpha);
		glClear(GL_COLOR_BUFFER_BIT);
	} else {
#if defined(OS_ANDROID)
		// Using the GLES3 paths causes some background garbage where the Overscan is
		// TODO: Investigate this
		m_cachedFunctions->getCachedClearColor()->setClearColor(_red, _green, _blue, _alpha);
		glClear(GL_COLOR_BUFFER_BIT);
#else
		GLfloat values[4] = {_red, _green, _blue, _alpha};
		glClearBufferfv(GL_COLOR, 0, values);
#endif // OS_ANDROID
	}

	enableScissor->enable(true);
}

void ContextImpl::clearDepthBuffer()
{
	CachedEnable * enableScissor = m_cachedFunctions->getCachedEnable(graphics::enable::SCISSOR_TEST);
	CachedDepthMask * depthMask = m_cachedFunctions->getCachedDepthMask();
	enableScissor->enable(false);

	depthMask->setDepthMask(true);
	glClear(GL_DEPTH_BUFFER_BIT);

	enableScissor->enable(true);
}

void ContextImpl::setPolygonOffset(f32 _factor, f32 _units)
{
	glPolygonOffset(_factor, _units);
}

/*---------------Texture-------------*/

graphics::ObjectHandle ContextImpl::createTexture(graphics::Parameter _target)
{
	return m_createTexture->createTexture(_target);
}

void ContextImpl::deleteTexture(graphics::ObjectHandle _name)
{
	u32 glName(_name);
	glDeleteTextures(1, &glName);
	m_init2DTexture->reset(_name);

	m_cachedFunctions->getTexParams()->erase(u32(_name));
}

void ContextImpl::init2DTexture(const graphics::Context::InitTextureParams & _params)
{
	m_init2DTexture->init2DTexture(_params);
}

void ContextImpl::update2DTexture(const graphics::Context::UpdateTextureDataParams & _params)
{
	m_update2DTexture->update2DTexture(_params);
}

void ContextImpl::setTextureParameters(const graphics::Context::TexParameters & _parameters)
{
	m_set2DTextureParameters->setTextureParameters(_parameters);
}

void ContextImpl::bindTexture(const graphics::Context::BindTextureParameters & _params) {
	m_cachedFunctions->getCachedBindTexture()->bind(_params.textureUnitIndex, _params.target, _params.texture);
}

void ContextImpl::setTextureUnpackAlignment(s32 _param)
{
	m_cachedFunctions->getCachedTextureUnpackAlignment()->setTextureUnpackAlignment(_param);
}

s32 ContextImpl::getTextureUnpackAlignment() const
{
	GLint unpackAlignment;
	glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpackAlignment);
	return unpackAlignment;
}

s32 ContextImpl::getMaxTextureSize() const
{
	GLint maxTextureSize;
	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
	return maxTextureSize;
}

f32 ContextImpl::getMaxAnisotropy() const
{
	GLfloat maxInisotropy = 0.0f;

	if (m_glInfo.anisotropic_filtering) {
		glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxInisotropy);
		return maxInisotropy;
	} else {
		return 0.0f;
	}
}

void ContextImpl::bindImageTexture(const graphics::Context::BindImageTextureParameters & _params)
{
	glBindImageTexture(GLuint(_params.imageUnit), GLuint(_params.texture), 0, GL_FALSE, 0, GLenum(_params.accessMode), GLenum(_params.textureFormat));
}

u32 ContextImpl::convertInternalTextureFormat(u32 _format) const
{
	if (!m_glInfo.isGLES2)
		return _format;

	switch (_format) {
	case GL_RGB8:
		return GL_RGB;
	case GL_RGBA8:
	case GL_RGBA4:
	case GL_RGB5_A1:
		return GL_RGBA;
	}

	return _format;
}

void ContextImpl::textureBarrier()
{
	if (m_glInfo.texture_barrier)
		glTextureBarrier();
	else if (m_glInfo.texture_barrierNV)
		glTextureBarrierNV();
}

/*---------------Framebuffer-------------*/

graphics::FramebufferTextureFormats * ContextImpl::getFramebufferTextureFormats()
{
	return m_fbTexFormats.release();
}

graphics::ObjectHandle ContextImpl::createFramebuffer()
{
	return m_createFramebuffer->createFramebuffer();
}

void ContextImpl::deleteFramebuffer(graphics::ObjectHandle _name)
{
	u32 fbo(_name);
	if (fbo != 0) {
		glDeleteFramebuffers(1, &fbo);
		m_cachedFunctions->getCachedBindFramebuffer()->reset();
	}
}

void ContextImpl::bindFramebuffer(graphics::BufferTargetParam _target, graphics::ObjectHandle _name)
{
	if (m_glInfo.renderer == Renderer::VideoCore) {
		CachedDepthMask * depthMask = m_cachedFunctions->getCachedDepthMask();
		depthMask->setDepthMask(true);
		glClear(GL_DEPTH_BUFFER_BIT);
	}
	m_cachedFunctions->getCachedBindFramebuffer()->bind(_target, _name);
}

graphics::ObjectHandle ContextImpl::createRenderbuffer()
{
	return m_createRenderbuffer->createRenderbuffer();
}

void ContextImpl::initRenderbuffer(const graphics::Context::InitRenderbufferParams & _params)
{
	m_initRenderbuffer->initRenderbuffer(_params);
}

void ContextImpl::addFrameBufferRenderTarget(const graphics::Context::FrameBufferRenderTarget & _params)
{
	m_addFramebufferRenderTarget->addFrameBufferRenderTarget(_params);
}

bool ContextImpl::blitFramebuffers(const graphics::Context::BlitFramebuffersParams & _params)
{
	return m_blitFramebuffers->blitFramebuffers(_params);
}

void ContextImpl::setDrawBuffers(u32 _num)
{
	GLenum targets[5] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4};
	glDrawBuffers(_num, targets);
}

graphics::PixelReadBuffer * ContextImpl::createPixelReadBuffer(size_t _sizeInBytes)
{
	if (m_createPixelReadBuffer)
		return m_createPixelReadBuffer->createPixelReadBuffer(_sizeInBytes);
	return nullptr;
}

graphics::ColorBufferReader * ContextImpl::createColorBufferReader(CachedTexture * _pTexture)
{
#if defined(EGL) && defined(OS_ANDROID)
	if (m_glInfo.eglImage)
		return new ColorBufferReaderWithEGLImage(_pTexture, m_cachedFunctions->getCachedBindTexture());
#endif

	if (m_glInfo.bufferStorage && m_glInfo.renderer != Renderer::Intel)
		return new ColorBufferReaderWithBufferStorage(_pTexture, m_cachedFunctions->getCachedBindBuffer());

	if (!m_glInfo.isGLES2)
		return new ColorBufferReaderWithPixelBuffer(_pTexture, m_cachedFunctions->getCachedBindBuffer());

	return new ColorBufferReaderWithReadPixels(_pTexture);
}

/*---------------Shaders-------------*/

bool ContextImpl::isCombinerProgramBuilderObsolete()
{
	if (!m_combinerProgramBuilder)
		return true;
	return m_combinerProgramBuilder->isObsolete();
}

void ContextImpl::resetCombinerProgramBuilder()
{
	if (!isCombinerProgramBuilderObsolete())
		return;

	if (config.generalEmulation.enableInaccurateTextureCoordinates) {
		m_combinerProgramBuilder = ::make_unique<glsl::CombinerProgramBuilderFast>(m_glInfo, m_cachedFunctions->getCachedUseProgram());
	} else {
		m_combinerProgramBuilder = ::make_unique<glsl::CombinerProgramBuilderAccurate>(m_glInfo, m_cachedFunctions->getCachedUseProgram());
	}

	m_specialShadersFactory.reset(new glsl::SpecialShadersFactory(m_glInfo,
		m_cachedFunctions->getCachedUseProgram(),
		m_combinerProgramBuilder->getVertexShaderHeader(),
		m_combinerProgramBuilder->getFragmentShaderHeader(),
		m_combinerProgramBuilder->getFragmentShaderEnd()));
}

graphics::CombinerProgram * ContextImpl::createCombinerProgram(Combiner & _color, Combiner & _alpha, const CombinerKey & _key)
{
	return m_combinerProgramBuilder->buildCombinerProgram(_color, _alpha, _key);
}

bool ContextImpl::saveShadersStorage(const graphics::Combiners & _combiners)
{
	glsl::ShaderStorage storage(m_glInfo, m_cachedFunctions->getCachedUseProgram());
	return storage.saveShadersStorage(_combiners);
}

bool ContextImpl::loadShadersStorage(graphics::Combiners & _combiners)
{
	glsl::ShaderStorage storage(m_glInfo, m_cachedFunctions->getCachedUseProgram());
	return storage.loadShadersStorage(_combiners);
}

graphics::ShaderProgram * ContextImpl::createDepthFogShader()
{
	return m_specialShadersFactory->createShadowMapShader();
}

graphics::TexrectDrawerShaderProgram * ContextImpl::createTexrectDrawerDrawShader()
{
	return m_specialShadersFactory->createTexrectDrawerDrawShader();
}

graphics::ShaderProgram * ContextImpl::createTexrectDrawerClearShader()
{
	return m_specialShadersFactory->createTexrectDrawerClearShader();
}

graphics::ShaderProgram * ContextImpl::createTexrectUpscaleCopyShader()
{
	return m_specialShadersFactory->createTexrectUpscaleCopyShader();
}

graphics::ShaderProgram * ContextImpl::createTexrectColorAndDepthUpscaleCopyShader()
{
	return m_specialShadersFactory->createTexrectColorAndDepthUpscaleCopyShader();
}

graphics::ShaderProgram * ContextImpl::createTexrectDownscaleCopyShader()
{
	return m_specialShadersFactory->createTexrectDownscaleCopyShader();
}

graphics::ShaderProgram * ContextImpl::createTexrectColorAndDepthDownscaleCopyShader()
{
	return m_specialShadersFactory->createTexrectColorAndDepthDownscaleCopyShader();
}

graphics::ShaderProgram * ContextImpl::createGammaCorrectionShader()
{
	return m_specialShadersFactory->createGammaCorrectionShader();
}

graphics::ShaderProgram * ContextImpl::createFXAAShader()
{
	return m_specialShadersFactory->createFXAAShader();
}

graphics::TextDrawerShaderProgram * ContextImpl::createTextDrawerShader()
{
	return m_specialShadersFactory->createTextDrawerShader();
}

void ContextImpl::resetShaderProgram()
{
	m_cachedFunctions->getCachedUseProgram()->useProgram(graphics::ObjectHandle::null);
}

void ContextImpl::drawTriangles(const graphics::Context::DrawTriangleParameters & _params)
{
	m_graphicsDrawer->drawTriangles(_params);
}

void ContextImpl::drawRects(const graphics::Context::DrawRectParameters & _params)
{
	m_graphicsDrawer->drawRects(_params);
}

void ContextImpl::drawLine(f32 _width, SPVertex * _vertices)
{
	m_graphicsDrawer->drawLine(_width, _vertices);
}

f32 ContextImpl::getMaxLineWidth()
{
	GLfloat lineWidthRange[2] = { 0.0f, 0.0f };
	glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, lineWidthRange);
	return lineWidthRange[1];
}

bool ContextImpl::isSupported(graphics::SpecialFeatures _feature) const
{
	switch (_feature) {
	case graphics::SpecialFeatures::BlitFramebuffer:
		return !m_glInfo.isGLES2;
	case graphics::SpecialFeatures::WeakBlitFramebuffer:
		return m_glInfo.isGLESX;
	case graphics::SpecialFeatures::Multisampling:
		return m_glInfo.msaa;
	case graphics::SpecialFeatures::ImageTextures:
		return m_glInfo.imageTextures;
	case graphics::SpecialFeatures::ShaderProgramBinary:
		return m_glInfo.shaderStorage;
	case graphics::SpecialFeatures::DepthFramebufferTextures:
		return m_glInfo.depthTexture;
	case graphics::SpecialFeatures::IntegerTextures:
		return !m_glInfo.isGLES2;
	case graphics::SpecialFeatures::N64DepthWithFbFetchDepth:
		return m_glInfo.n64DepthWithFbFetch;
	case graphics::SpecialFeatures::FramebufferFetchColor:
		return m_glInfo.ext_fetch || m_glInfo.ext_fetch_arm;
	case graphics::SpecialFeatures::TextureBarrier:
		return m_glInfo.texture_barrier || m_glInfo.texture_barrierNV;
	case graphics::SpecialFeatures::EglImage:
		return m_glInfo.eglImage;
	case graphics::SpecialFeatures::EglImageFramebuffer:
		return m_glInfo.eglImageFramebuffer;
	case graphics::SpecialFeatures::DualSourceBlending:
		return m_glInfo.dual_source_blending;
	}
	return false;
}

s32 ContextImpl::getMaxMSAALevel()
{
	GLint maxMSAALevel = 0;
	glGetIntegerv(GL_MAX_SAMPLES, &maxMSAALevel);
	// Limit maxMSAALevel by 16.
	// Graphics driver may return 32 for max samples, but pixel format with 32 samples is not supported.
	return std::min(maxMSAALevel, 16);
}

bool ContextImpl::isError() const
{
	return Utils::isGLError();
}

bool ContextImpl::isFramebufferError() const
{
	return Utils::isFramebufferError();
}
