﻿/**
 * Author: Rory Lane Lutter 2015
 * 
 * A module that simulates touch imput based on the position of a 3D cursor in a worldspace canvas. 
 * 
 * In other words, it allows you to develop the same GUI for your Oculus rift and mobile application simultaneously.  
 * 
 * 
 **/


using System.Text;
using System;

namespace UnityEngine.EventSystems
{
	[AddComponentMenu("Event/ VR Friendly Touch Input Module")]
	public class VRTouchInputModule : PointerInputModule
	{
		protected  VRTouchInputModule()
		{ 
		
		}

		public Canvas canvas;
		public Camera GUIcamera;		
		public GameObject cursor;
	
		[HideInInspector] public Vector3 rayOrigin;
		[HideInInspector] public Vector3 rayDirection;
		[HideInInspector] public Ray cursorRay;

		public float hSpeed = 10.0f;
		public float vSpeed = 10.0f;
		public float cursorBoundsX = 1000.0f;
		public float cursorBoundsY = 1000.0f;



		private float hCursorMove;
		private float vCursorMove;

		private Vector2 m_LastMousePosition;
		private Vector2 m_MousePosition;
		
		[SerializeField]
		private bool m_AllowActivationOnStandalone;
		
		public bool allowActivationOnStandalone
		{
			get { return m_AllowActivationOnStandalone; }
			set { m_AllowActivationOnStandalone = value; }
		}
		
		public override void UpdateModule()
		{
			m_LastMousePosition = m_MousePosition;
			m_MousePosition = Input.mousePosition;
		}
		
		public override bool IsModuleSupported()
		{
			return m_AllowActivationOnStandalone || Input.touchSupported;
		}
		
		public override bool ShouldActivateModule()
		{
			if (!base.ShouldActivateModule())
				return false;
			
			if (UseFakeInput())
			{
				bool wantsEnable = Input.GetMouseButtonDown(0);
				
				wantsEnable |= (m_MousePosition - m_LastMousePosition).sqrMagnitude > 0.0f;
				return wantsEnable;
			}
			
			for (int i = 0; i < Input.touchCount; ++i)
			{
				Touch input = Input.GetTouch(i);
				
				if (input.phase == TouchPhase.Began
				    || input.phase == TouchPhase.Moved
				    || input.phase == TouchPhase.Stationary)
					return true;
			}
			return false;
		}
		
		private bool UseFakeInput()
		{
#if UNITY_EDITOR
	return true;
#else
#endif

#if UNITY_ANDROID 
//	cursor.SetActive(false);
	return false;
#else

#endif
	return true;
			
		}




		public override void Process()
		{

			if (UseFakeInput ())
				FakeTouches();
			else
				ProcessTouchEvents();

		}

		private void CopyFromTo(PointerEventData @from, PointerEventData @to)
		{
			@to.position = @from.position;
			@to.delta = @from.delta;
			@to.scrollDelta = @from.scrollDelta;
			@to.pointerCurrentRaycast = @from.pointerCurrentRaycast;
		}


