/**
 * Copyright (c) 2006-2020 LOVE Development Team
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 **/

// LOVE
#include "Mesh.h"
#include "common/Matrix.h"
#include "common/Exception.h"
#include "Shader.h"
#include "Graphics.h"

// C++
#include <algorithm>
#include <limits>

namespace love
{
namespace graphics
{

static_assert(offsetof(Vertex, x) == sizeof(float) * 0, "Incorrect position offset in Vertex struct");
static_assert(offsetof(Vertex, s) == sizeof(float) * 2, "Incorrect texture coordinate offset in Vertex struct");
static_assert(offsetof(Vertex, color.r) == sizeof(float) * 4, "Incorrect color offset in Vertex struct");

std::vector<Buffer::DataDeclaration> Mesh::getDefaultVertexFormat()
{
	return Buffer::getCommonFormatDeclaration(CommonFormat::XYf_STf_RGBAub);
}

love::Type Mesh::type("Mesh", &Drawable::type);

Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferUsage usage)
	: primitiveType(drawmode)
{
	try
	{
		vertexData = new uint8[datasize];
	}
	catch (std::exception &)
	{
		throw love::Exception("Out of memory");
	}

	memcpy(vertexData, data, datasize);

	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, usage);
	vertexBuffer.set(gfx->newBuffer(settings, vertexformat, vertexData, datasize, 0), Acquire::NORETAIN);

	vertexCount = vertexBuffer->getArrayLength();
	vertexStride = vertexBuffer->getArrayStride();
	vertexFormat = vertexBuffer->getDataMembers();

	setupAttachedAttributes();

	indexDataType = getIndexDataTypeFromMax(vertexCount);
}

Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferUsage usage)
	: vertexCount((size_t) vertexcount)
	, indexDataType(getIndexDataTypeFromMax(vertexcount))
	, primitiveType(drawmode)
{
	if (vertexcount <= 0)
		throw love::Exception("Invalid number of vertices (%d).", vertexcount);

	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, usage);
	vertexBuffer.set(gfx->newBuffer(settings, vertexformat, nullptr, 0, vertexcount), Acquire::NORETAIN);

	vertexStride = vertexBuffer->getArrayStride();
	vertexFormat = vertexBuffer->getDataMembers();

	setupAttachedAttributes();

	try
	{
		vertexData = new uint8[vertexBuffer->getSize()];
	}
	catch (std::exception &)
	{
		throw love::Exception("Out of memory");
	}

	memset(vertexData, 0, vertexBuffer->getSize());
	vertexBuffer->fill(0, vertexBuffer->getSize(), vertexData);
}

Mesh::Mesh(const std::vector<Mesh::BufferAttribute> &attributes, PrimitiveType drawmode)
	: primitiveType(drawmode)
{
	if (attributes.size() == 0)
		throw love::Exception("At least one buffer attribute must be specified in this constructor.");

	attachedAttributes = attributes;

	vertexCount = attachedAttributes.size() > 0 ? LOVE_UINT32_MAX : 0;

	for (const auto &attrib : attachedAttributes)
	{
		if ((attrib.buffer->getTypeFlags() & Buffer::TYPEFLAG_VERTEX) == 0)
			throw love::Exception("Buffer must be created with vertex buffer support to be used as a Mesh vertex attribute.");

		if (getAttachedAttributeIndex(attrib.name) != -1)
			throw love::Exception("Duplicate vertex attribute name: %s", attrib.name.c_str());

		vertexCount = std::min(vertexCount, attrib.buffer->getArrayLength());
	}

	indexDataType = getIndexDataTypeFromMax(vertexCount);
}

Mesh::~Mesh()
{
	delete vertexData;
	if (indexData != nullptr)
		free(indexData);
}

void Mesh::setupAttachedAttributes()
{
	for (size_t i = 0; i < vertexFormat.size(); i++)
	{
		const std::string &name = vertexFormat[i].decl.name;

		if (getAttachedAttributeIndex(name) != -1)
			throw love::Exception("Duplicate vertex attribute name: %s", name.c_str());

		attachedAttributes.push_back({name, vertexBuffer, nullptr, (int) i, STEP_PER_VERTEX, true});
	}
}

int Mesh::getAttachedAttributeIndex(const std::string &name) const
{
	for (int i = 0; i < (int) attachedAttributes.size(); i++)
	{
		if (attachedAttributes[i].name == name)
			return i;
	}

	return -1;
}

