/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES Utilities
 * ------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Wrapper for GL program object.
 *//*--------------------------------------------------------------------*/

#include "gluShaderProgram.hpp"
#include "gluRenderContext.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "tcuTestLog.hpp"
#include "deClock.h"

#include <cstring>

using std::string;

namespace glu
{

// Shader

Shader::Shader (const RenderContext& renderCtx, ShaderType shaderType)
	: m_gl		(renderCtx.getFunctions())
	, m_shader	(0)
{
	m_info.type	= shaderType;
	m_shader	= m_gl.createShader(getGLShaderType(shaderType));
	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glCreateShader()");
	TCU_CHECK(m_shader);
}

Shader::Shader (const glw::Functions& gl, ShaderType shaderType)
	: m_gl		(gl)
	, m_shader	(0)
{
	m_info.type	= shaderType;
	m_shader	= m_gl.createShader(getGLShaderType(shaderType));
	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glCreateShader()");
	TCU_CHECK(m_shader);
}

Shader::~Shader (void)
{
	m_gl.deleteShader(m_shader);
}

void Shader::setSources (int numSourceStrings, const char* const* sourceStrings, const int* lengths)
{
	m_gl.shaderSource(m_shader, numSourceStrings, sourceStrings, lengths);
	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glShaderSource()");

	m_info.source.clear();
	for (int ndx = 0; ndx < numSourceStrings; ndx++)
	{
		const size_t length = lengths && lengths[ndx] >= 0 ? lengths[ndx] : strlen(sourceStrings[ndx]);
		m_info.source += std::string(sourceStrings[ndx], length);
	}
}

void Shader::compile (void)
{
	m_info.compileOk		= false;
	m_info.compileTimeUs	= 0;
	m_info.infoLog.clear();

	{
		deUint64 compileStart = deGetMicroseconds();
		m_gl.compileShader(m_shader);
		m_info.compileTimeUs = deGetMicroseconds() - compileStart;
	}

	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glCompileShader()");

	// Query status
	{
		int compileStatus = 0;

		m_gl.getShaderiv(m_shader, GL_COMPILE_STATUS, &compileStatus);
		GLU_EXPECT_NO_ERROR(m_gl.getError(), "glGetShaderiv()");

		m_info.compileOk = compileStatus != GL_FALSE;
	}

	// Query log
	{
		int infoLogLen = 0;
		int unusedLen;

		m_gl.getShaderiv(m_shader, GL_INFO_LOG_LENGTH, &infoLogLen);
		GLU_EXPECT_NO_ERROR(m_gl.getError(), "glGetShaderiv()");

		if (infoLogLen > 0)
		{
			// The INFO_LOG_LENGTH query and the buffer query implementations have
			// very commonly off-by-one errors. Try to work around these issues.

			// add tolerance for off-by-one in log length, buffer write, and for terminator
			std::vector<char> infoLog(infoLogLen + 3, '\0');

			// claim buf size is one smaller to protect from off-by-one writing over buffer bounds
			m_gl.getShaderInfoLog(m_shader, (int)infoLog.size() - 1, &unusedLen, &infoLog[0]);

			if (infoLog[(int)(infoLog.size()) - 1] != '\0')
			{
				// return whole buffer if null terminator was overwritten
				m_info.infoLog = std::string(&infoLog[0], infoLog.size());
			}
			else
			{
				// read as C string. infoLog is guaranteed to be 0-terminated
				m_info.infoLog = std::string(&infoLog[0]);
			}
		}
	}
}

// Program

static bool getProgramLinkStatus (const glw::Functions& gl, deUint32 program)
{
	int	linkStatus				= 0;

	gl.getProgramiv(program, GL_LINK_STATUS, &linkStatus);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramiv()");
	return (linkStatus != GL_FALSE);
}

static std::string getProgramInfoLog (const glw::Functions& gl, deUint32 program)
{
	int infoLogLen = 0;
	int unusedLen;

	gl.getProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLen);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramiv()");

	if (infoLogLen > 0)
	{
		// The INFO_LOG_LENGTH query and the buffer query implementations have
		// very commonly off-by-one errors. Try to work around these issues.

		// add tolerance for off-by-one in log length, buffer write, and for terminator
		std::vector<char> infoLog(infoLogLen + 3, '\0');

		// claim buf size is one smaller to protect from off-by-one writing over buffer bounds
		gl.getProgramInfoLog(program, (int)infoLog.size() - 1, &unusedLen, &infoLog[0]);

		// return whole buffer if null terminator was overwritten
		if (infoLog[(int)(infoLog.size()) - 1] != '\0')
			return std::string(&infoLog[0], infoLog.size());

		// read as C string. infoLog is guaranteed to be 0-terminated
		return std::string(&infoLog[0]);
	}
	return std::string();
}

