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

namespace mgaia.Utils.LayerSystem
{
	/// <summary>
	/// Classe que gerencia os inputs mobile (touches e back button) do jogo.
	/// Controla o envio de inputs atraves das camadas de layers (IInputReceivalbes), enviando o input para a camada superior e só
	/// repassando para a de baixo caso a atual permita e nao tenha consumido o input.
	/// </summary>
    public class InputManager
    {
        private Dictionary<int, GaiaTouch> mTouchesByFingerId = new Dictionary<int, GaiaTouch>();

        public enum TouchPhases
        {
            None,
            Began,
            Moved,
            Ended
        }

        public bool IsInputBlocked = false;

        private class GaiaTouch
        {
            private TouchPhases _mTouchPhase;
            private int _mFingerID;
            private Vector3 _mTouchPos;

            public TouchPhases TouchPhase { get { return _mTouchPhase; } }
            public int FingerID { get { return _mFingerID; } }
            public Vector3 TouchPos { get { return _mTouchPos; } }

            public GaiaTouch(TouchPhases touchPhase, int fingerID, Vector3 touchPos)
            {
                _mTouchPhase = touchPhase;
                _mFingerID = fingerID;
                _mTouchPos = touchPos;
            }
        }

        private class ReceivablePriorityPair
        {
            public IInputReceivable Receivable;
            public int Priority;
            public bool InputShallNotPass;
			private bool _mListenOnEffects;

			public bool ListenOnEffects
			{
				get { return _mListenOnEffects; }
			}

            public ReceivablePriorityPair(IInputReceivable receivable, int priority, bool inputShallNotPass, bool listenOnEffects)
            {
                Receivable = receivable;
                Priority = priority;
                InputShallNotPass = inputShallNotPass;
				_mListenOnEffects = listenOnEffects;
            }
        }

        private List<ReceivablePriorityPair> mReceivablesList = new List<ReceivablePriorityPair>();

        public void RegisterInputReceivableOnTop(IInputReceivable receivable, bool inputShallNotPass, bool listenOnEffects = false)
        {
            int prioridade = mReceivablesList.Count > 0 ? mReceivablesList[0].Priority + 1 : 0;
            RegisterInputReceivable(receivable, inputShallNotPass, prioridade, listenOnEffects);
        }

        public void RegisterInputReceivable(IInputReceivable receivable, bool inputShallNotPass, int priority, bool listenOnEffects)
        {
            int indexToInsertAt = 0;
            // insere ordenado por prioridade em ordem decrescente
            foreach (ReceivablePriorityPair spawnerPair in mReceivablesList)
            {
                if (priority >= spawnerPair.Priority)
                {
                    break;
                }
                ++indexToInsertAt;
            }
            mReceivablesList.Insert(indexToInsertAt, new ReceivablePriorityPair(receivable, priority, inputShallNotPass, listenOnEffects));
        }

        public void UnregisterInputReceivable(IInputReceivable receivable)
        {
            mReceivablesList.RepeatUntil
            (
                receivablePair => { },
                receivablePair =>
                {
                    if (receivablePair.Receivable.Equals(receivable))
                    {
                        mReceivablesList.Remove(receivablePair);
                        return true;
                    }
                    return false;
                }
            );
        }

