using UnityEngine;
using System.Collections.Generic;
using Me.StefS.Utility;
using System;
using System.IO;

namespace Me.StefS.UnityUtility{
	[RequireComponent(typeof(Camera))]
	public class InputController : MonoBehaviour {
		private const bool KeyboardTest = 
			#if UNITY_EDITOR_OSX
				true;
			#else
				false;
			#endif

		[SerializeField]
		private float delayBeforeRecognisingGestureAsTapInSeconds = 0.21f;

		private Vector3 UpDirection = new Vector3(0,1,0);
		private Vector3 LeftDirection = new Vector3(-1,0,0);
		private Vector3 RigthDirection =  new Vector3(1,0,0);
		private Vector3 DownDirection = new Vector3(0,-1,0);
		private Vector3 mouseStartDrag;
		private bool isGestureStarted = false;
		private float timeSinceGestureStarted;
		private bool isGestureFired = false;
		private bool isHolding = false;
		private bool shouldWaitForRelease = false;
		private List<IInputListener> exclusivePermissionListener;
		private bool isExclusiveListenerSet;
		private List<IInputListener> inputListeners = new List<IInputListener>();
		private Collider[] sceneColliders;
		public List<IInputListener> ActiveInputListeners {
			get {
				return isExclusiveListenerSet ? exclusivePermissionListener : inputListeners;
			}
		}


		private Camera inputCamera;

		public void Start(){
			if(Input.GetMouseButton (0)){
				this.shouldWaitForRelease = true;
			}

			this.inputCamera = this.GetComponent<Camera> ();
			if(KeyboardTest){
				this.sceneColliders = FindObjectsOfType<Collider> ();
			}
		}

		// Update is called once per frame
		public void Update () {
			this.MouseUpdateAnalysis(Time.deltaTime);
			if(KeyboardTest){
				this.KeyboardAnalysis();
			}
		}

		public void Subscribe(IInputListener listener){
			if(listener == null){
				throw new System.ArgumentNullException("listener");
			}

			this.inputListeners.Add(listener);
		}

		public void Unsubscribe (IInputListener listener)
		{
			for(int listenerIndex = this.inputListeners.Count -1; listenerIndex >= 0; listenerIndex --){
				if(this.inputListeners[listenerIndex].Equals (listener)){
					this.inputListeners.RemoveAt (listenerIndex);
				}
			}
		}

		public void SetExclusivePermission(IInputListener listener){
			exclusivePermissionListener = new List<IInputListener>{listener};
			this.isExclusiveListenerSet = true;
		}

		public void RevokeExclusivePermissions(){
			exclusivePermissionListener = null;
			this.isExclusiveListenerSet = false;
		}
		private void KeyboardAnalysis ()
		{
			if(Input.GetKeyDown (KeyCode.LeftArrow)){
				this.InvokeSwipeLeft ();
			}
			
			if(Input.GetKeyDown (KeyCode.RightArrow)){
				this.InvokeSwipeRight ();
			}

			if(Input.GetKeyDown (KeyCode.DownArrow)){
				this.InvokeSwipeDown();
			}

			if(Input.GetKeyDown (KeyCode.Alpha0)){
				this.InvokeTapCollider (null);
			}

			if(Input.GetKeyDown (KeyCode.UpArrow)){
				this.InvokeHold ();
			}

			if(Input.GetKeyUp (KeyCode.UpArrow)){
				this.InvokeHoldEnd ();
			}

			for(int keyIndex = 1; keyIndex <= this.sceneColliders.Length; keyIndex++){
				if(Input.GetKeyDown (KeyCode.Alpha0 + keyIndex)){
					this.InvokeTapCollider (this.sceneColliders [keyIndex -1]);
				}
			}

			if(Input.GetKeyDown (KeyCode.R)){
				this.sceneColliders = FindObjectsOfType<Collider> ();
				Debug.Log (sceneColliders.Length);
			}

			if(Input.GetKeyDown (KeyCode.P)){
				ScreenCapture.CaptureScreenshot ("Shots" + Path.PathSeparator + "shotAt"+DateTime.Now.Ticks+".png", 2);
			}
		}

