/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * 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 Program interface
 *//*--------------------------------------------------------------------*/

#include "es31fProgramInterfaceDefinition.hpp"
#include "es31fProgramInterfaceDefinitionUtil.hpp"
#include "gluVarType.hpp"
#include "gluShaderProgram.hpp"
#include "deSTLUtil.hpp"
#include "deStringUtil.hpp"
#include "glwEnums.hpp"

#include <set>

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace ProgramInterfaceDefinition
{
namespace
{

static const glu::ShaderType s_shaderStageOrder[] =
{
	glu::SHADERTYPE_COMPUTE,

	glu::SHADERTYPE_VERTEX,
	glu::SHADERTYPE_TESSELLATION_CONTROL,
	glu::SHADERTYPE_TESSELLATION_EVALUATION,
	glu::SHADERTYPE_GEOMETRY,
	glu::SHADERTYPE_FRAGMENT
};

// s_shaderStageOrder does not contain ShaderType_LAST
DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_shaderStageOrder) == glu::SHADERTYPE_LAST);

static bool containsMatchingSubtype (const glu::VarType& varType, bool (*predicate)(glu::DataType))
{
	if (varType.isBasicType() && predicate(varType.getBasicType()))
		return true;

	if (varType.isArrayType())
		return containsMatchingSubtype(varType.getElementType(), predicate);

	if (varType.isStructType())
		for (int memberNdx = 0; memberNdx < varType.getStructPtr()->getNumMembers(); ++memberNdx)
			if (containsMatchingSubtype(varType.getStructPtr()->getMember(memberNdx).getType(), predicate))
				return true;

	return false;
}

static bool containsMatchingSubtype (const std::vector<glu::VariableDeclaration>& decls, bool (*predicate)(glu::DataType))
{
	for (int varNdx = 0; varNdx < (int)decls.size(); ++varNdx)
		if (containsMatchingSubtype(decls[varNdx].varType, predicate))
			return true;
	return false;
}

static bool isOpaqueType (glu::DataType type)
{
	return	glu::isDataTypeAtomicCounter(type)	||
			glu::isDataTypeImage(type)			||
			glu::isDataTypeSampler(type);
}

static int getShaderStageIndex (glu::ShaderType stage)
{
	const glu::ShaderType* const it = std::find(DE_ARRAY_BEGIN(s_shaderStageOrder), DE_ARRAY_END(s_shaderStageOrder), stage);

	if (it == DE_ARRAY_END(s_shaderStageOrder))
		return -1;
	else
	{
		const int index = (int)(it - DE_ARRAY_BEGIN(s_shaderStageOrder));
		return index;
	}
}

} // anonymous

Shader::Shader (glu::ShaderType type, glu::GLSLVersion version)
	: m_shaderType	(type)
	, m_version		(version)
{
}

Shader::~Shader (void)
{
}

static bool isIllegalVertexInput (const glu::VarType& varType)
{
	// booleans, opaque types, arrays, structs are not allowed as inputs
	if (!varType.isBasicType())
		return true;
	if (glu::isDataTypeBoolOrBVec(varType.getBasicType()))
		return true;
	return false;
}

static bool isIllegalVertexOutput (const glu::VarType& varType, bool insideAStruct = false, bool insideAnArray = false)
{
	// booleans, opaque types, arrays of arrays, arrays of structs, array in struct, struct struct are not allowed as vertex outputs

	if (varType.isBasicType())
	{
		const bool isOpaqueType = !glu::isDataTypeScalar(varType.getBasicType()) && !glu::isDataTypeVector(varType.getBasicType()) && !glu::isDataTypeMatrix(varType.getBasicType());

		if (glu::isDataTypeBoolOrBVec(varType.getBasicType()))
			return true;

		if (isOpaqueType)
			return true;

		return false;
	}
	else if (varType.isArrayType())
	{
		if (insideAnArray || insideAStruct)
			return true;

		return isIllegalVertexOutput(varType.getElementType(), insideAStruct, true);
	}
	else if (varType.isStructType())
	{
		if (insideAnArray || insideAStruct)
			return true;

		for (int ndx = 0; ndx < varType.getStructPtr()->getNumMembers(); ++ndx)
			if (isIllegalVertexOutput(varType.getStructPtr()->getMember(ndx).getType(), true, insideAnArray))
				return true;

		return false;
	}
	else
	{
		DE_ASSERT(false);
		return true;
	}
}