void *Mesh::checkVertexDataOffset(size_t vertindex, size_t *byteoffset)
{
	if (vertindex >= vertexCount)
		throw love::Exception("Invalid vertex index: %ld", vertindex + 1);

	if (vertexData == nullptr)
		throw love::Exception("Mesh must own its own vertex buffer.");

	size_t offset = vertindex * vertexStride;

	if (byteoffset != nullptr)
		*byteoffset = offset;
	return vertexData + offset;
}

size_t Mesh::getVertexCount() const
{
	return vertexCount;
}

size_t Mesh::getVertexStride() const
{
	return vertexStride;
}

Buffer *Mesh::getVertexBuffer() const
{
	return vertexBuffer;
}

const std::vector<Buffer::DataMember> &Mesh::getVertexFormat() const
{
	return vertexFormat;
}

void Mesh::setAttributeEnabled(const std::string &name, bool enable)
{
	int index = getAttachedAttributeIndex(name);
	if (index == -1)
		throw love::Exception("Mesh does not have an attached vertex attribute named '%s'", name.c_str());

	attachedAttributes[index].enabled = enable;
}

bool Mesh::isAttributeEnabled(const std::string &name) const
{
	int index = getAttachedAttributeIndex(name);
	if (index == -1)
		throw love::Exception("Mesh does not have an attached vertex attribute named '%s'", name.c_str());

	return attachedAttributes[index].enabled;
}

void Mesh::attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh, const std::string &attachname, AttributeStep step)
{
	if ((buffer->getTypeFlags() & Buffer::TYPEFLAG_VERTEX) == 0)
		throw love::Exception("Buffer must be created with vertex buffer support to be used as a Mesh vertex attribute.");

	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
	if (step == STEP_PER_INSTANCE && !gfx->getCapabilities().features[Graphics::FEATURE_INSTANCING])
		throw love::Exception("Vertex attribute instancing is not supported on this system.");

	BufferAttribute oldattrib = {};
	BufferAttribute newattrib = {};

	int oldindex = getAttachedAttributeIndex(name);
	if (oldindex != -1)
		oldattrib = attachedAttributes[oldindex];
	else if (attachedAttributes.size() + 1 > VertexAttributes::MAX)
		throw love::Exception("A maximum of %d attributes can be attached at once.", VertexAttributes::MAX);

	newattrib.name = name;
	newattrib.buffer = buffer;
	newattrib.mesh = mesh;
	newattrib.enabled = oldattrib.buffer.get() ? oldattrib.enabled : true;
	newattrib.indexInBuffer = buffer->getDataMemberIndex(attachname);
	newattrib.step = step;

	if (newattrib.indexInBuffer < 0)
		throw love::Exception("The specified vertex buffer does not have a vertex attribute named '%s'", attachname.c_str());

	if (oldindex != -1)
		attachedAttributes[oldindex] = newattrib;
	else
		attachedAttributes.push_back(newattrib);
}

bool Mesh::detachAttribute(const std::string &name)
{
	int index = getAttachedAttributeIndex(name);
	if (index == -1)
		return false;

	attachedAttributes.erase(attachedAttributes.begin() + index);

	if (vertexBuffer.get() && vertexBuffer->getDataMemberIndex(name) != -1)
		attachAttribute(name, vertexBuffer, nullptr, name);

	return true;
}

const std::vector<Mesh::BufferAttribute> &Mesh::getAttachedAttributes() const
{
	return attachedAttributes;
}

void *Mesh::getVertexData() const
{
	return vertexData;
}

void Mesh::setVertexDataModified(size_t offset, size_t size)
{
	if (vertexData != nullptr)
		modifiedVertexData.encapsulate(offset, size);
}

void Mesh::flush()
{
	if (vertexBuffer.get() && vertexData != nullptr && modifiedVertexData.isValid())
	{
		if (vertexBuffer->getUsage() == BUFFERUSAGE_STREAM)
		{
			vertexBuffer->fill(0, vertexBuffer->getSize(), vertexData);
		}
		else
		{
			size_t offset = modifiedVertexData.getOffset();
			size_t size = modifiedVertexData.getSize();
			vertexBuffer->fill(offset, size, vertexData + offset);
		}

		modifiedVertexData.invalidate();
	}

	if (indexDataModified && indexData != nullptr && indexBuffer != nullptr)
	{
		indexBuffer->fill(0, indexBuffer->getSize(), indexData);
		indexDataModified = false;
	}
}

/**
 * Copies index data from a vector to a mapped index buffer.
 **/
