﻿using System.Collections.Generic;
using System.Linq;
using Gamelogic;
using Gamelogic.Grids;
using UnityEngine;

namespace Gamelogic.Grids.Examples
{
	public class MobiusStripHexTest : GLMonoBehaviour
	{
		private static readonly float Sqrt3 = Mathf.Sqrt(3);

		private static readonly Vector2[] VertexDirections =
		{
			new Vector3(0, 1f/2),
			new Vector3(Sqrt3/4, 1f/4),
			new Vector3(Sqrt3/4, -1f/4),
			new Vector3(0, -1f/2),
			new Vector3(-Sqrt3/4, -1f/4),
			new Vector3(-Sqrt3/4, 1f/4),
			Vector3.zero
		};

		private static readonly Vector2[] UVDirections =
			VertexDirections.Select(v => new Vector2(v.x, v.y) + Vector2.one*0.5f).ToArray();

		private static readonly int[] Triangles =
		{
			6, 0, 1,
			6, 1, 2,
			6, 2, 3,
			6, 3, 4,
			6, 4, 5,
			6, 5, 0
		};

		//Tweakables
		//Assmes the texture you have on you renderer is divided 
		//in 5x4 rectangles. A texture like this: http://www.andrethiel.de/ContentImages/2D/Fullsize/Textures_Terrain.jpg
		private const int TextureGridWidth = 5;
		private const int TextureGridHeight = 4;

		private const float TextureCellWidth = 1f/TextureGridWidth;
		private const float TextureCellHeight = 1f/TextureGridHeight;

		public int halfTwistCount = 1;
		public int columnCount = 50;
		public int rowCount = 20;
		public float bigRadius = 20;
		public float smallRadius = 40;

		private float width;
		private float height;

		public void Start()
		{
			GenerateMesh();
		}

		public void GenerateMesh()
		{
			var grid = PointyHexGrid<int>.Parallelogram(columnCount, rowCount);

			foreach (var point in grid)
			{
				grid[point] = point.GetColor3_7();
			}

			var dimensions = new Vector2(69, 80);

			width = dimensions.x*columnCount;
			height = (3*rowCount*dimensions.y/4 + dimensions.y/4);
			var mapWidth = dimensions.x*(columnCount + (rowCount - 1)/2.0f);
			bigRadius = (width/2)/(2*Mathf.PI);
			smallRadius = height/2;

			var map = new PointyHexMap(dimensions)
				.WithWindow(new Rect(0, 0, mapWidth, height))
				.Stretch(grid).TranslateY(40);

			GetComponent<MeshFilter>().mesh = GenerateMesh(grid, map, dimensions);


		}

		private Mesh GenerateMesh(IGrid<int, PointyHexPoint> grid, IMap<PointyHexPoint> map, Vector2 dimensions)
		{
			var mesh = new Mesh
			{
				vertices = MakeVertices(grid, map, dimensions),
				uv = MakeUVs(grid),
				triangles = MakeTriangles(grid),
				colors = MakeColors(grid)
			};

			mesh.Optimize();
			mesh.RecalculateBounds();
			mesh.RecalculateNormals();

			return mesh;
		}

		private static Color[] MakeColors(IGrid<int, PointyHexPoint> grid)
		{
			return grid.SelectMany(p => Enumerable.Repeat(ExampleUtils.Colors[grid[p]], 7)).ToArray();
		}

		private static int[] MakeTriangles(IEnumerable<PointyHexPoint> grid)
		{
			var vertexIndices = Enumerable.Range(0, grid.Count());

			return vertexIndices
				.SelectMany(i => Triangles.Select(j => i*7 + j))
				.ToArray();
		}

		private static Vector2[] MakeUVs(IGrid<int, PointyHexPoint> grid)
		{
			return grid
				.SelectMany(p => UVDirections.Select(uv => CalcUV(uv, grid[p])))
				.ToArray();
		}

		private Vector3[] MakeVertices(IEnumerable<PointyHexPoint> grid, IMap<PointyHexPoint> map, Vector2 dimensions)
		{
			return grid
				.SelectMany(p => VertexDirections
					.Select(v => v*dimensions.y + map[p])
					.Select<Vector2, Vector3>(MapMobius))
				.ToArray();
		}

		private static Vector2 CalcUV(Vector2 fullUV, int textureIndex)
		{
			int textureIndexX = textureIndex%TextureGridWidth;
			int textureIndexY = textureIndex/TextureGridHeight;

			float u = fullUV.x/TextureGridWidth + textureIndexX*TextureCellWidth;
			float v = fullUV.y/TextureGridHeight + textureIndexY*TextureCellHeight;

			return new Vector2(u, v);
		}

		private Vector3 MapMobius(Vector2 v2)
		{
			var v = new Vector2(v2.x/width, v2.y/height);
			float angle = v.x*Mathf.PI*2;

			float smallCircleX1 = smallRadius*Mathf.Cos(halfTwistCount*angle/2);

			float x1 = (bigRadius + smallCircleX1)*Mathf.Cos(angle*2);
			float y1 = smallRadius*Mathf.Sin(halfTwistCount*angle/2); //smallCircleY
			float z1 = (bigRadius + smallCircleX1)*Mathf.Sin(angle*2);

			float smallCircleX2 = -smallRadius*Mathf.Cos(halfTwistCount*angle/2);

			float x2 = (bigRadius + smallCircleX2)*Mathf.Cos(angle*2);
			float y2 = -smallRadius*Mathf.Sin(halfTwistCount*angle/2); //smallCircleY
			float z2 = (bigRadius + smallCircleX2)*Mathf.Sin(angle*2);


			var position1 = new Vector3(x1, y1, z1);
			var position2 = new Vector3(x2, y2, z2);

			return Vector3.Lerp(position1, position2, v.y);
		}
	}
}