using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public static class MeshUtility
{
	// Clockwise
	private static readonly Vector3[] PLANE_VERTICES = new Vector3[]
	{
		new Vector3(-0.5f, 0, 0.5f), // 0- TL
		new Vector3(0.5f, 0, 0.5f), //  1- TR
		new Vector3(0.5f, 0, -0.5f), // 2- BR
		new Vector3(-0.5f, 0, -0.5f) // 3- BL
	};
	
	// Clockwise
	private static readonly Vector2[] PLANE_UV = new Vector2[]
	{
		new Vector2(0, 1), // 0- TL
		new Vector2(1, 1), // 1- TR
		new Vector2(1, 0), // 2- BR
		new Vector2(0, 0) //  3- BL
	};
	
	// Clockwise
	private static readonly int[] PLANE_INDICES = new int[] { 0, 1, 3, 3, 1, 2 };
	
	private static readonly Material DEFAULT_MATERIAL = new Material(Shader.Find ("Diffuse"));
	
	public enum VerticalAnchor : short
	{
		Bottom = -1,
		Center = 0,
		Top = 1,
	}
	
	public enum HorizontalAnchor : short
	{
		Left = -1,
		Center = 0,
		Right = 1
	}
	
	public static GameObject CreatePlane(HorizontalAnchor hAnchor, VerticalAnchor vAnchor, bool addCollider)
	{ // Cria um plano com normal = Vector3.up
		GameObject plane = new GameObject("Plane");
		Vector3 anchorOffset = new Vector3((short)hAnchor * 0.5f, 0, (short)vAnchor * 0.5f);
		
		MeshFilter meshFilter = plane.AddComponent<MeshFilter>();
		MeshRenderer renderer = plane.AddComponent<MeshRenderer>();

		Mesh mesh = new Mesh()
		{
			vertices = PLANE_VERTICES.Select((vertex) => vertex - anchorOffset).ToArray(),
			uv = PLANE_UV,
			triangles = PLANE_INDICES,
			normals = IEnumerableExtensions.Repeat(Vector3.up, PLANE_VERTICES.Length).ToArray()
		};
		mesh.Optimize();
		mesh.RecalculateBounds();

		meshFilter.sharedMesh = mesh;
		renderer.sharedMaterial = DEFAULT_MATERIAL;
		
		if (addCollider)
		{
			plane.AddComponent<BoxCollider>();
		}
		
		return plane;
	}
	
	public static Mesh Flip(this Mesh mesh)
	{
		Util.AssertTrue(mesh != null, "'mesh' must be non-null");

		Mesh flipped = new Mesh()
		{
			vertices = mesh.vertices.Select(vertex => new Vector3(-vertex.x, vertex.y, vertex.z)).ToArray(),
			uv = mesh.uv,
			normals = mesh.normals.Select(normal => new Vector3(-normal.x, normal.y, normal.z)).ToArray(),
			triangles = mesh.triangles.Reverse().ToArray(),
		};
		flipped.Optimize();
		flipped.RecalculateBounds();
		return flipped;
	}
	
	public static IEnumerable<Bounds> GetBounds(IEnumerable<Transform> transforms)
	{
		foreach (Transform t in transforms)
		{
			MeshFilter mf = t.GetComponent<MeshFilter>();
			if (mf == null || mf.sharedMesh == null)
			{
				continue;
			}
			Bounds current = new Bounds(t.TransformPoint(mf.sharedMesh.vertices[0]), Vector3.zero);
			// foreach encapsula todos os pontos da malha em uma estrutura de bounds
			foreach (var v in mf.sharedMesh.vertices) { current.Encapsulate(t.TransformPoint(v)); }
			yield return current;
		}
	}
	
	public static Vector3 GetSize(this GameObject obj, bool recursive, bool includeInactive)
	{
		Util.AssertTrue(obj != null, "'obj' must be non-null");
		
		IEnumerable<Transform> transforms;
		if (recursive)
		{
			transforms = obj.GetComponentsInChildren<Transform>(includeInactive);
		}
		else
		{
			transforms = IEnumerableExtensions.AsEnumerable(obj.transform);
		}
		
		Bounds[] allBounds = GetBounds(transforms).ToArray();
		Bounds current = allBounds[0];
		for (int i = 1; i < allBounds.Length; ++i) { current.Encapsulate(allBounds[i]); }
		
		return current.size;
	}
	
	public static void CombineChildren(GameObject parent, bool ignoreSelf, bool includeInactive)
	{
		Util.AssertTrue(parent != null, "'parent' argument must be non-null");
		Util.AssertTrue(parent.GetComponentsInChildren<MeshRenderer>(includeInactive)
			.Select((mr) => mr.sharedMaterial)
			.Where((material) => material != null)
			.Distinct().Count() == 1, "Combined meshes must share the same material");
		
		IEnumerable<MeshFilter> mfs = parent.GetComponentsInChildren<MeshFilter>(includeInactive); // Todos os MeshFilter
		if (ignoreSelf)
		{
			MeshFilter parentFilter = parent.GetComponent<MeshFilter>();
			if (parentFilter != null)
			{
				mfs = mfs.Except(parentFilter);
			}
		}
		
		CombineInstance[] combine = mfs
			.Select((filter) => new CombineInstance() { mesh = filter.sharedMesh, transform = filter.transform.localToWorldMatrix })
			.ToArray();

		MeshFilter meshFilter = parent.GetComponent<MeshFilter>();
		if (meshFilter == null)
		{
			meshFilter = parent.AddComponent<MeshFilter>();
			meshFilter.sharedMesh = new Mesh();
		}
		
		MeshRenderer renderer = parent.GetComponent<MeshRenderer>();
		if (renderer == null)
		{
			renderer = parent.AddComponent<MeshRenderer>();
		}
		
		Mesh mesh = meshFilter.sharedMesh;
		mesh.Clear();
		mesh.CombineMeshes(combine);
		mesh.Optimize();
		mesh.RecalculateBounds();
		
		IEnumerable<MeshRenderer> mrs = parent.GetComponentsInChildren<MeshRenderer>(includeInactive);
		if (ignoreSelf)
		{
			MeshRenderer thisRenderer = parent.GetComponent<MeshRenderer>();
			if (thisRenderer != null)
			{
				mrs.Except(thisRenderer);
			}
		}
		
		renderer.sharedMaterial = mrs
			.Select(mr => mr.sharedMaterial)
			.Where(material => material != null)
			.Distinct().Single();
		
		bool enabled = parent.active;
		parent.SetActiveRecursively(false);
		parent.active = enabled;
	}
	
}
