using UnityEngine;
using System.Collections;

public class AtmosphereDisc : MonoBehaviour {

	const int details = 64;

	[SerializeField] public Gradient atmosphereColor;
	[SerializeField] public float radius = 6f;
	[SerializeField] public float outerRadius = 6.6f;
	[SerializeField] public Transform targetCameraTransform = null;

	const int textureSize = 16;
	
	MeshFilter meshFilter;						
	MeshRenderer meshRenderer;
	Transform objectTransform;
	float distanceToCamera = 0;

	void Awake () {
		meshFilter   = Utils.GetOrCreateComponent<MeshFilter>(gameObject);
		meshRenderer = Utils.GetOrCreateComponent<MeshRenderer>(gameObject);
		objectTransform = transform;
	}
	
	void Start () {
		meshFilter.mesh = CreateDiscMesh(radius, outerRadius);
		meshRenderer.sharedMaterial = CreateMaterial ();
		if (targetCameraTransform == null) {
			targetCameraTransform = Camera.allCameras[0].transform;
		}
	}

	Vector3[] normals;
	Vector3[] vertexMeshComponent;

	void RefreshAtmosphericCone(float innerRadius, float outerRadius) {
		if (outerRadius < distanceToCamera) {
//			float angularAspect = Mathf.Acos(innerRadius / distanceToCamera);
//			float correctionAngle = Mathf.PI * 0.5f - angularAspect;
			float correctionAngle = Mathf.Acos(outerRadius / distanceToCamera);
			float aspectZ = -Mathf.Cos(correctionAngle);
			float aspectR = Mathf.Sin(correctionAngle);
			for (int i = 0; i <= details; i ++) {
				float angle = (360f / (details / (float)i)) / 180f * Mathf.PI;
				normals [i * 2] = (new Vector3 (Mathf.Cos (angle) * aspectR, Mathf.Sin (angle) * aspectR, aspectZ)).normalized; 
				normals [i * 2 + 1] = (new Vector3 (Mathf.Cos (angle) * aspectR, Mathf.Sin (angle) * aspectR, aspectZ)).normalized; 
			}
			for (int i = 0; i <= details; i++) {
				vertexMeshComponent [i * 2] = normals [i * 2] * innerRadius;
				vertexMeshComponent [i * 2 + 1] = normals [i * 2 + 1] * outerRadius;
			}
			meshFilter.mesh.normals = normals;
			meshFilter.mesh.vertices = vertexMeshComponent;
		}
	}

	Mesh CreateDiscMesh(float innerRadius, float outerRadius){
		int h = (details + 1) * 2;
		normals  = new Vector3[h];
     	vertexMeshComponent = new Vector3[h];
		Vector2[] textures = new Vector2[h];
		Color[] colorMeshComponent = new Color[h];
		int[] triangles    = new int[details * 6];
		for(int i = 0; i <= details; i ++){
			float angle = (360f /(details / (float)i)) / 180f * Mathf.PI;
			normals[i * 2    ] = (new Vector3( Mathf.Cos(angle), Mathf.Sin(angle), 0f)).normalized; 
			normals[i * 2 + 1] = (new Vector3( Mathf.Cos(angle), Mathf.Sin(angle), 0f)).normalized; 
		}
		for(int i = 0; i <= details; i++) {
			vertexMeshComponent[i * 2    ] = normals[i * 2    ] * innerRadius;
			vertexMeshComponent[i * 2 + 1] = normals[i * 2 + 1] * outerRadius;
		}
		for(int i = 0; i <= details; i++) {
			colorMeshComponent[i * 2    ] = new Color(1, 1, 1);
			colorMeshComponent[i * 2 + 1] = new Color(1, 1, 1);
			textures[i * 2    ] = new Vector2(1f * (float)i / (details), 0);
			textures[i * 2 + 1] = new Vector2(1f * (float)i / (details), 1f);
		}
		for(int i = 0; i < details; i++) {
			triangles[i * 6 + 0] = (i) * 2;// 0 0
			triangles[i * 6 + 1] = (i + 1) * 2;// 1 0
			triangles[i * 6 + 2] = (i) * 2 + 1;// 0 1
			triangles[i * 6 + 3] = (i) * 2 + 1;// 0 1
			triangles[i * 6 + 4] = (i + 1) * 2;// 1 0
			triangles[i * 6 + 5] = (i + 1) * 2 + 1;// 1 1
		}
		return new Mesh{ vertices = vertexMeshComponent, uv = textures, colors = colorMeshComponent, triangles = triangles, normals = normals };
	}

	Material CreateMaterial (){
		byte[] texData = new byte[textureSize * textureSize * 6];
		for (int i = 0; i < textureSize; i++) {
			for (int j = 0; j < textureSize; j++) {
				Color color = atmosphereColor.Evaluate((float)j / (float)(textureSize - 1));
				texData[(i + j * textureSize) * 4 + 0] = (byte)(color.r * 255f);
				texData[(i + j * textureSize) * 4 + 1] = (byte)(color.g * 255f);
				texData[(i + j * textureSize) * 4 + 2] = (byte)(color.b * 255f);
				texData[(i + j * textureSize) * 4 + 3] = (byte)(color.a * 255f);
			}
		}
		Texture2D texture = new Texture2D (textureSize, textureSize, TextureFormat.RGBA32, true);
		texture.LoadRawTextureData (texData);
		texture.Apply ();
		texture.filterMode = FilterMode.Trilinear;
		texture.wrapMode = TextureWrapMode.Clamp;
		Material material = new Material (Shader.Find ("Sprites/Diffuse"));
		material.mainTexture = texture;
		return material;
	}

	void Update () {
		if (targetCameraTransform != null) {
			objectTransform.rotation = targetCameraTransform.rotation;
			float distance = (objectTransform.position - targetCameraTransform.position).magnitude;
			if (distance != distanceToCamera) {
				distanceToCamera = distance;
				RefreshAtmosphericCone(radius, outerRadius);
			}
		}
	}
}