static bool isIllegalFragmentInput (const glu::VarType& varType)
{
	return isIllegalVertexOutput(varType);
}

static bool isIllegalFragmentOutput (const glu::VarType& varType, bool insideAnArray = false)
{
	// booleans, opaque types, matrices, structs, arrays of arrays are not allowed as outputs

	if (varType.isBasicType())
	{
		const bool isOpaqueType = !glu::isDataTypeScalar(varType.getBasicType()) && !glu::isDataTypeVector(varType.getBasicType()) && !glu::isDataTypeMatrix(varType.getBasicType());

		if (glu::isDataTypeBoolOrBVec(varType.getBasicType()) || isOpaqueType || glu::isDataTypeMatrix(varType.getBasicType()))
			return true;
		return false;
	}
	else if (varType.isArrayType())
	{
		if (insideAnArray)
			return true;
		return isIllegalFragmentOutput(varType.getElementType(), true);
	}
	else if (varType.isStructType())
		return true;
	else
	{
		DE_ASSERT(false);
		return true;
	}
}

static bool isTypeIntegerOrContainsIntegers (const glu::VarType& varType)
{
	if (varType.isBasicType())
		return glu::isDataTypeIntOrIVec(varType.getBasicType()) || glu::isDataTypeUintOrUVec(varType.getBasicType());
	else if (varType.isArrayType())
		return isTypeIntegerOrContainsIntegers(varType.getElementType());
	else if (varType.isStructType())
	{
		for (int ndx = 0; ndx < varType.getStructPtr()->getNumMembers(); ++ndx)
			if (isTypeIntegerOrContainsIntegers(varType.getStructPtr()->getMember(ndx).getType()))
				return true;
		return false;
	}
	else
	{
		DE_ASSERT(false);
		return true;
	}
}

