//Build a Vertex Buffer from a mesh filter
//Project: Open GL Framework
//https://github.com/Illation/GLFramework
//Author: Robert Lindner

//Mesh filters cotain all the data loaded from 3d files like vertex positions, normal, texcoords etc
//Various shaders can require defferent types of data, some form of uber shader might need  most of the data a mesh has,
//	while an emissive shader only needs the positions and maybe the color, but doesnt really need normals
//A material knows what data is required by the shader
//Both the mesh filter and the material are known by a model component in the framework
//When the model component renders, it needs a vertex buffer from the mesh filter that is suited to the material needs
//the mesh filter can hold various buffers for different materials


//*******************
//Static Map containing all possible flags for the input layout
//*******************
enum VertexFlags
{
	POSITION = 1 << 0,
	NORMAL   = 1 << 1,
	BINORMAL = 1 << 2,
	TANGENT  = 1 << 3,
	COLOR    = 1 << 4,
	TEXCOORD = 1 << 5
};
class MeshFilter
{
public:
//[...]
	static std::map<VertexFlags, AttributeDescriptor> LayoutAttributes;
//[...]
}
std::map<VertexFlags, AttributeDescriptor> MeshFilter::LayoutAttributes =
{
	{ POSITION,	{ "position",	GL_FLOAT, 3 } },
	{ NORMAL,	{ "normal",		GL_FLOAT, 3 } },
	{ BINORMAL,	{ "binormal",	GL_FLOAT, 3 } },
	{ TANGENT,	{ "tangent",	GL_FLOAT, 3 } },
	{ COLOR,	{ "color",		GL_FLOAT, 3 } },
	{ TEXCOORD,	{ "texcoord",	GL_FLOAT, 2 } }
};

//********************
//build an unsigned int containing all layout flags for a material
//********************
void Material::SpecifyInputLayout()
{
	unsigned stride = 0;
	for (auto it = MeshFilter::LayoutAttributes.begin(); it != MeshFilter::LayoutAttributes.end(); ++it)
	{
		if (m_LayoutFlags & it->first) stride += it->second.dataSize;
	}
	unsigned startPos = 0;
	for (auto it = MeshFilter::LayoutAttributes.begin(); it != MeshFilter::LayoutAttributes.end(); ++it)
	{
		if (m_LayoutFlags & it->first)
		{
			const char* name = it->second.name.c_str();
			GLint attrib = glGetAttribLocation(m_Shader->GetProgram(), name);
			if (attrib >= 0)
			{
				glEnableVertexAttribArray(attrib);
				glVertexAttribPointer(attrib, it->second.dataSize, it->second.dataType, GL_FALSE,
					stride * sizeof(GLfloat), (void*)(startPos * sizeof(GLfloat)));
				startPos += it->second.dataSize;
			}
			else LOGGER::Log("Could not bind attribute '" + string(name) + "' to shader: " + m_Shader->GetName(), LogLevel::Error);
		}
	}
}

//*******************
//Build a vertex buffer containing all the layout attributes required by a material
//*******************
void MeshFilter::BuildVertexBuffer(Material* pMaterial)
{
	unsigned layoutFlags = pMaterial->GetLayoutFlags();
	//Check if VertexBufferInfo already exists with requested InputLayout
	if (GetVertexObjectId(layoutFlags) >= 0)
		return;
	//Determine stride of vertex layout
	unsigned stride = 0;
	for (auto it = LayoutAttributes.begin(); it != LayoutAttributes.end(); ++it)
	{
		if (layoutFlags & it->first)//material requires this data
		{
			if (m_SupportedFlags & it->first)stride += it->second.dataSize;//filter can provide this data
			else
			{
				string FailString = "Failed to build vertex buffer, mesh filter cannot provide required data\n";
				FailString += "required data: "; 
				FailString += PrintFlags(layoutFlags);
				FailString += "\nprovided data: "; 
				FailString += PrintFlags(m_SupportedFlags);FailString += "\n";
				LOGGER::Log(FailString.c_str(), LogLevel::Error);
				return;
			}
		}
	}
	//Initialize datastructure
	std::vector<float> vertices;
	vertices.reserve(m_VertexCount*stride);
	//Add data if material uses attribute
	for (size_t i = 0; i < m_VertexCount; i++)
	{
		if (layoutFlags & VertexFlags::POSITION)
		{
			vertices.push_back(m_Positions[i].x);
			vertices.push_back(m_Positions[i].y);
			vertices.push_back(m_Positions[i].z);
		}
		if (layoutFlags & VertexFlags::NORMAL)
		{
			vertices.push_back(m_Normals[i].x);
			vertices.push_back(m_Normals[i].y);
			vertices.push_back(m_Normals[i].z);
		}
		if (layoutFlags & VertexFlags::BINORMAL)
		{
			vertices.push_back(m_BiNormals[i].x);
			vertices.push_back(m_BiNormals[i].y);
			vertices.push_back(m_BiNormals[i].z);
		}
		if (layoutFlags & VertexFlags::TANGENT)
		{
			vertices.push_back(m_Tangents[i].x);
			vertices.push_back(m_Tangents[i].y);
			vertices.push_back(m_Tangents[i].z);
		}
		if (layoutFlags & VertexFlags::COLOR)
		{
			vertices.push_back(m_Colors[i].x);
			vertices.push_back(m_Colors[i].y);
			vertices.push_back(m_Colors[i].z);
			//vertices.push_back(m_Colors[i][3]);//not sure which variable to choose
		}
		if (layoutFlags & VertexFlags::TEXCOORD)
		{
			vertices.push_back(m_TexCoords[i].x);
			vertices.push_back(m_TexCoords[i].y);
		}
	}

	VertexObject obj;
	glGenVertexArrays(1, &obj.array);
	glBindVertexArray(obj.array);
	glBindBuffer(GL_ARRAY_BUFFER, obj.array);
	//Vertex Buffer Object
	glGenBuffers(1, &obj.buffer);
	glBindBuffer(GL_ARRAY_BUFFER, obj.buffer);
	glBufferData(GL_ARRAY_BUFFER, sizeof(float)*vertices.size(), vertices.data(), GL_STATIC_DRAW);
	//Specify Input Layout
	pMaterial->SpecifyInputLayout();
	//index buffer
	glGenBuffers(1, &obj.index);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, obj.index);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(GLuint)*m_Indices.size(),m_Indices.data(),GL_STATIC_DRAW);
	
	//Add flags for later reference
	obj.flags = layoutFlags;
	//Add to VertexObject datastructure
	m_Objects.push_back(obj);
}