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

public class Ball : MonoBehaviour
{
	public float radius = 0.1f;
	public bool cameraFollows = true;
	public bool controllable = true;
	public bool lightFollows = true;
	public int index = 0;

	const int BALL_DETAIL_H = 32;
	const int BALL_DETAIL_V = 17;

	//private Mesh ballMesh = null;
	private static Dictionary<float, Mesh> ballMeshes = new Dictionary<float, Mesh>();

	private static bool audioDone = false;
	private static AudioClip launchSound;
	private static AudioClip bounceSound;
	private static AudioClip boundarySound;
	private static AudioClip collideSound;
	private static AudioClip rollSound;

	private static AudioClip chargeSound;
	private static AudioClip chargeLaunchSound;

	private static AudioClip goodSound;
	private static AudioClip badSound;
	private static AudioClip verygoodSound;

	private static int counter=0;
	protected int counter_this;

	AudioSource audioSource;
	AudioSource rollSource;
	AudioSource jubilationSource;
	AudioSource chargeSource;

	public void Good()
	{
		jubilationSource.clip = goodSound;
		jubilationSource.Play();
	}

	public void Bad()
	{
		jubilationSource.clip = badSound;
		jubilationSource.Play();
	}

	public void VeryGood()
	{
		jubilationSource.clip = verygoodSound;
		jubilationSource.Play();
	}

