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

public abstract class IInputReceivable : MonoBehaviour
{
    private bool _removeAndDestroy = false;
    public bool RemoveAndDestroy
    {
        get { return _removeAndDestroy; }
        set { _removeAndDestroy = value; }
    }

    private bool _initialized = false;
    public bool Initialized
    {
        get { return _initialized; }
        set { _initialized = value; }
    }
    
    private const int NO_FINGER_ID = -1;

    private Dictionary<string, Dictionary<InputManager.TouchPhases, Action>> mButtonActions = new Dictionary<string, Dictionary<InputManager.TouchPhases, Action>>();
    private ITouchable mCurrentlyPressedBtn = null, mPreviouslyPressedBtn = null;
    private ScrollingArea mCurrentlyPressedScrollingArea = null;
    private int mCurrentFingerId = NO_FINGER_ID;

    /*private void Start()
    {
        Util.AssertTrue(GetComponent<IInputReceivable>().name == gameObject.name, 
            "Gameobject name (" + gameObject.name + ") and script name (" + GetComponent<IInputReceivable>().name + ") are not the same but should be for organization puposes");
    }*/

    /// <summary>
    /// Warning: overriding this method and not calling its base will prevent the button methods from being called!
    /// </summary>
    /// <param name="screenPosition">The position on the screen where the touch occured</param>
    /// <returns>True if the touch event was consumed, false otherwise</returns>
    /// <owner>felipecb</owner>
    public virtual bool OnTouch(Vector3 screenPosition, InputManager.TouchPhases touchPhase, int fingerId)
    {
        switch (touchPhase)
        {
            case InputManager.TouchPhases.Began:
                if (mCurrentFingerId == NO_FINGER_ID)
                {
                    mCurrentFingerId = fingerId;
                    return OnTouchBegan(screenPosition);
                }
                return false;
            case InputManager.TouchPhases.Moved:
                if (fingerId == mCurrentFingerId)
                {
                    return OnTouchMoved(screenPosition);
                }
                return false;
            case InputManager.TouchPhases.Ended:
                if (fingerId == mCurrentFingerId)
                {
                    mCurrentFingerId = NO_FINGER_ID;
                    return OnTouchEnded(screenPosition);
                }
                return false;
        }
        Util.AssertTrue(false, "IInputReceivable is unable to process this kind of touch: " + touchPhase.ToString());
        return false;
    }

    private void RascastAndGetData(Vector3 position, out RaycastHit[] hitInfo, out GameObject buttonObj, out ITouchable buttonScript, out GameObject scrollObj, out ScrollingArea scrollScript)
    {
        hitInfo = Physics.RaycastAll(camera.ScreenPointToRay(position));
        buttonObj = hitInfo.Select(info => info.collider.gameObject).First(gameObj => gameObj.GetComponent<ITouchable>() != null);
        buttonScript = buttonObj == null ? null : buttonObj.GetComponent<ITouchable>();
        scrollObj = hitInfo.Select(info => info.collider.gameObject).First(gameObj => gameObj.GetComponent<ScrollingArea>() != null);
        scrollScript = scrollObj == null ? null : scrollObj.GetComponent<ScrollingArea>();
    }

    private bool OnTouchBegan(Vector3 screenPosition)
    {
        bool wasEventConsumed = false;
        GameObject buttonObj;
        ITouchable pressedBtnScript;
        GameObject scrollObj;
        ScrollingArea scrollScript;
        RaycastHit[] hitInfo;
        RascastAndGetData(screenPosition, out hitInfo, out buttonObj, out pressedBtnScript, out scrollObj, out scrollScript);
        if (hitInfo.Length > 0)
        {
            if (pressedBtnScript != null)
            {
                Util.AssertTrue(mButtonActions.ContainsKey(buttonObj.name), "An unregistered button has been touched: " + buttonObj.name);
                Util.AssertTrue(mButtonActions[buttonObj.name].ContainsKey(InputManager.TouchPhases.Began), "An unregistered button has been touched: " + buttonObj.name);
                pressedBtnScript.OnTouchBegan();
                mButtonActions[buttonObj.name][InputManager.TouchPhases.Began]();
                wasEventConsumed = true;
                mCurrentlyPressedBtn = pressedBtnScript;
            }
            if (scrollScript != null)
            {
                Util.AssertTrue(mCurrentlyPressedScrollingArea == null, "A ScrollingArea was pressed while another one was already pressed");
                RaycastHit scrollHitInfo = hitInfo.Where(info => info.collider.gameObject == scrollObj).First();
                scrollScript.OnTouchBegan(screenPosition, scrollHitInfo.point, mCurrentlyPressedBtn != null);
                mCurrentlyPressedScrollingArea = scrollScript;
            }
        }
        return wasEventConsumed;
    }