bool Shader::isValid (void) const
{
	// Default block variables
	{
		for (int varNdx = 0; varNdx < (int)m_defaultBlock.variables.size(); ++varNdx)
		{
			// atomic declaration in the default block without binding
			if (m_defaultBlock.variables[varNdx].layout.binding == -1 &&
				containsMatchingSubtype(m_defaultBlock.variables[varNdx].varType, glu::isDataTypeAtomicCounter))
				return false;

			// atomic declaration in a struct
			if (m_defaultBlock.variables[varNdx].varType.isStructType() &&
				containsMatchingSubtype(m_defaultBlock.variables[varNdx].varType, glu::isDataTypeAtomicCounter))
				return false;

			// Unsupported layout qualifiers

			if (m_defaultBlock.variables[varNdx].layout.matrixOrder != glu::MATRIXORDER_LAST)
				return false;

			if (containsMatchingSubtype(m_defaultBlock.variables[varNdx].varType, glu::isDataTypeSampler))
			{
				const glu::Layout layoutWithLocationAndBinding(m_defaultBlock.variables[varNdx].layout.location, m_defaultBlock.variables[varNdx].layout.binding);

				if (m_defaultBlock.variables[varNdx].layout != layoutWithLocationAndBinding)
					return false;
			}
		}
	}

	// Interface blocks
	{
		for (int interfaceNdx = 0; interfaceNdx < (int)m_defaultBlock.interfaceBlocks.size(); ++interfaceNdx)
		{
			// ES31 disallows interface block array arrays
			if (m_defaultBlock.interfaceBlocks[interfaceNdx].dimensions.size() > 1)
				return false;

			// Interface block arrays must have instance name
			if (!m_defaultBlock.interfaceBlocks[interfaceNdx].dimensions.empty() && m_defaultBlock.interfaceBlocks[interfaceNdx].instanceName.empty())
				return false;

			// Opaque types in interface block
			if (containsMatchingSubtype(m_defaultBlock.interfaceBlocks[interfaceNdx].variables, isOpaqueType))
				return false;
		}
	}

	// Shader type specific

	if (m_shaderType == glu::SHADERTYPE_VERTEX)
	{
		for (int varNdx = 0; varNdx < (int)m_defaultBlock.variables.size(); ++varNdx)
		{
			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_IN && isIllegalVertexInput(m_defaultBlock.variables[varNdx].varType))
				return false;
			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_OUT && isIllegalVertexOutput(m_defaultBlock.variables[varNdx].varType))
				return false;
			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_OUT && m_defaultBlock.variables[varNdx].interpolation != glu::INTERPOLATION_FLAT && isTypeIntegerOrContainsIntegers(m_defaultBlock.variables[varNdx].varType))
				return false;
		}
		for (int interfaceNdx = 0; interfaceNdx < (int)m_defaultBlock.interfaceBlocks.size(); ++interfaceNdx)
		{
			if (m_defaultBlock.interfaceBlocks[interfaceNdx].storage == glu::STORAGE_IN			||
				m_defaultBlock.interfaceBlocks[interfaceNdx].storage == glu::STORAGE_PATCH_IN	||
				m_defaultBlock.interfaceBlocks[interfaceNdx].storage == glu::STORAGE_PATCH_OUT)
			{
				return false;
			}
		}
	}
	else if (m_shaderType == glu::SHADERTYPE_FRAGMENT)
	{
		for (int varNdx = 0; varNdx < (int)m_defaultBlock.variables.size(); ++varNdx)
		{
			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_IN && isIllegalFragmentInput(m_defaultBlock.variables[varNdx].varType))
				return false;
			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_IN && m_defaultBlock.variables[varNdx].interpolation != glu::INTERPOLATION_FLAT && isTypeIntegerOrContainsIntegers(m_defaultBlock.variables[varNdx].varType))
				return false;
			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_OUT && isIllegalFragmentOutput(m_defaultBlock.variables[varNdx].varType))
				return false;
		}
		for (int interfaceNdx = 0; interfaceNdx < (int)m_defaultBlock.interfaceBlocks.size(); ++interfaceNdx)
		{
			if (m_defaultBlock.interfaceBlocks[interfaceNdx].storage == glu::STORAGE_PATCH_IN	||
				m_defaultBlock.interfaceBlocks[interfaceNdx].storage == glu::STORAGE_OUT		||
				m_defaultBlock.interfaceBlocks[interfaceNdx].storage == glu::STORAGE_PATCH_OUT)
			{
				return false;
			}
		}
	}
	else if (m_shaderType == glu::SHADERTYPE_COMPUTE)
	{
		for (int varNdx = 0; varNdx < (int)m_defaultBlock.variables.size(); ++varNdx)
		{
			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_IN			||
				m_defaultBlock.variables[varNdx].storage == glu::STORAGE_PATCH_IN	||
				m_defaultBlock.variables[varNdx].storage == glu::STORAGE_OUT		||
				m_defaultBlock.variables[varNdx].storage == glu::STORAGE_PATCH_OUT)
			{
				return false;
			}
		}
		for (int interfaceNdx = 0; interfaceNdx < (int)m_defaultBlock.interfaceBlocks.size(); ++interfaceNdx)
		{
			if (m_defaultBlock.interfaceBlocks[interfaceNdx].storage == glu::STORAGE_IN			||
				m_defaultBlock.interfaceBlocks[interfaceNdx].storage == glu::STORAGE_PATCH_IN	||
				m_defaultBlock.interfaceBlocks[interfaceNdx].storage == glu::STORAGE_OUT		||
				m_defaultBlock.interfaceBlocks[interfaceNdx].storage == glu::STORAGE_PATCH_OUT)
			{
				return false;
			}
		}
	}
	else if (m_shaderType == glu::SHADERTYPE_GEOMETRY)
	{
		for (int varNdx = 0; varNdx < (int)m_defaultBlock.variables.size(); ++varNdx)
		{
			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_PATCH_IN	||
				m_defaultBlock.variables[varNdx].storage == glu::STORAGE_PATCH_OUT)
			{
				return false;
			}
			// arrayed input
			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_IN && !m_defaultBlock.variables[varNdx].varType.isArrayType())
				return false;
		}
		for (int interfaceNdx = 0; interfaceNdx < (int)m_defaultBlock.interfaceBlocks.size(); ++interfaceNdx)
		{
			if (m_defaultBlock.interfaceBlocks[interfaceNdx].storage == glu::STORAGE_PATCH_IN	||
				m_defaultBlock.interfaceBlocks[interfaceNdx].storage == glu::STORAGE_PATCH_OUT)
			{
				return false;
			}
			// arrayed input
			if (m_defaultBlock.interfaceBlocks[interfaceNdx].storage == glu::STORAGE_IN && m_defaultBlock.interfaceBlocks[interfaceNdx].dimensions.empty())
				return false;
		}
	}
	else if (m_shaderType == glu::SHADERTYPE_TESSELLATION_CONTROL)
	{
		for (int varNdx = 0; varNdx < (int)m_defaultBlock.variables.size(); ++varNdx)
		{
			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_PATCH_IN)
				return false;
			// arrayed input
			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_IN && !m_defaultBlock.variables[varNdx].varType.isArrayType())
				return false;
			// arrayed output
			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_OUT && !m_defaultBlock.variables[varNdx].varType.isArrayType())
				return false;
		}
		for (int interfaceNdx = 0; interfaceNdx < (int)m_defaultBlock.interfaceBlocks.size(); ++interfaceNdx)
		{
			if (m_defaultBlock.interfaceBlocks[interfaceNdx].storage == glu::STORAGE_PATCH_IN)
				return false;
			// arrayed input
			if (m_defaultBlock.interfaceBlocks[interfaceNdx].storage == glu::STORAGE_IN && m_defaultBlock.interfaceBlocks[interfaceNdx].dimensions.empty())
				return false;
			// arrayed output
			if (m_defaultBlock.interfaceBlocks[interfaceNdx].storage == glu::STORAGE_OUT && m_defaultBlock.interfaceBlocks[interfaceNdx].dimensions.empty())
				return false;
		}
	}
	else if (m_shaderType == glu::SHADERTYPE_TESSELLATION_EVALUATION)
	{
		for (int varNdx = 0; varNdx < (int)m_defaultBlock.variables.size(); ++varNdx)
		{
			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_PATCH_OUT)
				return false;
			// arrayed input
			if (m_defaultBlock.variables[varNdx].storage == glu::STORAGE_IN && !m_defaultBlock.variables[varNdx].varType.isArrayType())
				return false;
		}
		for (int interfaceNdx = 0; interfaceNdx < (int)m_defaultBlock.interfaceBlocks.size(); ++interfaceNdx)
		{
			if (m_defaultBlock.interfaceBlocks[interfaceNdx].storage == glu::STORAGE_PATCH_OUT)
				return false;
			// arrayed input
			if (m_defaultBlock.interfaceBlocks[interfaceNdx].storage == glu::STORAGE_IN && m_defaultBlock.interfaceBlocks[interfaceNdx].dimensions.empty())
				return false;
		}
	}
	else
		DE_ASSERT(false);

	return true;
}