		private void MouseUpdateAnalysis (float deltaTime)
		{
			if(this.shouldWaitForRelease){
				if(!Input.GetMouseButtonUp (0)){
					return;
				}else{
					this.shouldWaitForRelease = false;
					return;
				}
			}

			//This has 0 0 in the bottom left corner, and works with pixel(screen) space
			//i need to convert it to viewport so i can do calcs regardles of resolution
			
			//furhtermore, the orientation is needed, therefore it would be wise to put this in CamerController
			//the vehicles one is connecting to it either way
			if(Input.GetMouseButtonDown(0) && !this.isGestureStarted){
				this.mouseStartDrag = Input.mousePosition;
				//Debug.Log(this.mouseStartDrag);
				this.isGestureStarted = true;
				this.timeSinceGestureStarted = 0;
			}
			
			if(this.isGestureStarted && !this.isGestureFired){
				this.timeSinceGestureStarted += deltaTime;
				//Dont fire new ones until this one stops
				float dragMagnitude = Input.mousePosition.VectorTo (this.mouseStartDrag).magnitude;
				if(dragMagnitude > 100){
					this.isGestureFired = true;
					this.OnSwipeTresholdPassed();
				}else if(dragMagnitude < 10 && this.timeSinceGestureStarted >= this.delayBeforeRecognisingGestureAsTapInSeconds){
					this.isGestureFired = true;
					this.isHolding = true;
					this.InvokeHold ();
				}
			}
			
			if(Input.GetMouseButtonUp(0)){
				if(isGestureFired == false){
					this.InvokeTap();
				}else{
					this.isGestureFired = false;
					if(isHolding){
						isHolding = false;
						this.InvokeHoldEnd ();
					}
				}
				this.isGestureStarted = false;
			}
		}

		private void OnSwipeTresholdPassed ()
		{
			float angleTreshold = 45f;
			float angleToLeft = this.mouseStartDrag.VectorTo(Input.mousePosition).AngleWith(this.LeftDirection);
			float angleToRight = this.mouseStartDrag.VectorTo(Input.mousePosition).AngleWith(this.RigthDirection);
			float angleToDown = this.mouseStartDrag.VectorTo(Input.mousePosition).AngleWith(this.DownDirection);
			float angleToUp = this.mouseStartDrag.VectorTo(Input.mousePosition).AngleWith(this.UpDirection);
			if( Mathf.Abs(angleToLeft) < angleTreshold){
				this.InvokeSwipeLeft();
			} else if(Mathf.Abs(angleToRight) < angleTreshold){
				this.InvokeSwipeRight();
			} else if(Mathf.Abs (angleToDown) < angleTreshold){
				this.InvokeSwipeDown ();
			} else if(Mathf.Abs (angleToUp) < angleTreshold){
				this.InvokeSwipeUp ();
			}
		}

		//Add parameters where necessary
		private void InvokeSwipeLeft ()
		{
			for(int listenerIndex = this.ActiveInputListeners.Count - 1; listenerIndex >=0 ; listenerIndex--){
				this.ActiveInputListeners[listenerIndex].OnSwipeLeft(this);
			}
		}

		private void InvokeSwipeRight ()
		{
			for(int listenerIndex = this.ActiveInputListeners.Count - 1; listenerIndex >=0 ; listenerIndex--){
				this.ActiveInputListeners[listenerIndex].OnSwipeRight(this);
			}
		}

		private void InvokeSwipeDown ()
		{
			for(int listenerIndex = this.ActiveInputListeners.Count - 1; listenerIndex >=0 ; listenerIndex--){
				this.ActiveInputListeners[listenerIndex].OnSwipeDown(this);
			}
		}

		private void InvokeSwipeUp ()
		{
			for(int listenerIndex = this.ActiveInputListeners.Count - 1; listenerIndex >=0 ; listenerIndex--){
				this.ActiveInputListeners[listenerIndex].OnSwipeUp(this);
			}
		}

		private void InvokeTap ()
		{
			Ray ray = inputCamera.ScreenPointToRay (Input.mousePosition);
			RaycastHit hitInfo;
			Collider hitCollider = Physics.Raycast (ray, out hitInfo) ? hitInfo.collider : null;
			this.InvokeTapCollider (hitCollider);
		}

		private void InvokeTapCollider (Collider hitCollider)
		{
			bool isConsumed = false;
			for (int listenerIndex = this.ActiveInputListeners.Count - 1; listenerIndex >= 0 && !isConsumed; listenerIndex--) {
				isConsumed = this.ActiveInputListeners [listenerIndex].OnTap (this, hitCollider);
			}
		}

		void InvokeHold ()
		{
			for(int listenerIndex = this.ActiveInputListeners.Count - 1; listenerIndex >=0 ; listenerIndex--){
				this.ActiveInputListeners[listenerIndex].OnHold(this);
			}
		}

		void InvokeHoldEnd ()
		{
			for(int listenerIndex = this.ActiveInputListeners.Count - 1; listenerIndex >=0 ; listenerIndex--){
				this.ActiveInputListeners[listenerIndex].OnHoldEnded(this);
			}
		}
	}
}