    private bool OnTouchMoved(Vector3 screenPosition)
    {
        GameObject buttonObj;
        ITouchable pressedBtnScript;
        GameObject scrollObj;
        ScrollingArea scrollScript;
        RaycastHit[] hitInfo;
        RascastAndGetData(screenPosition, out hitInfo, out buttonObj, out pressedBtnScript, out scrollObj, out scrollScript);
        Util.AssertTrue(buttonObj == null ? true : mButtonActions.ContainsKey(buttonObj.name), "An unregistered button has been touched: " /*+ buttonObj.name*/);
        if (mCurrentlyPressedBtn != null && mCurrentlyPressedBtn != pressedBtnScript)
        {
            mCurrentlyPressedBtn.OnTouchEnded();
            mPreviouslyPressedBtn = mCurrentlyPressedBtn;
            mCurrentlyPressedBtn = null;
        }
        else if(mPreviouslyPressedBtn != null && mPreviouslyPressedBtn == pressedBtnScript)
        {
            mPreviouslyPressedBtn.OnTouchBegan();
            mCurrentlyPressedBtn = mPreviouslyPressedBtn;
            mPreviouslyPressedBtn = null;
        }
        if (mCurrentlyPressedScrollingArea != null)
        {
            if (mCurrentlyPressedScrollingArea.OnTouchMoved(screenPosition))
            {
                if (mCurrentlyPressedBtn != null)
                {
                    mCurrentlyPressedBtn.OnTouchEnded();
                }
                else if (mPreviouslyPressedBtn != null)
                {
                    mPreviouslyPressedBtn.OnTouchEnded();
                }
                mCurrentlyPressedBtn = mPreviouslyPressedBtn = null;
            }
        }
        return mCurrentlyPressedBtn != null || mPreviouslyPressedBtn != null || mCurrentlyPressedScrollingArea != null;
    }

    private bool OnTouchEnded(Vector3 screenPosition)
    {
        GameObject buttonObj;
        ITouchable pressedBtnScript;
        GameObject scrollObj;
        ScrollingArea scrollScript;
        RaycastHit[] hitInfo;
        RascastAndGetData(screenPosition, out hitInfo, out buttonObj, out pressedBtnScript, out scrollObj, out scrollScript);
        if (hitInfo.Length > 0)
        {
            if (pressedBtnScript != null && pressedBtnScript == mCurrentlyPressedBtn)
            {
                Util.AssertTrue(mButtonActions.ContainsKey(buttonObj.name), "An unregistered button has been touched: " + buttonObj.name);
                Util.AssertTrue(mButtonActions[buttonObj.name].ContainsKey(InputManager.TouchPhases.Ended), "An unregistered button has been touched " + buttonObj.name);
                pressedBtnScript.OnTouchEnded();
                mButtonActions[buttonObj.name][InputManager.TouchPhases.Ended]();
            }
            else
            {
                // FIX: Com movimentos muito rápidos (no mouse é possível) consegue-se dar um TouchEnded fora do botao
                // atualmente pressionado (antes que o botao atualmente pressionado receba o evento de TouchEnded no TouchMoved)
                if (mCurrentlyPressedBtn != null)
                {
                    mCurrentlyPressedBtn.OnTouchEnded();
                }
            }
        }
        else
        {
            // FIX: Com movimentos muito rápidos (no mouse é possível) consegue-se dar um TouchEnded fora do botao
            // atualmente pressionado (antes que o botao atualmente pressionado receba o evento de TouchEnded no TouchMoved)
            if (mCurrentlyPressedBtn != null)
            {
                mCurrentlyPressedBtn.OnTouchEnded();
            }
        }
        if (mCurrentlyPressedScrollingArea != null)
        {
            mCurrentlyPressedScrollingArea.OnTouchEnded(screenPosition);
            mCurrentlyPressedScrollingArea = null;
            return true;
        }
        bool retorno = mCurrentlyPressedBtn != null || mPreviouslyPressedBtn != null;
        mCurrentlyPressedBtn = null;
        mPreviouslyPressedBtn = null;
        return retorno;
    }

    public void RegisterButtonAction(string buttonName, Action actionDown, Action actionUp)
    {
        Util.AssertTrue(actionDown != null && actionUp != null, "Button actions cannot be null");
        RegisterPhasedButtonAction(buttonName, actionDown, InputManager.TouchPhases.Began);
        RegisterPhasedButtonAction(buttonName, actionUp, InputManager.TouchPhases.Ended);
    }

    private void RegisterPhasedButtonAction(string buttonName, Action action, InputManager.TouchPhases touchPhase)
    {
        if (!mButtonActions.ContainsKey(buttonName))
        {
            mButtonActions[buttonName] = new Dictionary<InputManager.TouchPhases, Action>();
        }
        Util.AssertTrue(!mButtonActions[buttonName].ContainsKey(touchPhase), "This button has already registered this phase's action");
        mButtonActions[buttonName][touchPhase] = action;
    }

    public virtual void OnLayerEffectFinished(bool removing) { }

    public virtual void OnDataPackReceived(object dataPack) { }
}