	void Start ()
	{
		counter_this = counter++;
		last_side = (transform.position.y>0)?1:-1;

		if (!audioDone)
		{
			launchSound = Resources.Load<AudioClip>("sounds/launch");
			bounceSound = Resources.Load<AudioClip>("sounds/bounce");
			boundarySound = Resources.Load<AudioClip>("sounds/boundary");
			collideSound = Resources.Load<AudioClip>("sounds/collide");
			rollSound = Resources.Load<AudioClip>("sounds/roll");

			goodSound = Resources.Load<AudioClip>("sounds/good");
			badSound = Resources.Load<AudioClip>("sounds/bad");
			verygoodSound = Resources.Load<AudioClip>("sounds/very_good");

			chargeSound = Resources.Load<AudioClip>("sounds/charge_loop");
			chargeLaunchSound = Resources.Load<AudioClip>("sounds/charge_launch");

			audioDone = true;
		}

		Mesh mesh = null;

		if (!ballMeshes.TryGetValue(radius, out mesh))
		{
			int num_verts = (2*BALL_DETAIL_V+1)*(BALL_DETAIL_H+1);
			int num_tris = 4*BALL_DETAIL_H*BALL_DETAIL_V;
			
			//Debug.Log("Ball: " + num_verts + " vertices, " + num_tris + " tris");
			
			Vector3[] p = new Vector3[num_verts];
			Vector3[] n = new Vector3[num_verts];
			Vector2[] uv = new Vector2[num_verts];
			int[] t = new int[3*num_tris];

			for (int b=0; b<=BALL_DETAIL_H; b++)
			{
				float fb = ((float)b)/BALL_DETAIL_H;
				float phi = 2*Mathf.PI*fb;
				float cs_phi = Mathf.Cos(phi);
				float sn_phi = Mathf.Sin(phi);

				p[b].Set(cs_phi, 0, sn_phi);
				uv[b].Set(2*fb-1, 0.5f);

				for (int a=0; a<BALL_DETAIL_V; a++)
				{
					float fa = ((float)a+1)/BALL_DETAIL_V;
					//float theta = Mathf.Asin(fa);
					float theta = fa*0.5f*Mathf.PI*fa + (1-fa)*Mathf.Asin(fa);
					float cs_theta = Mathf.Cos(theta);
					float sn_theta = Mathf.Sin(theta);

					p[(BALL_DETAIL_H+1)*(1+2*a+0) + b].Set(cs_phi*cs_theta,  sn_theta, sn_phi*cs_theta);
					p[(BALL_DETAIL_H+1)*(1+2*a+1) + b].Set(cs_phi*cs_theta, -sn_theta, sn_phi*cs_theta);

					uv[(BALL_DETAIL_H+1)*(1+2*a+0) + b].Set (2*fb-1, 0.5f*(1+theta*2/Mathf.PI));
					uv[(BALL_DETAIL_H+1)*(1+2*a+1) + b].Set (2*fb-1, 0.5f*(1-theta*2/Mathf.PI));
				}
			}

			for (int b=0; b<BALL_DETAIL_H; b++)
			{
				int prev_mul_0 = 0;
				int prev_mul_1 = 0;
				for (int a=0; a<BALL_DETAIL_V; a++)
				{
					int next_mul_0 = 1+2*a+0;
					int next_mul_1 = 1+2*a+1;

					t[3*(4*b*BALL_DETAIL_V+4*a+0)+0] = (BALL_DETAIL_H+1)*prev_mul_0 + b+1;
					t[3*(4*b*BALL_DETAIL_V+4*a+0)+1] = (BALL_DETAIL_H+1)*prev_mul_0 + b;
					t[3*(4*b*BALL_DETAIL_V+4*a+0)+2] = (BALL_DETAIL_H+1)*next_mul_0 + b;

					t[3*(4*b*BALL_DETAIL_V+4*a+1)+0] = (BALL_DETAIL_H+1)*next_mul_0 + b;
					t[3*(4*b*BALL_DETAIL_V+4*a+1)+1] = (BALL_DETAIL_H+1)*next_mul_0 + b+1;
					t[3*(4*b*BALL_DETAIL_V+4*a+1)+2] = (BALL_DETAIL_H+1)*prev_mul_0 + b+1;

					t[3*(4*b*BALL_DETAIL_V+4*a+2)+0] = (BALL_DETAIL_H+1)*prev_mul_1 + b;
					t[3*(4*b*BALL_DETAIL_V+4*a+2)+1] = (BALL_DETAIL_H+1)*prev_mul_1 + b+1;
					t[3*(4*b*BALL_DETAIL_V+4*a+2)+2] = (BALL_DETAIL_H+1)*next_mul_1 + b;
					
					t[3*(4*b*BALL_DETAIL_V+4*a+3)+0] = (BALL_DETAIL_H+1)*next_mul_1 + b+1;
					t[3*(4*b*BALL_DETAIL_V+4*a+3)+1] = (BALL_DETAIL_H+1)*next_mul_1 + b;
					t[3*(4*b*BALL_DETAIL_V+4*a+3)+2] = (BALL_DETAIL_H+1)*prev_mul_1 + b+1;

					prev_mul_0 = next_mul_0;
					prev_mul_1 = next_mul_1;
				}
			}

			for (int j=0; j<num_verts; j++)
			{
				n[j] = p[j];
				p[j] *= radius;
			}

			mesh = new Mesh();
			mesh.vertices = p;
			mesh.normals = n;
			mesh.uv = uv;
			mesh.triangles = t;
			mesh.Optimize();

			ballMeshes.Add(radius, mesh);
		}

		audioSource = gameObject.AddComponent<AudioSource>();
		rollSource = gameObject.AddComponent<AudioSource>();
		chargeSource = gameObject.AddComponent<AudioSource>();
		jubilationSource = gameObject.AddComponent<AudioSource>();
		
		GetComponent<MeshFilter>().sharedMesh = mesh;

		GetComponent<SphereCollider>().radius = radius;
		GetComponent<SphereCollider>().material = Resources.Load<PhysicMaterial>("phys_materials/ball_physmat");

		if (index == 0)
		{
			GetComponent<MeshRenderer>().sharedMaterial = Resources.Load<Material>("materials/cueball_mat");
		}
		else
		{
			GetComponent<MeshRenderer>().material = new Material(Resources.Load<Material>("materials/ball_mat"));
			if (index>0) GetComponent<MeshRenderer>().material.mainTexture = Resources.Load<Texture2D>("materials/ball_"+index);
			else GetComponent<MeshRenderer>().material.mainTexture = Resources.Load<Texture2D>("materials/problem_ball");
		}

		mainCamera = GameObject.Find ("Main Camera");
		mainLight = GameObject.Find ("Main Light").GetComponent<Light>();
		rb = GetComponent<Rigidbody>();
		rb.solverIterationCount = 16;
	}

	private Rigidbody rb = null;
	private GameObject mainCamera = null;
	private Light mainLight = null;
	private float grav = 0;

	private float sideSpin = 0;
	private float topSpin = 0;

	private bool has_flipped = false;
	public bool hasFlipped
	{
		get
		{
			if (has_flipped)
			{
				has_flipped = false;
				return true;
			}
			else return false;
		}
	}
	private int last_side=1;