Program::Program (const RenderContext& renderCtx)
	: m_gl		(renderCtx.getFunctions())
	, m_program	(0)
{
	m_program = m_gl.createProgram();
	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glCreateProgram()");
}

Program::Program (const glw::Functions& gl)
	: m_gl		(gl)
	, m_program	(0)
{
	m_program = m_gl.createProgram();
	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glCreateProgram()");
}

Program::Program (const RenderContext& renderCtx, deUint32 program)
	: m_gl		(renderCtx.getFunctions())
	, m_program	(program)
{
	m_info.linkOk	= getProgramLinkStatus(m_gl, program);
	m_info.infoLog	= getProgramInfoLog(m_gl, program);
}

Program::~Program (void)
{
	m_gl.deleteProgram(m_program);
}

void Program::attachShader (deUint32 shader)
{
	m_gl.attachShader(m_program, shader);
	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glAttachShader()");
}

void Program::detachShader (deUint32 shader)
{
	m_gl.detachShader(m_program, shader);
	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glDetachShader()");
}

void Program::bindAttribLocation (deUint32 location, const char* name)
{
	m_gl.bindAttribLocation(m_program, location, name);
	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glBindAttribLocation()");
}

void Program::transformFeedbackVaryings (int count, const char* const* varyings, deUint32 bufferMode)
{
	m_gl.transformFeedbackVaryings(m_program, count, varyings, bufferMode);
	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glTransformFeedbackVaryings()");
}

void Program::link (void)
{
	m_info.linkOk		= false;
	m_info.linkTimeUs	= 0;
	m_info.infoLog.clear();

	{
		deUint64 linkStart = deGetMicroseconds();
		m_gl.linkProgram(m_program);
		m_info.linkTimeUs = deGetMicroseconds() - linkStart;
	}
	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glLinkProgram()");

	m_info.linkOk	= getProgramLinkStatus(m_gl, m_program);
	m_info.infoLog	= getProgramInfoLog(m_gl, m_program);
}

bool Program::isSeparable (void) const
{
	int separable = GL_FALSE;

	m_gl.getProgramiv(m_program, GL_PROGRAM_SEPARABLE, &separable);
	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glGetProgramiv()");

	return (separable != GL_FALSE);
}

void Program::setSeparable (bool separable)
{
	m_gl.programParameteri(m_program, GL_PROGRAM_SEPARABLE, separable);
	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glProgramParameteri()");
}

// ProgramPipeline

ProgramPipeline::ProgramPipeline (const RenderContext& renderCtx)
	: m_gl			(renderCtx.getFunctions())
	, m_pipeline	(0)
{
	m_gl.genProgramPipelines(1, &m_pipeline);
	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glGenProgramPipelines()");
}

ProgramPipeline::ProgramPipeline (const glw::Functions& gl)
	: m_gl			(gl)
	, m_pipeline	(0)
{
	m_gl.genProgramPipelines(1, &m_pipeline);
	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glGenProgramPipelines()");
}

ProgramPipeline::~ProgramPipeline (void)
{
	m_gl.deleteProgramPipelines(1, &m_pipeline);
}

void ProgramPipeline::useProgramStages (deUint32 stages, deUint32 program)
{
	m_gl.useProgramStages(m_pipeline, stages, program);
	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glUseProgramStages()");
}

void ProgramPipeline::activeShaderProgram (deUint32 program)
{
	m_gl.activeShaderProgram(m_pipeline, program);
	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glActiveShaderProgram()");
}

bool ProgramPipeline::isValid (void)
{
	glw::GLint status = GL_FALSE;
	m_gl.validateProgramPipeline(m_pipeline);
	GLU_EXPECT_NO_ERROR(m_gl.getError(), "glValidateProgramPipeline()");

	m_gl.getProgramPipelineiv(m_pipeline, GL_VALIDATE_STATUS, &status);

	return (status != GL_FALSE);
}

// ShaderProgram

ShaderProgram::ShaderProgram (const RenderContext& renderCtx, const ProgramSources& sources)
	: m_program(renderCtx.getFunctions())
{
	init(renderCtx.getFunctions(), sources);
}

ShaderProgram::ShaderProgram (const glw::Functions& gl, const ProgramSources& sources)
	: m_program(gl)
{
	init(gl, sources);
}

