using Framework.DataStructures;
using Framework.InteractionSystem;
using Framework.PlasmaStateSystem;
using Framework.Sharing;
using Framework.StatSystem;
using Framework.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace Framework.Entities
{
    public delegate void ActionHandler(CoreAction action);

    [AddComponentMenu("Scripts/Framework/Entity Component")]
    public class CoreBehaviour : 
        MonoBehaviour, // This is a unity component to be added to a GameObject
        Interactable, // This is an interactable object that can act on other interables
        Statable // This object uses a state machine to track its state
    {
        protected StateMachinelike stateMachine;
        protected ListMap<Type, Interactable> exposedComponents; // Action >> Component
        protected Dictionary<Type, ActionHandler> actionMap; // Action >> Handler
        protected bool awake = false;
        private Attributes attributes;

        public CoreBehaviour()
        {
            this.exposedComponents = new ListMap<Type, Interactable>();
            this.actionMap = new Dictionary<Type, ActionHandler>();
        }

        public GameObject GetGameObject()
        {
            return this.gameObject;
        }

        /// <summary>
        /// Determines whether this component has another component in its list 
        /// of exposed components.
        /// </summary>
        /// <param name="component">The component.</param>
        /// <returns></returns>
        public bool HasExposedComponent(Interactable component)
        {
            return exposedComponents.Contains(component);
        }

        /// <summary>
        /// Determines whether this component has an exposed component that handles
        /// an action of a certain type as passed in by the generic parameter.
        /// </summary>
        /// <typeparam name="ActionType">The type of the ction type.</typeparam>
        /// <returns></returns>
        public bool HasExposedAction<ActionType>()
        {
            return exposedComponents.ContainsKey(typeof(ActionType));
        }

        /// <summary>
        /// Gets the object that holds this interactable as a component. This
        /// may not be applicable in all instances.
        /// </summary>
        /// <returns></returns>
        public object GetSelf()
        {
            return this.gameObject;
        }

        /// <summary>
        /// Sends an action to a global group of receivers managed by a
        /// mediator.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <returns></returns>
        public bool Announce(Actionlike action)
        {
            Log(this + " announcing action: " + action.ToString());
            return ActionMediatorLocator.Get().Broadcast(action);
        }

        public bool ExposedInteract(Actionlike action)
        {
            return ExposedInteract(action, false);
        }

        /// <summary>
        /// Only sends an action to the current exposed interactables and
        /// not the global mediator.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <returns></returns>
        public bool ExposedInteract(Actionlike action, bool announce)
        {
            Log(this + " exposed interacting action: " + action.ToString());
            int responseCount = 0;
            if (this.exposedComponents.ContainsKey(action.GetType()))
            {
                for (int i = 0; i < this.exposedComponents[action.GetType()].Count; i++)
                {
                    if ((GameObject)this.exposedComponents[action.GetType()][i].GetSelf() != this.gameObject &&
                        this.exposedComponents[action.GetType()][i].Receive(action))
                    {
                        Log(this + " acted " + action.GetType().Name + " upon " + this.exposedComponents[action.GetType()][i]);
                        responseCount++;
                    }
                }
            }
            if (announce)
            {
                if (Announce(action))
                {
                    responseCount++;
                }
            }
            return responseCount > 0;
        }

        public bool SelfInteract(Actionlike action)
        {
            return SelfInteract(action, false);
        }

        /// <summary>
        /// Interacts only with components on this current object.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <returns></returns>
        public bool SelfInteract(Actionlike action, bool announce)
        {
            Log(this + " self interacting action: " + action.ToString());
            int responseCount = 0;
            if(this.gameObject.Interact(action))
            {
                responseCount++;
            }
            if(announce)
            {
                if(Announce(action))
                {
                    responseCount++;
                }
            }
            return responseCount > 0;
        }

        /// <summary>
        /// Interacts directly with a gameObject.
        /// </summary>
        /// <param name="gameObject"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public bool DirectInteract(GameObject gameObject, Actionlike action)
        {
            return DirectInteract(gameObject, action, false);
        }

        public bool DirectInteract(GameObject gameObject, Actionlike action, bool announce)
        {
            int responseCount = 0;
            if (gameObject.Interact(action))
            {
                responseCount++;
            }
            if (announce)
            {
                if (Announce(action))
                {
                    responseCount++;
                }
            }
            return responseCount > 0;
        }

        public bool Interact(Actionlike action)
        {
            return Interact(action, false);
        }

        /// <summary>
        /// Sends an action to all self interactables, current exposed
        /// interactables and a global mediator.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <returns></returns>
        public bool Interact(Actionlike action, bool announce)
        {
            return SelfInteract(action, announce) || ExposedInteract(action, announce);
        }

        // TODO, Consider error handling for sub class and contains key
        /// <summary>
        /// Handles reception of an action by dispatching it to a delegate
        /// method.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <returns></returns>
        public bool Receive(Actionlike action)
        {
            if(action.GetType().IsSubclassOf(typeof(CoreAction)))
            {
                if(actionMap.ContainsKey(action.GetType()))
                {
                    actionMap[action.GetType()]((CoreAction)action);
                    return true;
                }
            }
            return false;
        }

        // TODO, This is confusing... who is exposing who? Who should call this? Who knows what?
        /// <summary>
        /// Exposes an action &gt; interactable map to the interactable this is
        /// called on. Gives over information to the interactable that this
        /// is called on as to which acts this the given interactable is
        /// interested in.
        /// </summary>
        /// <param name="actionType">Type of the action.</param>
        /// <param name="interactable">The interactable.</param>
        public void Expose(Type actionType, Interactable component)
        {
            Log(this.ToString() + ", has exposed component: " + component.ToString() + " for action: " + actionType.Name);
            this.exposedComponents.Add(actionType, component);
        }

        /// <summary>
        /// Requests exposure from the object this is called on and hands
        /// exposure back to the requester parameter. This will expose
        /// all action &gt; interactable maps to the requester.
        /// </summary>
        /// <param name="requester"></param>
        public void RequestExpose(Interactable requester)
        {
            foreach(KeyValuePair<Type, ActionHandler> a in this.actionMap)
            {
                requester.Expose(a.Key, this);
            }
        }

        /// <summary>
        /// Removes an interactable from the list of current exposed
        /// interactables.
        /// </summary>
        /// <param name="interactable">The interactable.</param>
        public void ClearExposed(Interactable interactable)
        {
            this.exposedComponents.Remove(interactable);
        }

        /// <summary>
        /// Sets the initial state.
        /// </summary>
        /// <param name="state">The state.</param>
        public void SetInitialState(params Statelike[] state)
        {
            return;
        }

        /// <summary>
        /// Gets the state of this instance.
        /// </summary>
        /// <returns>
        /// The complete set of states that represents the state of this instance.
        /// </returns>
        public Statelike[] GetState()
        {
            return this.stateMachine.GetState();
        }

        /// <summary>
        /// Does the set of states of this current instance contain a specific state?
        /// </summary>
        /// <param name="state">The state to look for.</param>
        /// <returns>
        /// Whether or not the state exists within this instances current state.
        /// </returns>
        public bool HasState(Statelike state)
        {
            return this.stateMachine.HasState(state);
        }

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String" /> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return this.GetType().Name;
        }

        /// <summary>
        /// The UnityEngine awake method. Should not be overriden
        /// </summary>
        protected void Awake()
        {
            InnerAwake();
            this.awake = true;
        }

        /// <summary>
        /// The UnityEngine start method. Should not be overriden.
        /// </summary>
        protected void Start()
        {
            ExposeSelf();
            InnerStart();
        }

        /// <summary>
        /// The UnityEngine update method. Should not be overriden.
        /// </summary>
        protected void Update()
        {
            if (this.stateMachine != null)
            {
                this.stateMachine.Update();
            }
            InnerUpdate();
        }

        /// <summary>
        /// Called when this component is awoken after the Unity 
        /// Awake is called. Should be overriden.
        /// </summary>
        protected virtual void InnerAwake()
        {
        }

        /// <summary>
        /// Called when this component is started after the Unity
        /// Start is called. Should be overriden.
        /// </summary>
        protected virtual void InnerStart()
        {
        }

        /// <summary>
        /// Called when this component is updated after the Unity
        /// Update is called. Should be overriden.
        /// </summary>
        protected virtual void InnerUpdate()
        {
        }

        /// <summary>
        /// A default action handler that can be used to handle any CoreAction
        /// as a state change for the state machine.
        /// </summary>
        /// <param name="action">The action.</param>
        protected void TransformState(CoreAction action)
        {
            this.stateMachine.Ignite(action);
        }

        /// <summary>
        /// Must be called when the component wakes up, exposes this component to 
        /// all other components within this gameObject so that self interactions 
        /// can occur.
        /// </summary>
        protected void ExposeSelf()
        {
            CoreBehaviour[] components = GetComponents<CoreBehaviour>();
            for(int i = 0; i < components.Length; i++)
            {
                if (components[i] != this)
                {
                    components[i].RequestExpose(this);
                }
            }
        }

        /// <summary>
        /// Adds an action to the action map of this component while specifying a handler
        /// to deal with that action. The type 'Action' here is the System.Action type
        /// while the action refered to by the method name is the Interaction.Action,
        /// </summary>
        /// <typeparam name="ActionType">The type of the ction type.</typeparam>
        /// <param name="actionHandler">The action handler.</param>
        protected void AddActionHandler<ActionType>(Action<ActionType> actionHandler) where ActionType : CoreAction
        {
            if (this.awake)
            {
                Debug.LogError("AddActionHandler should not be in any method other than InnerAwake! This may result in failed interactions.");
            }

            System.Diagnostics.Debug.Assert(!this.awake);
            // Now here's where things get wild.
            // We add new item to the action map, to do this we pass in the type of
            // action that we want to map a method handler to. 
            // We then convert the method that we're given (as actHandler) to 
            // a delegate of type ActHandler, this fully constrains the type of
            // the parameter of the handler to be a ComponentAct while still
            // allowing each method to receive its own parameter type.
            this.actionMap.Add(typeof(ActionType), delegate(CoreAction action) { actionHandler((ActionType)action); });
            Log(this + " has action handler for action: " + typeof(ActionType).Name);
        }

        protected void AddGlobalActionHandler<ActionType>(Interactable subscriber, Action<ActionType> actionHandler) where ActionType : CoreAction
        {
            ActionMediatorLocator.Get().Subscribe<ActionType>(subscriber);
            AddActionHandler<ActionType>(actionHandler);
        }

        protected void Log(string message)
        {
            //Debug.Log(message);
        }
    }
}
