#include <eepp/graphics/blendmode.hpp>
#include <eepp/graphics/renderer/openglext.hpp>
#include <eepp/graphics/renderer/renderer.hpp>
#include <eepp/system/log.hpp>

namespace EE { namespace Graphics {

Uint32 factorToGlConstant( BlendMode::Factor blendFactor ) {
	switch ( blendFactor ) {
		case BlendMode::Factor::Zero:
			return GL_ZERO;
		case BlendMode::Factor::One:
			return GL_ONE;
		case BlendMode::Factor::SrcColor:
			return GL_SRC_COLOR;
		case BlendMode::Factor::OneMinusSrcColor:
			return GL_ONE_MINUS_SRC_COLOR;
		case BlendMode::Factor::DstColor:
			return GL_DST_COLOR;
		case BlendMode::Factor::OneMinusDstColor:
			return GL_ONE_MINUS_DST_COLOR;
		case BlendMode::Factor::SrcAlpha:
			return GL_SRC_ALPHA;
		case BlendMode::Factor::OneMinusSrcAlpha:
			return GL_ONE_MINUS_SRC_ALPHA;
		case BlendMode::Factor::DstAlpha:
			return GL_DST_ALPHA;
		case BlendMode::Factor::OneMinusDstAlpha:
			return GL_ONE_MINUS_DST_ALPHA;
	}

	Log::warning( "Invalid value for BlendMode::Factor! Fallback to BlendMode::Zero." );

	return GL_ZERO;
}

Uint32 equationToGlConstant( BlendMode::Equation blendEquation ) {
	switch ( blendEquation ) {
		case BlendMode::Equation::Add:
			return GL_FUNC_ADD;
		case BlendMode::Equation::Subtract:
			return GL_FUNC_SUBTRACT;
		case BlendMode::Equation::ReverseSubtract:
			return GL_FUNC_REVERSE_SUBTRACT;
	}

	Log::warning( "Invalid value for BlendMode::Equation! Fallback to BlendMode::Add." );

	return GL_FUNC_ADD;
}

BlendMode BlendMode::sLastBlend = BlendMode::Add();

std::string BlendMode::equationToString( const Equation& eq ) {
	switch ( eq ) {
		case BlendMode::Equation::Add:
			return "Add";
		case BlendMode::Equation::Subtract:
			return "Subtract";
		case BlendMode::Equation::ReverseSubtract:
			return "ReverseSubtract";
	}
	return "";
}

std::string BlendMode::factorToString( const Factor& fc ) {
	switch ( fc ) {
		case BlendMode::Factor::Zero:
			return "Zero";
		case BlendMode::Factor::One:
			return "One";
		case BlendMode::Factor::SrcColor:
			return "SrcColor";
		case BlendMode::Factor::OneMinusSrcColor:
			return "OneMinusSrcColor";
		case BlendMode::Factor::DstColor:
			return "DstColor";
		case BlendMode::Factor::OneMinusDstColor:
			return "OneMinusDstColor";
		case BlendMode::Factor::SrcAlpha:
			return "SrcAlpha";
		case BlendMode::Factor::OneMinusSrcAlpha:
			return "OneMinusSrcAlpha";
		case BlendMode::Factor::DstAlpha:
			return "DstAlpha";
		case BlendMode::Factor::OneMinusDstAlpha:
			return "OneMinusDstAlpha";
	}
	return "";
}

void BlendMode::setMode( const BlendMode& mode, bool force ) {
	if ( sLastBlend != mode || force ) {
		GLi->enable( GL_BLEND );

		if ( GLi->isExtension( EEGL_EXT_blend_func_separate ) ) {
			GLi->blendFuncSeparate( factorToGlConstant( mode.colorSrcFactor ),
									factorToGlConstant( mode.colorDstFactor ),
									factorToGlConstant( mode.alphaSrcFactor ),
									factorToGlConstant( mode.alphaDstFactor ) );
		} else {
			GLi->blendFunc( factorToGlConstant( mode.colorSrcFactor ),
							factorToGlConstant( mode.colorDstFactor ) );
		}

		if ( GLi->isExtension( EEGL_EXT_blend_minmax ) &&
			 GLi->isExtension( EEGL_EXT_blend_subtract ) ) {
			GLi->blendEquationSeparate( equationToGlConstant( mode.colorEquation ),
										equationToGlConstant( mode.alphaEquation ) );
		}

		sLastBlend = mode;
	}
}

BlendMode BlendMode::getPreBlendFunc() {
	return sLastBlend;
}

std::string BlendMode::toString() const {
	return factorToString( colorSrcFactor ) + " " + factorToString( colorDstFactor ) + " " +
		   equationToString( colorEquation ) + " " + factorToString( alphaSrcFactor ) + " " +
		   factorToString( alphaDstFactor ) + " " + equationToString( alphaEquation );
}

}} // namespace EE::Graphics