		public void Update(float ellapsed)
		{
			List<GaiaTouch> touchesToFireEvents = new List<GaiaTouch>();

#if UNITY_EDITOR || UNITY_STANDALONE_WIN
			if (Input.GetMouseButtonDown(0)) ProcessTouchPhase(0, Input.mousePosition, TouchPhases.Began, touchesToFireEvents);
			else if (Input.GetMouseButton(0)) ProcessTouchPhase(0, Input.mousePosition, TouchPhases.Moved, touchesToFireEvents);
			else if (Input.GetMouseButtonUp(0)) ProcessTouchPhase(0, Input.mousePosition, TouchPhases.Ended, touchesToFireEvents);
			if (Input.GetMouseButtonDown(1)) ProcessTouchPhase(1, Input.mousePosition, TouchPhases.Began, touchesToFireEvents);
			else if (Input.GetMouseButton(1)) ProcessTouchPhase(1, Input.mousePosition, TouchPhases.Moved, touchesToFireEvents);
			else if (Input.GetMouseButtonUp(1)) ProcessTouchPhase(1, Input.mousePosition, TouchPhases.Ended, touchesToFireEvents);
			if (Input.GetMouseButtonDown(2)) ProcessTouchPhase(2, Input.mousePosition, TouchPhases.Began, touchesToFireEvents);
			else if (Input.GetMouseButton(2)) ProcessTouchPhase(2, Input.mousePosition, TouchPhases.Moved, touchesToFireEvents);
			else if (Input.GetMouseButtonUp(2)) ProcessTouchPhase(2, Input.mousePosition, TouchPhases.Ended, touchesToFireEvents);
#else
            Input.touches.ForEach(touch =>
            {
                TouchPhases touchPhase = TouchPhases.None;
                if (touch.phase == TouchPhase.Began) touchPhase = TouchPhases.Began;
                else if (touch.phase == TouchPhase.Moved) touchPhase = TouchPhases.Moved;
                else if (touch.phase == TouchPhase.Ended) touchPhase = TouchPhases.Ended;
                if (touchPhase != TouchPhases.None)
                {
                    ProcessTouchPhase(touch.fingerId, touch.position, touchPhase, touchesToFireEvents);
                }
            });
#endif

			touchesToFireEvents.ForEach(gaiaTouch =>
			{
                Func<ReceivablePriorityPair, bool> testMethod = delegate(ReceivablePriorityPair receivablePriorityPair)
                {
                    return receivablePriorityPair.Receivable.OnTouch(gaiaTouch.TouchPos, gaiaTouch.TouchPhase, gaiaTouch.FingerID);
                };

                TestInputOnPairs(testMethod);
			});

			if (Input.GetKeyDown(KeyCode.Escape))
			{
                Func<ReceivablePriorityPair, bool> testMethod = delegate(ReceivablePriorityPair receivablePriorityPair)
                {
                    return receivablePriorityPair.Receivable.OnEscapeKeyDown();
                };

                TestInputOnPairs(testMethod);
            }
		}

        private void TestInputOnPairs(Func<ReceivablePriorityPair, bool> testMethod)
        {
            for (int idx = 0; idx < mReceivablesList.Count; idx++)
            {
                ReceivablePriorityPair receivablePriorityPair = mReceivablesList[idx];

                if (receivablePriorityPair.InputShallNotPass)
                {
                    TestInputOnPair(receivablePriorityPair, testMethod);
                    break;
                }
                else
                {
                    if (TestInputOnPair(receivablePriorityPair, testMethod))
                    {
                        break;
                    }
                }
            }
        }

        private bool TestInputOnPair(ReceivablePriorityPair receivablePriorityPair, Func<ReceivablePriorityPair, bool> testMethod)
        {
            if (!IsInputBlocked || receivablePriorityPair.ListenOnEffects)
            {
                return testMethod(receivablePriorityPair);
            }

            return false;
        }

        private void ProcessTouchPhase(int fingerId, Vector3 mousePosition, TouchPhases newTouchPhase, List<GaiaTouch> listOfEventsToFire)
        {
            GaiaTouch newGaiaTouch = new GaiaTouch(newTouchPhase, fingerId, mousePosition);
			
            switch (newTouchPhase)
            {
                case TouchPhases.Began:
                    listOfEventsToFire.Add(newGaiaTouch);

                    mTouchesByFingerId[fingerId] = newGaiaTouch;
                    break;
                case TouchPhases.Moved:
                    bool isLastPositionDifferent = true;

                    if (mTouchesByFingerId.ContainsKey(fingerId))
					{
						Vector3 lastMovedPosition = mTouchesByFingerId[fingerId].TouchPos;
                        isLastPositionDifferent = lastMovedPosition.x != mousePosition.x || lastMovedPosition.y != mousePosition.y;
					}

                    if (isLastPositionDifferent)
                    {
                        listOfEventsToFire.Add(newGaiaTouch);
                        mTouchesByFingerId[fingerId] = newGaiaTouch;
                    }
                    break;
                case TouchPhases.Ended:
                    listOfEventsToFire.Add(newGaiaTouch);

					mTouchesByFingerId.Remove(fingerId);
                    break;
            }
        }
    }
}