Program::Program (void)
	: m_separable				(false)
	, m_xfbMode					(0)
	, m_geoNumOutputVertices	(0)
	, m_tessNumOutputVertices	(0)
{
}

static void collectStructPtrs (std::set<const glu::StructType*>& dst, const glu::VarType& type)
{
	if (type.isArrayType())
		collectStructPtrs(dst, type.getElementType());
	else if (type.isStructType())
	{
		dst.insert(type.getStructPtr());

		for (int memberNdx = 0; memberNdx < type.getStructPtr()->getNumMembers(); ++memberNdx)
			collectStructPtrs(dst, type.getStructPtr()->getMember(memberNdx).getType());
	}
}

Program::~Program (void)
{
	// delete shader struct types, need to be done by the program since shaders might share struct types
	{
		std::set<const glu::StructType*> structTypes;

		for (int shaderNdx = 0; shaderNdx < (int)m_shaders.size(); ++shaderNdx)
		{
			for (int varNdx = 0; varNdx < (int)m_shaders[shaderNdx]->m_defaultBlock.variables.size(); ++varNdx)
				collectStructPtrs(structTypes, m_shaders[shaderNdx]->m_defaultBlock.variables[varNdx].varType);

			for (int interfaceNdx = 0; interfaceNdx < (int)m_shaders[shaderNdx]->m_defaultBlock.interfaceBlocks.size(); ++interfaceNdx)
				for (int varNdx = 0; varNdx < (int)m_shaders[shaderNdx]->m_defaultBlock.interfaceBlocks[interfaceNdx].variables.size(); ++varNdx)
					collectStructPtrs(structTypes, m_shaders[shaderNdx]->m_defaultBlock.interfaceBlocks[interfaceNdx].variables[varNdx].varType);
		}

		for (std::set<const glu::StructType*>::iterator it = structTypes.begin(); it != structTypes.end(); ++it)
			delete *it;
	}

	for (int shaderNdx = 0; shaderNdx < (int)m_shaders.size(); ++shaderNdx)
		delete m_shaders[shaderNdx];
	m_shaders.clear();
}