		private Vector3 lastMousePos;
		private readonly MouseState n_MouseState = new MouseState();
		protected override MouseState GetMousePointerEventData()
		{


			/*
			 * 
			 * This is the important part. It gets a little convoluded, 
			 * but it essentially takes the 3D cursor, traces a ray from the GUI_Camera
			 * through the 3D cursor, and generates a tough event from it.
			 * It also handles moving the cursor and framing it's extents. 
			 * 
			 */

			Vector3 lastFrameCursorPos = GUIcamera.WorldToScreenPoint(cursor.transform.position);
			Vector3 oldCursorLocalPos = cursor.transform.localPosition;


			PointerEventData leftData;
			var created = GetPointerData(kMouseLeftId, out leftData, true);
			
			leftData.Reset();
			
			if (created) 
			{
				leftData.position = new Vector2(lastFrameCursorPos.x,lastFrameCursorPos.y);
			}

			Vector3 mouseMoveDelta = new Vector3 (Input.GetAxis ("Mouse X"), Input.GetAxis ("Mouse Y"), 0);

			hCursorMove = mouseMoveDelta.x*hSpeed;
			vCursorMove = mouseMoveDelta.y*vSpeed;

			lastMousePos = Input.mousePosition;
			Vector3 newLocalPosition = new Vector3 (oldCursorLocalPos.x + hCursorMove, oldCursorLocalPos.y + vCursorMove, oldCursorLocalPos.z);
		
			if (Math.Abs (newLocalPosition.x) > cursorBoundsX) {
				newLocalPosition.x=cursorBoundsX*(Math.Abs(newLocalPosition.x)/newLocalPosition.x);
			}
			if (Math.Abs (newLocalPosition.y) > cursorBoundsY) {
				newLocalPosition.y=cursorBoundsY*(Math.Abs(newLocalPosition.y)/newLocalPosition.y);
			}

			cursor.transform.localPosition = newLocalPosition;

			Vector3 thisFrameCursorPos = GUIcamera.WorldToScreenPoint(cursor.transform.position);
			Vector2 pos = new Vector2 (thisFrameCursorPos.x, thisFrameCursorPos.y);
			leftData.delta = pos - leftData.position;
			leftData.position = pos;

			if (Input.GetKeyDown (KeyCode.Space)) {
				cursor.transform.localPosition = new Vector3 (0, 0, 0);
				thisFrameCursorPos = GUIcamera.WorldToScreenPoint(cursor.transform.position);
				leftData.position = new Vector2(thisFrameCursorPos.x,thisFrameCursorPos.y);
				leftData.delta = Vector2.zero;
			}


			leftData.scrollDelta = Input.mouseScrollDelta;
			leftData.button = PointerEventData.InputButton.Left;

			eventSystem.RaycastAll(leftData, m_RaycastResultCache);
			var raycast = FindFirstRaycast(m_RaycastResultCache);
			leftData.pointerCurrentRaycast = raycast;
		//	Debug.Log ("raycast.distance::" + raycast.distance);
			m_RaycastResultCache.Clear();
			
			// copy the apropriate data into right and middle slots
			PointerEventData rightData;
			GetPointerData(kMouseRightId, out rightData, true);
			CopyFromTo(leftData, rightData);
			rightData.button = PointerEventData.InputButton.Right;
			
			PointerEventData middleData;
			GetPointerData(kMouseMiddleId, out middleData, true);
			CopyFromTo(leftData, middleData);
			middleData.button = PointerEventData.InputButton.Middle;
			
			n_MouseState.SetButtonState(PointerEventData.InputButton.Left, StateForMouseButton(0), leftData);
			n_MouseState.SetButtonState(PointerEventData.InputButton.Right, StateForMouseButton(1), rightData);
			n_MouseState.SetButtonState(PointerEventData.InputButton.Middle, StateForMouseButton(2), middleData);
			


			return n_MouseState;
		}


		private void FakeTouches()
		{
			//Debug.Log ("fake touches");
			var pointerData = GetMousePointerEventData();
			
			var leftPressData = pointerData.GetButtonState(PointerEventData.InputButton.Left).eventData;
			
			// fake touches... on press clear delta
			if (leftPressData.PressedThisFrame())
				leftPressData.buttonData.delta = Vector2.zero;
			
			ProcessTouchPress(leftPressData.buttonData, leftPressData.PressedThisFrame(), leftPressData.ReleasedThisFrame());
			
			// only process move if we are pressed...
			if (Input.GetMouseButton(0))
			{
				ProcessMove(leftPressData.buttonData);
				ProcessDrag(leftPressData.buttonData);
			}
		}


		private void updateVirtualCursor(Touch t){
			cursor.transform.localPosition = new Vector3 (t.position.x - Screen.width/2, t.position.y- Screen.height/2, 0);

		}

		/// <summary>
		/// Process all touch events.
		/// </summary>
		private void ProcessTouchEvents()
		{
			for (int i = 0; i < Input.touchCount; ++i)
			{
				Touch input = Input.GetTouch(i);
				if(i==0){
					updateVirtualCursor(input);
				}
				
				bool released;
				bool pressed;
				var pointer = GetTouchPointerEventData(input, out pressed, out released);
				
				ProcessTouchPress(pointer, pressed, released);
				
				if (!released)
				{
					ProcessMove(pointer);
					ProcessDrag(pointer);
				}
				else
					RemovePointerData(pointer);
			}
		}
		