template <typename T>
static void copyToIndexBuffer(const std::vector<uint32> &indices, void *data, size_t maxval)
{
	T *elems = (T *) data;

	for (size_t i = 0; i < indices.size(); i++)
	{
		if (indices[i] >= maxval)
			throw love::Exception("Invalid vertex map value: %d", indices[i] + 1);

		elems[i] = (T) indices[i];
	}
}

void Mesh::setVertexMap(const std::vector<uint32> &map)
{
	size_t maxval = getVertexCount();

	IndexDataType datatype = getIndexDataTypeFromMax(maxval);
	DataFormat dataformat = getIndexDataFormat(datatype);

	// Calculate the size in bytes of the index buffer data.
	size_t size = map.size() * getIndexDataSize(datatype);

	if (indexBuffer.get() == nullptr || size > indexBuffer->getSize() || indexBuffer->getDataMember(0).decl.format != dataformat)
	{
		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
		auto usage = vertexBuffer.get() ? vertexBuffer->getUsage() : BUFFERUSAGE_DYNAMIC;
		Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, usage);
		indexBuffer.set(gfx->newBuffer(settings, dataformat, nullptr, size, 0), Acquire::NORETAIN);
	}

	useIndexBuffer = true;
	indexCount = map.size();

	if (!indexBuffer || indexCount == 0)
		return;

	// Fill the buffer with the index values from the vector.
	switch (datatype)
	{
	case INDEX_UINT16:
		copyToIndexBuffer<uint16>(map, indexData, maxval);
		break;
	case INDEX_UINT32:
	default:
		copyToIndexBuffer<uint32>(map, indexData, maxval);
		break;
	}

	indexDataType = datatype;
}

void Mesh::setVertexMap(IndexDataType datatype, const void *data, size_t datasize)
{
	DataFormat dataformat = getIndexDataFormat(datatype);

	if (indexBuffer.get() == nullptr || datasize > indexBuffer->getSize() || indexBuffer->getDataMember(0).decl.format != dataformat)
	{
		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
		auto usage = vertexBuffer.get() ? vertexBuffer->getUsage() : BUFFERUSAGE_DYNAMIC;
		Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, usage);
		indexBuffer.set(gfx->newBuffer(settings, dataformat, nullptr, datasize, 0), Acquire::NORETAIN);
	}

	indexCount = datasize / getIndexDataSize(datatype);

	if (!indexBuffer || indexCount == 0)
		return;

	Buffer::Mapper ibomap(*indexBuffer);
	memcpy(ibomap.data, data, datasize);

	useIndexBuffer = true;
	indexDataType = datatype;
}

void Mesh::setVertexMap()
{
	useIndexBuffer = false;
}

/**
 * Copies index data from a mapped buffer to a vector.
 **/
template <typename T>
static void copyFromIndexBuffer(void *buffer, size_t count, std::vector<uint32> &indices)
{
	T *elems = (T *) buffer;
	for (size_t i = 0; i < count; i++)
		indices.push_back((uint32) elems[i]);
}

bool Mesh::getVertexMap(std::vector<uint32> &map) const
{
	if (!useIndexBuffer)
		return false;

	map.clear();
	map.reserve(indexCount);

	if (indexData == nullptr || indexCount == 0)
		return true;

	// Fill the vector from the buffer.
	switch (indexDataType)
	{
	case INDEX_UINT16:
		copyFromIndexBuffer<uint16>(indexData, indexCount, map);
		break;
	case INDEX_UINT32:
	default:
		copyFromIndexBuffer<uint32>(indexData, indexCount, map);
		break;
	}

	return true;
}

void Mesh::setIndexBuffer(Buffer *buffer)
{
	// Buffer constructor does the rest of the validation for index buffers
	// (data member formats, etc.)
	if (buffer != nullptr && (buffer->getTypeFlags() & Buffer::TYPEFLAG_INDEX) == 0)
		throw love::Exception("setIndexBuffer requires a Buffer created as an index buffer.");

	indexBuffer.set(buffer);
	useIndexBuffer = buffer != nullptr;
	indexCount = buffer != nullptr ? buffer->getArrayLength() : 0;

	if (buffer != nullptr)
		indexDataType = getIndexDataType(buffer->getDataMember(0).decl.format);

	if (indexData != nullptr)
	{
		free(indexData);
		indexData = nullptr;
	}
}

Buffer *Mesh::getIndexBuffer() const
{
	return indexBuffer;
}

size_t Mesh::getIndexCount() const
{
	return indexCount;
}

void Mesh::setTexture(Texture *tex)
{
	texture.set(tex);
}

void Mesh::setTexture()
{
	texture.set(nullptr);
}

Texture *Mesh::getTexture() const
{
	return texture.get();
}

