/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#include "GeometryFactory.hpp"
#include "Vertex.hpp"

#define _USE_MATH_DEFINES
#include <math.h>
#include <assert.h>

namespace OpenVulkano::Scene
{
	Geometry GeometryFactory::MakeCube(float x, float y, float z, const Math::Vector4f& color)
	{
		Geometry result;
		const int indexCount = 36;
		result.Init(24, indexCount);
		uint32_t indices[indexCount] =
		{
			0, 1, 2, 0, 2, 3,		// front face index data
			4, 5, 6, 4, 6, 7,		// back face index data
			8, 9, 10, 8, 10, 11,	// top face index data
			12, 13, 14, 12, 14, 15,	// bottom face index data
			16, 17, 18, 16, 18, 19,	// left face index data
			20, 21, 22, 20, 22, 23	// right face index data
		};
		result.SetIndices(indices, result.indexCount);

		x *= 0.5f; y *= 0.5f; z *= 0.5f;

		uint32_t i = 0;
		// front face vertex data
		result.vertices[i++].Set(+x, -y, +z, +0, +0, +1, +0, +1);
		result.vertices[i++].Set(+x, +y, +z, +0, +0, +1, +0, +0);
		result.vertices[i++].Set(-x, +y, +z, +0, +0, +1, +1, +0);
		result.vertices[i++].Set(-x, -y, +z, +0, +0, +1, +1, +1);
		// back face vertex data
		result.vertices[i++].Set(+x, -y, -z, +0, +0, -1, +1, +1);
		result.vertices[i++].Set(-x, -y, -z, +0, +0, -1, +0, +1);
		result.vertices[i++].Set(-x, +y, -z, +0, +0, -1, +0, +0);
		result.vertices[i++].Set(+x, +y, -z, +0, +0, -1, +1, +0);
		// top face vertex data
		result.vertices[i++].Set(+x, +y, +z, +0, -1, +0, +0, +1);
		result.vertices[i++].Set(+x, +y, -z, +0, -1, +0, +0, +0);
		result.vertices[i++].Set(-x, +y, -z, +0, -1, +0, +1, +0);
		result.vertices[i++].Set(-x, +y, +z, +0, -1, +0, +1, +1);
		// bottom face vertex data
		result.vertices[i++].Set(+x, -y, +z, +0, +1, +0, +1, +1);
		result.vertices[i++].Set(-x, -y, +z, +0, +1, +0, +0, +1);
		result.vertices[i++].Set(-x, -y, -z, +0, +1, +0, +0, +0);
		result.vertices[i++].Set(+x, -y, -z, +0, +1, +0, +1, +0);
		// Fill in the left face vertex data
		result.vertices[i++].Set(-x, -y, -z, -1, +0, +0, +1, +1);
		result.vertices[i++].Set(-x, -y, +z, -1, +0, +0, +0, +1);
		result.vertices[i++].Set(-x, +y, +z, -1, +0, +0, +0, +0);
		result.vertices[i++].Set(-x, +y, -z, -1, +0, +0, +1, +0);
		// Fill in the right face vertex data
		result.vertices[i++].Set(+x, -y, +z, +1, +0, +0, +1, +1);
		result.vertices[i++].Set(+x, -y, -z, +1, +0, +0, +0, +1);
		result.vertices[i++].Set(+x, +y, -z, +1, +0, +0, +0, +0);
		result.vertices[i].Set(+x, +y, +z, +1, +0, +0, +1, +0);

		for(i = 0; i < result.vertexCount; i++)
		{
			result.vertices[i].color = color;
		}
		return result;
	}

	Geometry GeometryFactory::MakePlane(float width, float height, const Math::Vector4f& color)
	{
		Geometry result;
		result.Init(4, 6);
		uint32_t indices[] = { 0, 2, 1, 0, 3, 2 };
		result.SetIndices(indices, result.indexCount);

		width *= 0.5f; height *= 0.5f;

		result.vertices[0].Set(-width, -height, 0, 0, 0, 1, 0, 1);
		result.vertices[1].Set(-width, height, 0, 0, 0, 1, 0, 0);
		result.vertices[2].Set(width, height, 0, 0, 0, 1, 1, 0);
		result.vertices[3].Set(width, -height, 0, 0, 0, 1, 1, 1);

		for (uint32_t i = 0; i < result.vertexCount; i++)
		{
			result.vertices[i].color = color;
		}

		return result;
	}