	void FixedUpdate()
	{
		grav += Mathf.Min(1, 1.5f*Time.fixedDeltaTime)*(25.0f*Mathf.Clamp(0.5f*transform.position.y,-1,1) - grav);
		rb.velocity = rb.velocity + (grav * Time.fixedDeltaTime)*Vector3.down;

		Vector3 pos = rb.position;

		if (pos.y > 1)
		{
			if (last_side < 0)
			{
				last_side = 1;
				has_flipped = true;
			}
		}
		else if (pos.y < -1)
		{
			if (last_side > 0)
			{
				last_side = -1;
				has_flipped = true;
			}
		}

		pos.y = 0;
		float r = pos.magnitude;
		if (r > WorldFrame.FRAME_RADIUS)
		{
			rb.velocity = rb.velocity - 2*(r - WorldFrame.FRAME_RADIUS)*Time.fixedDeltaTime*pos;
		}

		if (pokePower > 0)
		{
			float sideTurn = pokeSide*0.45f*Mathf.PI;
			float cs = Mathf.Cos(sideTurn);
			float sn = Mathf.Sin(sideTurn);
			Vector3 pokeRel = pokeHeight*Vector3.up - Mathf.Sqrt(1-pokeHeight*pokeHeight)*(new Vector3(cs*pokeDir.x - sn*pokeDir.z, 0, cs*pokeDir.z + sn*pokeDir.x));
			rb.AddForceAtPosition (60/Time.fixedDeltaTime*rb.mass*pokePower*pokeDir, rb.position + radius*pokeRel);

			//rb.velocity += 60*pokePower*pokeDir;
			
			sideSpin += pokeSide;
			topSpin += pokeHeight;

			pokePower = 0;
			pokeHeight = 0;
			pokeSide = 0;
		}

		if ((controllable && Input.GetMouseButton(1)) || charging)
		{
			Vector3 v = rb.velocity;
			v.x -= Mathf.Min (1, 2.5f*Time.fixedDeltaTime)*v.x;
			v.z -= Mathf.Min (1, 2.5f*Time.fixedDeltaTime)*v.z;
			rb.velocity = v;
		}
	}

	private float yaw = 0;
	private float pitch = 20;
	private float power = 0;
	private float pokePower = 0;
	private Vector3 pokeDir = Vector3.zero;
	private float pokeHeight = 0;
	private float pokeSide = 0;

	void OnCollisionEnter(Collision collision)
	{
		Ball otherBall = collision.gameObject.GetComponent<Ball>();

		if ((otherBall != null) && (otherBall.counter_this > counter_this))
		{
			audioSource.clip = collideSound;
			audioSource.volume = 0.1f*Mathf.Sqrt(collision.relativeVelocity.magnitude);
			audioSource.Play();
		}
		else if(collision.gameObject.GetComponent<WorldFrame>() != null)
		{
			audioSource.clip = boundarySound;
			audioSource.volume = 0.1f*Mathf.Sqrt(collision.relativeVelocity.magnitude);
			audioSource.Play();
		}
		else if(collision.gameObject.GetComponent<WorldField>() != null)
		{
			audioSource.clip = bounceSound;
			audioSource.volume = 0.1f*Mathf.Sqrt(collision.relativeVelocity.magnitude);
			audioSource.Play();

			rollSource.clip = rollSound;
			rollSource.volume = 0.1f*Mathf.Max(0, Mathf.Sqrt(rb.velocity.magnitude)-1);
			rollSource.pitch = 0.5f + 0.1f*Mathf.Sqrt(rb.velocity.magnitude);
			rollSource.loop = true;
			rollSource.Play();
		}
	}

	void OnCollisionStay(Collision collision)
	{
		if(collision.gameObject.GetComponent<WorldField>() != null)
		{
			rollSource.volume = 0.1f*Mathf.Max(0, Mathf.Sqrt(rb.velocity.magnitude)-1);
			rollSource.pitch = 0.5f + 0.1f*Mathf.Sqrt(rb.velocity.magnitude);
		}
	}

	void OnCollisionExit(Collision collision)
	{
		if(collision.gameObject.GetComponent<WorldField>() != null) rollSource.volume = 0;
	}