void Mesh::setDrawMode(PrimitiveType mode)
{
	primitiveType = mode;
}

PrimitiveType Mesh::getDrawMode() const
{
	return primitiveType;
}

void Mesh::setDrawRange(int start, int count)
{
	if (start < 0 || count <= 0)
		throw love::Exception("Invalid draw range.");

	rangeStart = start;
	rangeCount = count;
}

void Mesh::setDrawRange()
{
	rangeStart = rangeCount = -1;
}

bool Mesh::getDrawRange(int &start, int &count) const
{
	if (rangeStart < 0 || rangeCount <= 0)
		return false;

	start = rangeStart;
	count = rangeCount;
	return true;
}

void Mesh::draw(Graphics *gfx, const love::Matrix4 &m)
{
	drawInstanced(gfx, m, 1);
}

void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount)
{
	if (vertexCount <= 0 || instancecount <= 0)
		return;

	if (instancecount > 1 && !gfx->getCapabilities().features[Graphics::FEATURE_INSTANCING])
		throw love::Exception("Instancing is not supported on this system.");

	gfx->flushBatchedDraws();

	flush();

	if (Shader::isDefaultActive())
		Shader::attachDefault(Shader::STANDARD_DEFAULT);

	if (Shader::current && texture.get())
		Shader::current->checkMainTexture(texture);

	VertexAttributes attributes;
	BufferBindings buffers;

	int activebuffers = 0;

	for (const auto &attrib : attachedAttributes)
	{
		if (!attrib.enabled)
			continue;

		Buffer *buffer = attrib.buffer.get();
		int attributeindex = -1;

		// If the attribute is one of the LOVE-defined ones, use the constant
		// attribute index for it, otherwise query the index from the shader.
		BuiltinVertexAttribute builtinattrib;
		if (getConstant(attrib.name.c_str(), builtinattrib))
			attributeindex = (int) builtinattrib;
		else if (Shader::current)
			attributeindex = Shader::current->getVertexAttributeIndex(attrib.name);

		if (attributeindex >= 0)
		{
			if (attrib.mesh.get())
				attrib.mesh->flush();

			const auto &member = buffer->getDataMember(attrib.indexInBuffer);

			uint16 offset = (uint16) member.offset;
			uint16 stride = (uint16) buffer->getArrayStride();

			attributes.set(attributeindex, member.decl.format, offset, activebuffers);
			attributes.setBufferLayout(activebuffers, stride, attrib.step);

			// TODO: Ideally we want to reuse buffers with the same stride+step.
			buffers.set(activebuffers, buffer, 0);
			activebuffers++;
		}
	}

	// Not supported on all platforms or GL versions, I believe.
	if (!attributes.isEnabled(ATTRIB_POS))
		throw love::Exception("Mesh must have an enabled VertexPosition attribute to be drawn.");

	Graphics::TempTransform transform(gfx, m);

	if (useIndexBuffer && indexBuffer != nullptr && indexCount > 0)
	{
		Graphics::DrawIndexedCommand cmd(&attributes, &buffers, indexBuffer);

		cmd.primitiveType = primitiveType;
		cmd.indexType = indexDataType;
		cmd.instanceCount = instancecount;
		cmd.texture = texture;
		cmd.cullMode = gfx->getMeshCullMode();

		int start = std::min(std::max(0, rangeStart), (int) indexCount - 1);
		cmd.indexBufferOffset = start * getIndexDataSize(indexDataType);

		cmd.indexCount = (int) indexCount;
		if (rangeCount > 0)
			cmd.indexCount = std::min(cmd.indexCount, rangeCount);

		cmd.indexCount = std::min(cmd.indexCount, (int) indexCount - start);

		if (cmd.indexCount > 0)
			gfx->draw(cmd);
	}
	else if (vertexCount > 0)
	{
		Graphics::DrawCommand cmd(&attributes, &buffers);

		cmd.primitiveType = primitiveType;
		cmd.vertexStart = std::min(std::max(0, rangeStart), (int) vertexCount - 1);

		cmd.vertexCount = (int) vertexCount;
		if (rangeCount > 0)
			cmd.vertexCount = std::min(cmd.vertexCount, rangeCount);

		cmd.vertexCount = std::min(cmd.vertexCount, (int) vertexCount - cmd.vertexStart);
		cmd.instanceCount = instancecount;
		cmd.texture = texture;
		cmd.cullMode = gfx->getMeshCullMode();

		if (cmd.vertexCount > 0)
			gfx->draw(cmd);
	}
}

} // graphics
} // love