void ShaderProgram::init (const glw::Functions& gl, const ProgramSources& sources)
{
	try
	{
		bool shadersOk = true;

		for (int shaderType = 0; shaderType < SHADERTYPE_LAST; shaderType++)
		{
			for (int shaderNdx = 0; shaderNdx < (int)sources.sources[shaderType].size(); ++shaderNdx)
			{
				const char* source	= sources.sources[shaderType][shaderNdx].c_str();
				const int	length	= (int)sources.sources[shaderType][shaderNdx].size();

				m_shaders[shaderType].reserve(m_shaders[shaderType].size() + 1);

				m_shaders[shaderType].push_back(new Shader(gl, ShaderType(shaderType)));
				m_shaders[shaderType].back()->setSources(1, &source, &length);
				m_shaders[shaderType].back()->compile();

				shadersOk = shadersOk && m_shaders[shaderType].back()->getCompileStatus();
			}
		}

		if (shadersOk)
		{
			for (int shaderType = 0; shaderType < SHADERTYPE_LAST; shaderType++)
				for (int shaderNdx = 0; shaderNdx < (int)m_shaders[shaderType].size(); ++shaderNdx)
					m_program.attachShader(m_shaders[shaderType][shaderNdx]->getShader());

			for (std::vector<AttribLocationBinding>::const_iterator binding = sources.attribLocationBindings.begin(); binding != sources.attribLocationBindings.end(); ++binding)
				m_program.bindAttribLocation(binding->location, binding->name.c_str());

			DE_ASSERT((sources.transformFeedbackBufferMode == GL_NONE) == sources.transformFeedbackVaryings.empty());
			if (sources.transformFeedbackBufferMode != GL_NONE)
			{
				std::vector<const char*> tfVaryings(sources.transformFeedbackVaryings.size());
				for (int ndx = 0; ndx < (int)tfVaryings.size(); ndx++)
					tfVaryings[ndx] = sources.transformFeedbackVaryings[ndx].c_str();

				m_program.transformFeedbackVaryings((int)tfVaryings.size(), &tfVaryings[0], sources.transformFeedbackBufferMode);
			}

			if (sources.separable)
				m_program.setSeparable(true);

			m_program.link();
		}
	}
	catch (...)
	{
		for (int shaderType = 0; shaderType < SHADERTYPE_LAST; shaderType++)
			for (int shaderNdx = 0; shaderNdx < (int)m_shaders[shaderType].size(); ++shaderNdx)
				delete m_shaders[shaderType][shaderNdx];
		throw;
	}
}

ShaderProgram::~ShaderProgram (void)
{
	for (int shaderType = 0; shaderType < SHADERTYPE_LAST; shaderType++)
		for (int shaderNdx = 0; shaderNdx < (int)m_shaders[shaderType].size(); ++shaderNdx)
			delete m_shaders[shaderType][shaderNdx];
}

// Utilities

deUint32 getGLShaderType (ShaderType shaderType)
{
	static const deUint32 s_typeMap[] =
	{
		GL_VERTEX_SHADER,
		GL_FRAGMENT_SHADER,
		GL_GEOMETRY_SHADER,
		GL_TESS_CONTROL_SHADER,
		GL_TESS_EVALUATION_SHADER,
		GL_COMPUTE_SHADER
	};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_typeMap) == SHADERTYPE_LAST);
	DE_ASSERT(de::inBounds<int>(shaderType, 0, DE_LENGTH_OF_ARRAY(s_typeMap)));
	return s_typeMap[shaderType];
}

deUint32 getGLShaderTypeBit (ShaderType shaderType)
{
	static const deUint32 s_typebitMap[] =
	{
		GL_VERTEX_SHADER_BIT,
		GL_FRAGMENT_SHADER_BIT,
		GL_GEOMETRY_SHADER_BIT,
		GL_TESS_CONTROL_SHADER_BIT,
		GL_TESS_EVALUATION_SHADER_BIT,
		GL_COMPUTE_SHADER_BIT
	};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_typebitMap) == SHADERTYPE_LAST);
	DE_ASSERT(de::inBounds<int>(shaderType, 0, DE_LENGTH_OF_ARRAY(s_typebitMap)));
	return s_typebitMap[shaderType];
}

qpShaderType getLogShaderType (ShaderType shaderType)
{
	static const qpShaderType s_typeMap[] =
	{
		QP_SHADER_TYPE_VERTEX,
		QP_SHADER_TYPE_FRAGMENT,
		QP_SHADER_TYPE_GEOMETRY,
		QP_SHADER_TYPE_TESS_CONTROL,
		QP_SHADER_TYPE_TESS_EVALUATION,
		QP_SHADER_TYPE_COMPUTE
	};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_typeMap) == SHADERTYPE_LAST);
	DE_ASSERT(de::inBounds<int>(shaderType, 0, DE_LENGTH_OF_ARRAY(s_typeMap)));
	return s_typeMap[shaderType];
}