Shader* Program::addShader (glu::ShaderType type, glu::GLSLVersion version)
{
	DE_ASSERT(type < glu::SHADERTYPE_LAST);

	Shader* shader;

	// make sure push_back() cannot throw
	m_shaders.reserve(m_shaders.size() + 1);

	shader = new Shader(type, version);
	m_shaders.push_back(shader);

	return shader;
}

void Program::setSeparable (bool separable)
{
	m_separable = separable;
}

bool Program::isSeparable (void) const
{
	return m_separable;
}

const std::vector<Shader*>& Program::getShaders (void) const
{
	return m_shaders;
}

glu::ShaderType Program::getFirstStage (void) const
{
	const int	nullValue	= DE_LENGTH_OF_ARRAY(s_shaderStageOrder);
	int			firstStage	= nullValue;

	for (int shaderNdx = 0; shaderNdx < (int)m_shaders.size(); ++shaderNdx)
	{
		const int index = getShaderStageIndex(m_shaders[shaderNdx]->getType());
		if (index != -1)
			firstStage = de::min(firstStage, index);
	}

	if (firstStage == nullValue)
		return glu::SHADERTYPE_LAST;
	else
		return s_shaderStageOrder[firstStage];
}

glu::ShaderType Program::getLastStage (void) const
{
	const int	nullValue	= -1;
	int			lastStage	= nullValue;

	for (int shaderNdx = 0; shaderNdx < (int)m_shaders.size(); ++shaderNdx)
	{
		const int index = getShaderStageIndex(m_shaders[shaderNdx]->getType());
		if (index != -1)
			lastStage = de::max(lastStage, index);
	}

	if (lastStage == nullValue)
		return glu::SHADERTYPE_LAST;
	else
		return s_shaderStageOrder[lastStage];
}

bool Program::hasStage (glu::ShaderType stage) const
{
	for (int shaderNdx = 0; shaderNdx < (int)m_shaders.size(); ++shaderNdx)
	{
		if (m_shaders[shaderNdx]->getType() == stage)
			return true;
	}
	return false;
}

void Program::addTransformFeedbackVarying (const std::string& varName)
{
	m_xfbVaryings.push_back(varName);
}

const std::vector<std::string>& Program::getTransformFeedbackVaryings (void) const
{
	return m_xfbVaryings;
}