		private void ProcessTouchPress(PointerEventData pointerEvent, bool pressed, bool released)
		{
			var currentOverGo = pointerEvent.pointerCurrentRaycast.gameObject;
			
			// PointerDown notification
			if (pressed)
			{
				pointerEvent.eligibleForClick = true;
				pointerEvent.delta = Vector2.zero;
				pointerEvent.dragging = false;
				pointerEvent.useDragThreshold = true;
				pointerEvent.pressPosition = pointerEvent.position;
				pointerEvent.pointerPressRaycast = pointerEvent.pointerCurrentRaycast;
				
				DeselectIfSelectionChanged(currentOverGo, pointerEvent);
				
				if (pointerEvent.pointerEnter != currentOverGo)
				{
					// send a pointer enter to the touched element if it isn't the one to select...
					HandlePointerExitAndEnter(pointerEvent, currentOverGo);
					pointerEvent.pointerEnter = currentOverGo;
				}
				
				// search for the control that will receive the press
				// if we can't find a press handler set the press
				// handler to be what would receive a click.
				var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.pointerDownHandler);
				
				// didnt find a press handler... search for a click handler
				if (newPressed == null)
					newPressed = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
				
				// Debug.Log("Pressed: " + newPressed);
				
				float time = Time.unscaledTime;
				
				if (newPressed == pointerEvent.lastPress)
				{
					var diffTime = time - pointerEvent.clickTime;
					if (diffTime < 0.3f)
						++pointerEvent.clickCount;
					else
						pointerEvent.clickCount = 1;
					
					pointerEvent.clickTime = time;
				}
				else
				{
					pointerEvent.clickCount = 1;
				}
				
				pointerEvent.pointerPress = newPressed;
				pointerEvent.rawPointerPress = currentOverGo;
				
				pointerEvent.clickTime = time;
				
				// Save the drag handler as well
				pointerEvent.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(currentOverGo);
				
				if (pointerEvent.pointerDrag != null)
					ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.initializePotentialDrag);
			}
			
			// PointerUp notification
			if (released)
			{
				// Debug.Log("Executing pressup on: " + pointer.pointerPress);
				ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler);
				
				// Debug.Log("KeyCode: " + pointer.eventData.keyCode);
				
				// see if we mouse up on the same element that we clicked on...
				var pointerUpHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
				
				// PointerClick and Drop events
				if (pointerEvent.pointerPress == pointerUpHandler && pointerEvent.eligibleForClick)
				{
					ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerClickHandler);
				}
				else if (pointerEvent.pointerDrag != null)
				{
					ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.dropHandler);
				}
				
				pointerEvent.eligibleForClick = false;
				pointerEvent.pointerPress = null;
				pointerEvent.rawPointerPress = null;
				
				if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
					ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.endDragHandler);
				
				pointerEvent.dragging = false;
				pointerEvent.pointerDrag = null;
				
				if (pointerEvent.pointerDrag != null)
					ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.endDragHandler);
				
				pointerEvent.pointerDrag = null;
				
				// send exit events as we need to simulate this on touch up on touch device
				ExecuteEvents.ExecuteHierarchy(pointerEvent.pointerEnter, pointerEvent, ExecuteEvents.pointerExitHandler);
				pointerEvent.pointerEnter = null;
			}
		}
		
		public override void DeactivateModule()
		{
			base.DeactivateModule();
			ClearSelection();
		}
		
		public override string ToString()
		{
			var sb = new StringBuilder();
			sb.AppendLine(UseFakeInput() ? "Input: Faked" : "Input: Touch");
			if (UseFakeInput())
			{
				var pointerData = GetLastPointerEventData(kMouseLeftId);
				if (pointerData != null)
					sb.AppendLine(pointerData.ToString());
			}
			else
			{
				foreach (var pointerEventData in m_PointerData)
					sb.AppendLine(pointerEventData.ToString());
			}
			return sb.ToString();
		}
	}
}