tcu::TestLog& operator<< (tcu::TestLog& log, const ShaderInfo& shaderInfo)
{
	return log << tcu::TestLog::Shader(getLogShaderType(shaderInfo.type), shaderInfo.source, shaderInfo.compileOk, shaderInfo.infoLog);
}

tcu::TestLog& operator<< (tcu::TestLog& log, const Shader& shader)
{
	return log << tcu::TestLog::ShaderProgram(false, "Plain shader") << shader.getInfo() << tcu::TestLog::EndShaderProgram;
}

static void logShaderProgram (tcu::TestLog& log, const ProgramInfo& programInfo, size_t numShaders, const ShaderInfo* const* shaderInfos)
{
	log << tcu::TestLog::ShaderProgram(programInfo.linkOk, programInfo.infoLog);
	try
	{
		for (size_t shaderNdx = 0; shaderNdx < numShaders; ++shaderNdx)
			log << *shaderInfos[shaderNdx];
	}
	catch (...)
	{
		log << tcu::TestLog::EndShaderProgram;
		throw;
	}
	log << tcu::TestLog::EndShaderProgram;

	// Write statistics.
	{
		static const struct
		{
			const char*		name;
			const char*		description;
		} s_compileTimeDesc[] =
		{
			{ "VertexCompileTime",			"Vertex shader compile time"					},
			{ "FragmentCompileTime",		"Fragment shader compile time"					},
			{ "GeometryCompileTime",		"Geometry shader compile time"					},
			{ "TessControlCompileTime",		"Tesselation control shader compile time"		},
			{ "TessEvaluationCompileTime",	"Tesselation evaluation shader compile time"	},
			{ "ComputeCompileTime",			"Compute shader compile time"					},
		};
		DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_compileTimeDesc) == SHADERTYPE_LAST);

		bool allShadersOk = true;

		for (size_t shaderNdx = 0; shaderNdx < numShaders; ++shaderNdx)
		{
			const ShaderInfo&	shaderInfo	= *shaderInfos[shaderNdx];

			log << tcu::TestLog::Float(s_compileTimeDesc[shaderInfo.type].name,
									   s_compileTimeDesc[shaderInfo.type].description,
									   "ms", QP_KEY_TAG_TIME, (float)shaderInfo.compileTimeUs / 1000.0f);

			allShadersOk = allShadersOk && shaderInfo.compileOk;
		}

		if (allShadersOk)
			log << tcu::TestLog::Float("LinkTime", "Link time", "ms", QP_KEY_TAG_TIME, (float)programInfo.linkTimeUs / 1000.0f);
	}
}

tcu::TestLog& operator<< (tcu::TestLog& log, const ShaderProgramInfo& shaderProgramInfo)
{
	std::vector<const ShaderInfo*>	shaderPtrs	(shaderProgramInfo.shaders.size());

	for (size_t ndx = 0; ndx < shaderPtrs.size(); ndx++)
		shaderPtrs[ndx] = &shaderProgramInfo.shaders[ndx];

	logShaderProgram(log, shaderProgramInfo.program, shaderPtrs.size(), shaderPtrs.empty() ? DE_NULL : &shaderPtrs[0]);

	return log;
}

tcu::TestLog& operator<< (tcu::TestLog& log, const ShaderProgram& shaderProgram)
{
	std::vector<const ShaderInfo*>	shaderPtrs;

	for (int shaderType = 0; shaderType < SHADERTYPE_LAST; shaderType++)
	{
		for (int shaderNdx = 0; shaderNdx < shaderProgram.getNumShaders((ShaderType)shaderType); shaderNdx++)
			shaderPtrs.push_back(&shaderProgram.getShaderInfo((ShaderType)shaderType, shaderNdx));
	}

	logShaderProgram(log, shaderProgram.getProgramInfo(), shaderPtrs.size(), shaderPtrs.empty() ? DE_NULL : &shaderPtrs[0]);

	return log;
}

tcu::TestLog& operator<< (tcu::TestLog& log, const ProgramSources& sources)
{
	log << tcu::TestLog::ShaderProgram(false, "(Source only)");

	try
	{
		for (int shaderType = 0; shaderType < SHADERTYPE_LAST; shaderType++)
		{
			for (size_t shaderNdx = 0; shaderNdx < sources.sources[shaderType].size(); shaderNdx++)
			{
				log << tcu::TestLog::Shader(getLogShaderType((ShaderType)shaderType),
											sources.sources[shaderType][shaderNdx],
											false, "");
			}
		}
	}
	catch (...)
	{
		log << tcu::TestLog::EndShaderProgram;
		throw;
	}

	log << tcu::TestLog::EndShaderProgram;

	return log;
}

} // glu