using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace Framework.PlasmaStateSystem
{
    /// <summary>
    /// Represents a single state of an object. Used within a state machine
    /// to fascilate states and state transitions. 
    /// </summary>
    public class State : Statelike, Transitionable
    {
        protected string name;
        protected Dictionary<Type, Transitionlike> transitions;
        protected Statelike superstate;
        protected Statelike[] initialStates;
        protected EnterAction Enter;
        protected UpdateAction Update;
        protected ExitAction Exit;

        /// <summary>
        /// Initializes a new instance of the <see cref="State"/> class.
        /// </summary>
        public State()
        {
            this.transitions = new Dictionary<Type,Transitionlike>();
            if (Enter == null)
            {
                Enter = NullEnter;
            }
            if (Update == null)
            {
                Update = NullUpdate;
            }
            if (Exit == null)
            {
                Exit = NullExit;
            }
        }

        
        // Constructors

        public State(string name)
            : this()
        {
            this.name = name;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="State"/> class.
        /// </summary>
        /// <param name="superstate">The superstate.</param>
        public State(string name, Statelike superstate) : this(name)
        {
            this.superstate = superstate;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="State"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="enter">The enter.</param>
        public State(string name, EnterAction enter)
            : this(name)
        {
            Enter = enter;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="State"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="superstate">The superstate.</param>
        /// <param name="enter">The enter.</param>
        public State(string name, Statelike superstate, EnterAction enter)
            : this(name, superstate)
        {
            Enter = enter;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="State"/> class.
        /// </summary>
        /// <param name="superstate">The superstate.</param>
        public State(string name, Statelike superstate, EnterAction enter,
            UpdateAction update, ExitAction exit)
            : this(name, superstate, enter)
        {
            Update = update;
            Exit = exit;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="State"/> class.
        /// </summary>
        /// <param name="superstate">The superstate.</param>
        public State(string name, EnterAction enter, UpdateAction update, 
            ExitAction exit) 
            : this(name)
        {
            Enter = enter;
            Update = update;
            Exit = exit;
        }

        // Public Methods

        /// <summary>
        /// Gets the name of this state.
        /// </summary>
        /// <returns></returns>
        public string GetName()
        {
            return this.name;
        }

        /// <summary>
        /// Determines whether this state is a substate of the given superstate.
        /// </summary>
        /// <param name="superstate">The superstate.</param>
        /// <returns></returns>
        public bool IsSubstate(Statelike superstate)
        {
            bool result = false;
            if(this.superstate != null)
            {
                if (this.superstate == superstate)
                {
                    return true;
                }
                else
                {
                    return this.superstate.IsSubstate(superstate);
                }
            }
            return result;
        }

        /// <summary>
        /// Gets the handler.
        /// </summary>
        /// <param name="spark">The spark.</param>
        /// <returns></returns>
        /// <exception cref="System.NotImplementedException"></exception>
        public Statelike GetHandler(Sparklike spark)
        {
            Statelike handlingState = null;
            if (this.transitions.ContainsKey(spark.GetType()))
            {
                handlingState = this;
            }
            else if (this.superstate != null)
            {
                handlingState = this.superstate.GetHandler(spark);
            }
            return handlingState;
        }

        /// <summary>
        /// Gets the leaf states of a state like object.
        /// </summary>
        /// <returns>
        /// The leaf states.
        /// </returns>
        public Statelike[] GetLeafStates()
        {
            if (this.initialStates != null)
            {
                List<Statelike> leafStates = new List<Statelike>();
                for (int i = 0; i < this.initialStates.Length; i++)
                {
                    leafStates.AddRange(this.initialStates[i].GetLeafStates());
                }
                return leafStates.ToArray();
            }
            else
            {
                return new Statelike[] { this };
            }
        }

        /// <summary>
        /// Sets a transition on this state.
        /// </summary>
        /// <param name="spark">The spark.</param>
        /// <param name="targetState">Target state to change to.</param>
        public void SetTransition<SparkType>(Statelike targetState) where SparkType : Sparklike
        {
            this.transitions.Add(typeof(SparkType), new Transition(targetState));
        }

        /// <summary>
        /// Sets a transition on this state instance with a condition.
        /// </summary>
        /// <param name="spark">The spark.</param>
        /// <param name="targetState">Target state to change to.</param>
        /// <param name="condition">The condition.</param>
        public void SetTransition<SparkType>(Statelike targetState, TransitionCondition condition) where SparkType : Sparklike
        {
            this.transitions.Add(typeof(SparkType), new Transition(targetState, condition));
        }

        /// <summary>
        /// Sets the initial states of this state.
        /// </summary>
        /// <param name="initialStates">The initial states.</param>
        public void SetInitialStates(params Statelike[] initialStates)
        {
            this.initialStates = initialStates;
        }

        /// <summary>
        /// Attempts to transition of this state to another by returning the 
        /// new states of a state machine system. 
        /// The method takes a spark which it then checks in its transition 
        /// dictionary. From the transition it finds it gets the leaf states
        /// that the new system will 'fall' into. If the transition has a 
        /// condition this must pass as true.
        /// </summary>
        /// <param name="spark">The spark.</param>
        /// <returns>
        /// The new states.
        /// </returns>
        public Statelike[] Transition(Sparklike spark)
        {
            Statelike[] newState = null;
            if (this.transitions.ContainsKey(spark.GetType()))
            {
                Transitionlike transition = this.transitions[spark.GetType()];
                if (transition.IsConditional())
                {
                    if (transition.Passes())
                    {
                        newState = transition.GetState().GetLeafStates();
                    }
                }
                else
                {
                    newState = transition.GetState().GetLeafStates();
                }
            }
            return newState;
        }

        // TODO, do you exit and enter all states in the hierachy?

        /// <summary>
        /// Invokes the enter action.
        /// </summary>
        public void InvokeEnter()
        {
            // HACK: Invoking superstate needs testing
            if (this.superstate != null)
            {
                this.superstate.InvokeEnter();
            }
            if (Enter == null)
            {
                Enter = NullEnter;
            }
            Enter();
        }

        /// <summary>
        /// Invokes the update action.
        /// </summary>
        public void InvokeUpdate()
        {
            // HACK: Invoking superstate needs testing
            if (this.superstate != null)
            {
                this.superstate.InvokeUpdate();
            }
            // HACK: SERIOUS ISSUE, BASICALLY: Update because null before this is called somehow.
            if (Update == null)
            {
                Update = NullUpdate;
            }
            Update();
        }

        /// <summary>
        /// Invokes the exit action.
        /// </summary>
        public void InvokeExit()
        {
            // HACK: Invoking superstate needs testing
            if (this.superstate != null)
            {
                this.superstate.InvokeExit();
            }
            if (Exit == null)
            {
                Exit = NullExit;
            }
            Exit();
        }

        /// <summary>
        /// Called when a transitional object is entered.
        /// </summary>
        public void NullEnter()
        {
            Log("Entering state: " + this.name);
        }

        /// <summary>
        /// Called when a transitional object is exited.
        /// </summary>
        public void NullExit()
        {
            Log("Exiting state: " + this.name);
        }

        /// <summary>
        /// Updates this state.
        /// </summary>
        public void NullUpdate()
        {
        }

        /// <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.name;
        }

        /// <summary>
        /// Logs the specified message.
        /// </summary>
        /// <param name="message">The message.</param>
        private void Log(string message)
        {
            //Debug.Log(message);
        }
    }
}