void Program::setTransformFeedbackMode (deUint32 mode)
{
	m_xfbMode = mode;
}

deUint32 Program::getTransformFeedbackMode (void) const
{
	return m_xfbMode;
}

deUint32 Program::getGeometryNumOutputVertices (void) const
{
	return m_geoNumOutputVertices;
}

void Program::setGeometryNumOutputVertices (deUint32 vertices)
{
	m_geoNumOutputVertices = vertices;
}

deUint32 Program::getTessellationNumOutputPatchVertices (void) const
{
	return m_tessNumOutputVertices;
}

void Program::setTessellationNumOutputPatchVertices (deUint32 vertices)
{
	m_tessNumOutputVertices = vertices;
}

bool Program::isValid (void) const
{
	const bool	isOpenGLES			= (m_shaders.empty()) ? (false) : (glu::glslVersionIsES(m_shaders[0]->getVersion()));
	bool		computePresent		= false;
	bool		vertexPresent		= false;
	bool		fragmentPresent		= false;
	bool		tessControlPresent	= false;
	bool		tessEvalPresent		= false;
	bool		geometryPresent		= false;

	if (m_shaders.empty())
		return false;

	for (int ndx = 0; ndx < (int)m_shaders.size(); ++ndx)
		if (!m_shaders[ndx]->isValid())
			return false;

	// same version
	for (int ndx = 1; ndx < (int)m_shaders.size(); ++ndx)
		if (m_shaders[0]->getVersion() != m_shaders[ndx]->getVersion())
			return false;

	for (int ndx = 0; ndx < (int)m_shaders.size(); ++ndx)
	{
		switch (m_shaders[ndx]->getType())
		{
			case glu::SHADERTYPE_COMPUTE:					computePresent = true;		break;
			case glu::SHADERTYPE_VERTEX:					vertexPresent = true;		break;
			case glu::SHADERTYPE_FRAGMENT:					fragmentPresent = true;		break;
			case glu::SHADERTYPE_TESSELLATION_CONTROL:		tessControlPresent = true;	break;
			case glu::SHADERTYPE_TESSELLATION_EVALUATION:	tessEvalPresent = true;		break;
			case glu::SHADERTYPE_GEOMETRY:					geometryPresent = true;		break;
			default:
				DE_ASSERT(false);
				break;
		}
	}
	// compute present -> no other stages present
	{
		const bool nonComputePresent = vertexPresent || fragmentPresent || tessControlPresent || tessEvalPresent || geometryPresent;
		if (computePresent && nonComputePresent)
			return false;
	}

	// must contain both vertex and fragment shaders
	if (!computePresent && !m_separable)
	{
		if (!vertexPresent || !fragmentPresent)
			return false;
	}

	// tess.Eval present <=> tess.Control present
	if (!m_separable)
	{
		if (tessEvalPresent != tessControlPresent)
			return false;
	}

	if ((m_tessNumOutputVertices != 0) != (tessControlPresent || tessEvalPresent))
		return false;

	if ((m_geoNumOutputVertices != 0) != geometryPresent)
		return false;

	for (int ndx = 0; ndx < (int)m_xfbVaryings.size(); ++ndx)
	{
		// user-defined
		if (!de::beginsWith(m_xfbVaryings[ndx], "gl_"))
		{
			std::vector<ProgramInterfaceDefinition::VariablePathComponent> path;
			if (!findProgramVariablePathByPathName(path, this, m_xfbVaryings[ndx], VariableSearchFilter::createShaderTypeStorageFilter(getProgramTransformFeedbackStage(this), glu::STORAGE_OUT)))
				return false;
			if (!path.back().isVariableType())
				return false;

			// Khronos bug #12787 disallowed capturing whole structs in OpenGL ES.
			if (path.back().getVariableType()->isStructType() && isOpenGLES)
				return false;
		}
	}

	return true;
}

} // ProgramInterfaceDefinition
} // Functional
} // gles31
} // deqp