	void Update ()
	{
		if (controllable)
		{
			Screen.lockCursor = true;
			Screen.showCursor = false;
		}

		float y = transform.position.y;
		//y = Mathf.Clamp(2*y,-1,1);
		y = Mathf.Clamp(1.5f*y,-2,2);
		y += y*y*y*(-3.0f/16.0f + y*y*(1.0f/64.0f));

		float mx=0, my=0;

		if (cameraFollows || controllable)
		{
			mx = Input.GetAxis("Mouse X");
			my = Input.GetAxis("Mouse Y");

			if (!(controllable && Input.GetMouseButton(0))) yaw = Mathf.DeltaAngle(0,yaw+4*mx);
			else yaw = Mathf.DeltaAngle(0,yaw+1*mx);
			pitch = Mathf.DeltaAngle(0,Mathf.Clamp(pitch-4*my,-10,30));
		}
		
		if (cameraFollows && (mainCamera != null))
		{
			Vector3 ea = mainCamera.transform.eulerAngles;

			float shake = Mathf.Max(0, 3*(power-0.05f));

			ea.y = y*(yaw + shake*(2*Random.value-1));
			ea.x = y*(pitch + shake*(2*Random.value-1));
			ea.z = (1-y)*90;
			//Debug.Log (ea);
			mainCamera.transform.eulerAngles = ea;

			float d = 5;

			RaycastHit hitInfo;
			if (Physics.Raycast(transform.position, -mainCamera.transform.forward, out hitInfo, d+0.25f))
			{
				d = Mathf.Max(0, hitInfo.distance - 0.25f);
			}

			mainCamera.transform.position = transform.position - d*mainCamera.transform.forward;

			//Vector3 camera_oldpos = mainCamera.transform.position;
			//Vector3 camera_dpos = transform.position - d*mainCamera.transform.forward - camera_oldpos;

			//mainCamera.transform.position = camera_oldpos + Mathf.Min(1,Time.deltaTime*1000.0f/(1+camera_dpos.sqrMagnitude))*camera_dpos;
			//mainCamera.transform.position = camera_oldpos + Mathf.Min(0.95f,8.0f*Time.deltaTime)*camera_dpos;
			//mainCamera.transform.LookAt(transform.position);
		}

		if (lightFollows && (mainLight != null))
		{
			Vector3 ea = mainLight.transform.eulerAngles;

			ea.x = 35*y;

			mainLight.transform.eulerAngles = ea;
		}

		if (index<0)
		{
			if (charging)
			{
				power += Mathf.Min(0.9f, 0.125f*Time.deltaTime)*(1-power);

				chargeSource.volume = 16*power*power;
				chargeSource.pitch = 1 + 2*power*power;

				if ((power>0.25f) && (Random.value < 2*power*Time.deltaTime))
				{
					pokePower = power;
					pokeDir = (GameObject.Find("Frame").GetComponent<WorldFrame>().cueball.transform.position - transform.position).normalized;
					pokeDir.y = 0;
					pokeHeight = 0;
					pokeSide = 0;
					power = 0;
					charging = false;

					chargeSource.volume = 0;

					jubilationSource.clip = chargeLaunchSound;
					jubilationSource.volume = 16*pokePower*pokePower;
					jubilationSource.Play();
				}
			}
			else if(rb.velocity.magnitude < 15.0f)
			{
				if (Random.value < 0.05f*Time.deltaTime)
				{
					charging = true;

					chargeSource.clip = chargeSound;
					chargeSource.volume = 0;
					chargeSource.loop = true;
					chargeSource.Play();
				}
			}
		}

		if (controllable)
		{
			if (Input.GetMouseButton(0))
			{
				power += Mathf.Min(0.9f, 0.125f*Time.deltaTime)*(1-power);
				pokeSide += 0.01f*mx;
				pokeSide -= Mathf.Min(0.5f, 0.1f*Time.deltaTime*pokeSide);
			}

			if (Input.GetMouseButtonUp(0))
			{
				if (power > 0.01f)
				{
					audioSource.clip = launchSound;
					audioSource.volume = Mathf.Sqrt(power);
					audioSource.Play();

					pokePower = power;
					pokeDir.Set(Mathf.Sin(y*yaw*Mathf.PI/180), 0, Mathf.Cos(y*yaw*Mathf.PI/180));
					pokeHeight = pitch;
					if (pokeHeight<0) pokeHeight *= 3;
					pokeHeight *= y/35;
					pokeSide = Mathf.Clamp(y*pokeSide,-1,1);

					//Debug.Log(pokePower + " " + pokeHeight + " " + pokeSide);
				}

				power = 0;
			}
		}
	}

	bool charging = false;
}