	Geometry GeometryFactory::MakeSphere(float radius, uint32_t segments, uint32_t rings, const Math::Vector4f& color)
	{
		Geometry result;
		uint32_t vertexCount = (rings + 1) * (segments + 1);
		uint32_t indexCount = 6 * rings * segments;
		result.Init(vertexCount, indexCount);

		uint32_t i = 0;
		for (uint32_t ring = 0; ring <= rings; ++ring)
		{
			float theta = ring * M_PI / rings;
			float sinTheta = sin(theta);
			float cosTheta = cos(theta);

			for (uint32_t segment = 0; segment <= segments; ++segment)
			{
				float phi = segment * 2 * M_PI / segments;
				float sinPhi = sin(phi);
				float cosPhi = cos(phi);

				float x = cosPhi * sinTheta;
				float y = cosTheta;
				float z = sinPhi * sinTheta;
				float u = 1 - (float(segment) / segments);
				float v = float(ring) / rings;

				result.vertices[i].Set(radius * x, radius * y, radius * z, x, y, z, u, v);
				result.vertices[i].color = color;
				++i;
			}
		}

		i = 0;
		uint32_t* indices = new uint32_t[indexCount];
		for (uint32_t ring = 0; ring < rings; ++ring)
		{
			for (uint32_t segment = 0; segment < segments; ++segment)
			{
				uint32_t first = (ring * (segments + 1)) + segment;
				uint32_t second = first + segments + 1;

				indices[i++] = first;
				indices[i++] = first + 1;
				indices[i++] = second;

				indices[i++] = second;
				indices[i++] = first + 1;
				indices[i++] = second + 1;
			}
		}
		result.SetIndices(indices, indexCount);
		delete[] indices;

		return result;
	}

	Geometry GeometryFactory::MakeHemisphere(float radius, uint32_t segments, uint32_t rings, const Math::Vector4f& color)
	{
		Geometry result;
		uint32_t vertexCount = (rings + 1) * (segments + 1);
		uint32_t indexCount = 6 * rings * segments;
		result.Init(vertexCount, indexCount);

		uint32_t i = 0;
		for (uint32_t ring = 0; ring <= rings; ++ring)
		{
			float theta = (ring * M_PI_2) / rings;
			float sinTheta = sin(theta);
			float cosTheta = cos(theta);

			for (uint32_t segment = 0; segment <= segments; ++segment)
			{
				float phi = segment * 2 * M_PI / segments;
				float sinPhi = sin(phi);
				float cosPhi = cos(phi);

				float x = cosPhi * sinTheta;
				float y = cosTheta;
				float z = sinPhi * sinTheta;
				float u = 1 - (float(segment) / segments);
				float v = float(ring) / rings;

				result.vertices[i].Set(radius * x, radius * y, radius * z, x, y, z, u, v);
				result.vertices[i].color = color;
				++i;
			}
		}

		i = 0;
		uint32_t* indices = new uint32_t[indexCount];
		for (uint32_t ring = 0; ring < rings; ++ring)
		{
			for (uint32_t segment = 0; segment < segments; ++segment)
			{
				uint32_t first = (ring * (segments + 1)) + segment;
				uint32_t second = first + segments + 1;

				indices[i++] = first;
				indices[i++] = first + 1;
				indices[i++] = second;

				indices[i++] = second;
				indices[i++] = first + 1;
				indices[i++] = second + 1;
			}
		}
		result.SetIndices(indices, indexCount);
		delete[] indices;

		return result;
	}

	Geometry GeometryFactory::MakeTriangle(const Math::Vector3f& p1, const Math::Vector3f& p2, const Math::Vector3f& p3, const Math::Vector4f& color)
	{
		Geometry result;
		result.Init(3, 3);
		uint32_t indices[] = { 0, 1, 2 };
		result.SetIndices(indices, result.indexCount);

		result.vertices[0].Set(p1.x, p1.y, p1.z, 0, 0, 1, 0, 0);
		result.vertices[1].Set(p2.x, p2.y, p2.z, 0, 0, 1, 1, 0);
		result.vertices[2].Set(p3.x, p3.y, p3.z, 0, 0, 1, 0, 1);

		for (uint32_t i = 0; i < result.vertexCount; i++)
		{
			result.vertices[i].color = color;
		}

		return result;
	}

