/*-------------------------------------------------------------------------
 * 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 SGLR shader program.
 *//*--------------------------------------------------------------------*/

#include "sglrShaderProgram.hpp"

namespace sglr
{
namespace pdec
{

ShaderProgramDeclaration::ShaderProgramDeclaration (void)
	: m_geometryDecl		(rr::GEOMETRYSHADERINPUTTYPE_LAST, rr::GEOMETRYSHADEROUTPUTTYPE_LAST, 0, 0)
	, m_vertexShaderSet		(false)
	, m_fragmentShaderSet	(false)
	, m_geometryShaderSet	(false)
{
}

ShaderProgramDeclaration& pdec::ShaderProgramDeclaration::operator<< (const VertexAttribute& v)
{
	m_vertexAttributes.push_back(v);
	return *this;
}

ShaderProgramDeclaration& pdec::ShaderProgramDeclaration::operator<< (const VertexToFragmentVarying& v)
{
	m_vertexToFragmentVaryings.push_back(v);
	return *this;
}

ShaderProgramDeclaration& pdec::ShaderProgramDeclaration::operator<< (const VertexToGeometryVarying& v)
{
	m_vertexToGeometryVaryings.push_back(v);
	return *this;
}

ShaderProgramDeclaration& pdec::ShaderProgramDeclaration::operator<< (const GeometryToFragmentVarying& v)
{
	m_geometryToFragmentVaryings.push_back(v);
	return *this;
}

ShaderProgramDeclaration& pdec::ShaderProgramDeclaration::operator<< (const FragmentOutput& v)
{
	m_fragmentOutputs.push_back(v);
	return *this;
}

ShaderProgramDeclaration& pdec::ShaderProgramDeclaration::operator<< (const Uniform& v)
{
	m_uniforms.push_back(v);
	return *this;
}

ShaderProgramDeclaration& pdec::ShaderProgramDeclaration::operator<< (const VertexSource& c)
{
	DE_ASSERT(!m_vertexShaderSet);
	m_vertexSource = c.source;
	m_vertexShaderSet = true;
	return *this;
}

ShaderProgramDeclaration& pdec::ShaderProgramDeclaration::operator<< (const FragmentSource& c)
{
	DE_ASSERT(!m_fragmentShaderSet);
	m_fragmentSource = c.source;
	m_fragmentShaderSet = true;
	return *this;
}

ShaderProgramDeclaration& pdec::ShaderProgramDeclaration::operator<< (const GeometrySource& c)
{
	DE_ASSERT(!m_geometryShaderSet);
	m_geometrySource = c.source;
	m_geometryShaderSet = true;
	return *this;
}

ShaderProgramDeclaration& pdec::ShaderProgramDeclaration::operator<< (const GeometryShaderDeclaration& c)
{
	m_geometryDecl = c;
	return *this;
}

bool ShaderProgramDeclaration::valid (void) const
{
	if (!m_vertexShaderSet || !m_fragmentShaderSet)
		return false;

	if (m_fragmentOutputs.empty())
		return false;

	if (hasGeometryShader())
	{
		if (m_geometryDecl.inputType == rr::GEOMETRYSHADERINPUTTYPE_LAST ||
			m_geometryDecl.outputType == rr::GEOMETRYSHADEROUTPUTTYPE_LAST)
			return false;
	}
	else
	{
		if (m_geometryDecl.inputType != rr::GEOMETRYSHADERINPUTTYPE_LAST ||
			m_geometryDecl.outputType != rr::GEOMETRYSHADEROUTPUTTYPE_LAST ||
			m_geometryDecl.numOutputVertices != 0 ||
			m_geometryDecl.numInvocations != 0)
			return false;
	}

	return true;
}

} //pdec

ShaderProgram::ShaderProgram (const pdec::ShaderProgramDeclaration& decl)
	: rr::VertexShader		(decl.getVertexInputCount(), decl.getVertexOutputCount())
	, rr::GeometryShader	(decl.getGeometryInputCount(),
							 decl.getGeometryOutputCount(),
							 decl.m_geometryDecl.inputType,
							 decl.m_geometryDecl.outputType,
							 decl.m_geometryDecl.numOutputVertices,
							 decl.m_geometryDecl.numInvocations)
	, rr::FragmentShader	(decl.getFragmentInputCount(), decl.getFragmentOutputCount())
	, m_attributeNames		(decl.getVertexInputCount())
	, m_uniforms			(decl.m_uniforms.size())
	, m_vertSrc				(decl.m_vertexSource)
	, m_fragSrc				(decl.m_fragmentSource)
	, m_geomSrc				(decl.hasGeometryShader() ? (decl.m_geometrySource) : (""))
	, m_hasGeometryShader	(decl.hasGeometryShader())
{
	DE_ASSERT(decl.valid());

	// Set up shader IO

	for (size_t ndx = 0; ndx < decl.m_vertexAttributes.size(); ++ndx)
	{
		this->rr::VertexShader::m_inputs[ndx].type	= decl.m_vertexAttributes[ndx].type;
		m_attributeNames[ndx]						= decl.m_vertexAttributes[ndx].name;
	}

	if (m_hasGeometryShader)
	{
		for (size_t ndx = 0; ndx < decl.m_vertexToGeometryVaryings.size(); ++ndx)
		{
			this->rr::VertexShader::m_outputs[ndx].type			= decl.m_vertexToGeometryVaryings[ndx].type;
			this->rr::VertexShader::m_outputs[ndx].flatshade	= decl.m_vertexToGeometryVaryings[ndx].flatshade;

			this->rr::GeometryShader::m_inputs[ndx]				= this->rr::VertexShader::m_outputs[ndx];
		}
		for (size_t ndx = 0; ndx < decl.m_geometryToFragmentVaryings.size(); ++ndx)
		{
			this->rr::GeometryShader::m_outputs[ndx].type		= decl.m_geometryToFragmentVaryings[ndx].type;
			this->rr::GeometryShader::m_outputs[ndx].flatshade	= decl.m_geometryToFragmentVaryings[ndx].flatshade;

			this->rr::FragmentShader::m_inputs[ndx]				= this->rr::GeometryShader::m_outputs[ndx];
		}
	}
	else
	{
		for (size_t ndx = 0; ndx < decl.m_vertexToFragmentVaryings.size(); ++ndx)
		{
			this->rr::VertexShader::m_outputs[ndx].type			= decl.m_vertexToFragmentVaryings[ndx].type;
			this->rr::VertexShader::m_outputs[ndx].flatshade	= decl.m_vertexToFragmentVaryings[ndx].flatshade;

			this->rr::FragmentShader::m_inputs[ndx]				= this->rr::VertexShader::m_outputs[ndx];
		}
	}

	for (size_t ndx = 0; ndx < decl.m_fragmentOutputs.size(); ++ndx)
		this->rr::FragmentShader::m_outputs[ndx].type = decl.m_fragmentOutputs[ndx].type;

	// Set up uniforms

	for (size_t ndx = 0; ndx < decl.m_uniforms.size(); ++ndx)
	{
		this->m_uniforms[ndx].name = decl.m_uniforms[ndx].name;
		this->m_uniforms[ndx].type = decl.m_uniforms[ndx].type;
	}
}

ShaderProgram::~ShaderProgram (void)
{
}

const UniformSlot& ShaderProgram::getUniformByName (const char* name) const
{
	DE_ASSERT(name);

	for (size_t ndx = 0; ndx < m_uniforms.size(); ++ndx)
		if (m_uniforms[ndx].name == std::string(name))
			return m_uniforms[ndx];

	DE_FATAL("Invalid uniform name, uniform not found.");
	return m_uniforms[0];
}

void ShaderProgram::shadePrimitives (rr::GeometryEmitter& output, int verticesIn, const rr::PrimitivePacket* packets, const int numPackets, int invocationID) const
{
	DE_UNREF(output);
	DE_UNREF(verticesIn && packets && numPackets && invocationID);

	// Should never be called.
	DE_ASSERT(DE_FALSE);
}

} // sglr