	Geometry GeometryFactory::MakeCylinder(float radius, float height, uint32_t segments, const Math::Vector4f& color)
	{
		Geometry result;
		uint32_t vertexCount = 2 * (segments + 1);
		uint32_t indexCount = 12 * segments;
		result.Init(vertexCount, indexCount);

		float halfHeight = height * 0.5f;
		uint32_t i = 0;
		for (uint32_t segment = 0; segment <= segments; ++segment)
		{
			float theta = segment * 2 * M_PI / segments;
			float x = radius * cos(theta);
			float z = radius * sin(theta);
			float u = float(segment) / segments;

			result.vertices[i].Set(x, -halfHeight, z, x, 0, z, u, 0);
			result.vertices[i++].color = color;
			result.vertices[i].Set(x, halfHeight, z, x, 0, z, u, 1);
			result.vertices[i++].color = color;
		}

		i = 0;
		uint32_t* indices = new uint32_t[indexCount];
		for (uint32_t segment = 0; segment < segments; ++segment)
		{
			uint32_t first = segment * 2;
			uint32_t second = first + 1;

			indices[i++] = first;
			indices[i++] = second;
			indices[i++] = first + 2;

			indices[i++] = second;
			indices[i++] = second + 2;
			indices[i++] = first + 2;

			indices[i++] = first;
			indices[i++] = first + 2;
			indices[i++] = second + 2;

			indices[i++] = second;
			indices[i++] = first;
			indices[i++] = second + 2;
		}
		result.SetIndices(indices, indexCount);
		delete[] indices;

		return result;
	}

	Geometry GeometryFactory::MakePyramid(float baseLength, float height, const Math::Vector4f& color)
	{
		Geometry result;
		result.Init(5, 18);
		uint32_t indices[] = {
			// Base
			1, 2, 3,
			1, 3, 4,
			// Sides
			0, 1, 2,
			0, 2, 3,
			0, 3, 4,
			0, 4, 1
		};
		result.SetIndices(indices, result.indexCount);

		float halfBase = baseLength * 0.5f;

		// Top vertex
		result.vertices[0].Set(0, height, 0, 0, 1, 0, 0.5f, 1);
		// Base vertices
		result.vertices[1].Set(-halfBase, 0, halfBase, 0, -1, 0, 0, 0);
		result.vertices[2].Set(halfBase, 0, halfBase, 0, -1, 0, 1, 0);
		result.vertices[3].Set(halfBase, 0, -halfBase, 0, -1, 0, 1, 1);
		result.vertices[4].Set(-halfBase, 0, -halfBase, 0, -1, 0, 0, 1);

		for (uint32_t i = 0; i < result.vertexCount; i++)
		{
			result.vertices[i].color = color;
		}

		return result;
	}
	
	Geometry GeometryFactory::MakeArchStrip(float radius, float width, float endRadius, int segments, const Math::Vector4f& color, float endVCoord, bool indexBuffer)
	{
		Geometry result;
		segments = std::max(2, segments);
		result.Init(2 * segments, indexBuffer ? 6 * segments : 0);
		const float segmentAngle = endRadius / static_cast<float>(segments - 1);
		width /= 2;
		const float segmentV = endVCoord / (segments - 1);
		float segV = 0;
		for(int i = 0, seg = 0; seg < segments; seg++)
		{
			float angle = seg * segmentAngle;
			float z = std::cos(angle) * radius;
			float y = std::sin(angle) * radius;
			
			//TODO normals
			result.vertices[i].color = color;
			result.vertices[i++].Set(width, y, z, +0, +0, +1, +1, +segV);
			result.vertices[i].color = color;
			result.vertices[i++].Set(-width, y, z, +0, +0, +1, +0, +segV);
			segV += segmentV;
		}
		if (indexBuffer)
		{
			uint32_t* indices = new uint32_t[result.GetIndexCount()];
			for (uint32_t i = 0, segment = 0; segment < segments; ++segment)
			{
				uint32_t first = segment * 2;
				uint32_t second = first + 1;

				indices[i++] = first;
				indices[i++] = second;
				indices[i++] = first + 2;

				indices[i++] = first + 2;
				indices[i++] = second;
				indices[i++] = second + 2;
			}
			result.SetIndices(indices, result.GetIndexCount());
		}
		
		return result;
	}
}
