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

namespace Framework.PlasmaStateSystem
{
    /// <summary>
    /// Holds and manages state and controls state lifecycle. The machine must be setup
    /// with an initial state and then can move from one state to another by being 
    /// 'ignited'. The state of the machine can be queried while it is running.
    /// </summary>
    public class StateMachine : StateMachinelike
    {
        protected List<Statelike> state;
        protected List<Sparklike> sparkQueue;

        /// <summary>
        /// Initializes a new instance of the <see cref="StateMachine"/> class.
        /// </summary>
        /// <param name="state">The state.</param>
        public StateMachine()
        {
            this.sparkQueue = new List<Sparklike>();
        }

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

        /// <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.state.ToArray();
        }

        /// <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)
        {
            bool stateIsSubstate = false;
            for (int i = 0; i < this.state.Count; i++)
            {
                if (this.state[i].IsSubstate(state))
                {
                    stateIsSubstate = true;
                    break;
                }
            }
            return this.state.Contains(state) || stateIsSubstate;
        }

        /// <summary>
        /// Ignites this instance. A snapshot of the current state is taken and then
        /// each state within the current state is checked to see if it has a handling
        /// superstate that can deal with the generic spark type. If it does the transition
        /// is attempted and if this passes the new state is updated.
        /// </summary>
        /// <typeparam name="SparkType">The type of spark to ignite.</typeparam>
        public void Ignite(Sparklike spark)
        {
            Log("\nIgniting: " + spark.GetType().Name);
            this.sparkQueue.Add(spark);
        }

        public void Update()
        {
            HandleSparks();
            for (int i = 0; i < this.state.Count; i++)
            {
                this.state[i].InvokeUpdate();
            }
        }

        /// <summary>
        /// Updates the current state with the new state. The handling state or any of its
        /// substates is removed from the current state, they are replaced with the new state.
        /// </summary>
        /// <param name="handlingState"></param>
        /// <param name="newState"></param>
        protected void UpdateState(Statelike handlingState, Statelike[] newState)
        {
            // For each state we currently have, check to see if it is a substate or the same 
            // as the handling state, if so remove it from our current state while calling 
            // its Exit method, we are leaving these states

            // Temporarily store the current state as we're about to modify it but we need to work with what 
            // we had before it gets changed
            List<Statelike> stateSnapshot = GetStateSnapshot();

            for (int i = 0; i < stateSnapshot.Count; i++)
            {
                if (stateSnapshot[i].IsSubstate(handlingState) || stateSnapshot[i] == handlingState)
                {
                    //Log("\t" + stateSnapshot[i] + " is/is sub of " + handlingState);
                    stateSnapshot[i].InvokeExit();
                    this.state.Remove(stateSnapshot[i]);
                }
            }

            // For each new state add it to our current state and then call Entry, we are 
            // entering these states
            for (int i = 0; i < newState.Length; i++)
            {
                this.state.Add(newState[i]);
                newState[i].InvokeEnter();
            }
            //Log("\tUpdated State: " + string.Join(", ", this.state.Select(j => j.ToString()).ToArray()));
        }

        /// <summary>
        /// Gets the initial state of this instance based on a list of passed in states.
        /// </summary>
        /// <param name="state">The high level state to start in.</param>
        /// <returns>The state that is dropped into.</returns>
        protected List<Statelike> GetInitialState(Statelike[] state)
        {
            List<Statelike> initialState = new List<Statelike>();
            for (int i = 0; i < state.Length; i++)
            {
                initialState.AddRange(state[i].GetLeafStates());
            }
            return initialState;
        }

        /// <summary>
        /// Gets a snapshot of the current state.
        /// </summary>
        /// <returns></returns>
        private List<Statelike> GetStateSnapshot()
        {
            List<Statelike> stateSnapshot = new List<Statelike>();
            for (int i = 0; i < this.state.Count; i++)
            {
                stateSnapshot.Add(this.state[i]);
            }
            return stateSnapshot;
        }

        private List<Sparklike> GetSparkQueueSnapshot()
        {
            List<Sparklike> sparkQueueSnapshot = new List<Sparklike>();
            for (int i = 0; i < this.sparkQueue.Count; i++)
            {
                sparkQueueSnapshot.Add(this.sparkQueue[i]);
            }
            return sparkQueueSnapshot;
        }

        private void HandleSparks()
        {
            List<Sparklike> sparkQueueSnapshot = GetSparkQueueSnapshot();

            for (int i = 0; i < sparkQueueSnapshot.Count; i++)
            {
                HandleSpark(sparkQueueSnapshot[i]);
            }
        }

        private void HandleSpark(Sparklike spark)
        {
            Log("Spark queue: " + string.Join(", ", this.sparkQueue.Select(i => i.GetType().Name).ToArray()));
            Log("State before transition (" + spark.GetType().Name + "): " + string.Join(", ", this.state.Select(j => j.ToString()).ToArray()));
            // Temporarily store the current state as we're about to modify it but we need to work with what 
            // we had before it gets changed
            List<Statelike> stateSnapshot = GetStateSnapshot();

            // For each state check if it hasn't be removed by another transition then proceed to carry out 
            // its transition
            for (int i = 0; i < stateSnapshot.Count; i++)
            {
                //Log("\t" + i + ", tempState = " + stateSnapshot[i] + ": " + string.Join(", ", stateSnapshot.Select(j => j.ToString()).ToArray()));
                if (this.state.Contains(stateSnapshot[i]))
                {
                    // Get the handling state for this spark, this may be the current state or it maybe a superstate
                    // of the current state, there may not even be a handler
                    Statelike handlingState = stateSnapshot[i].GetHandler(spark);
                    if (handlingState != null)
                    {
                        // Call the transition on the handling state and then update the state of the component
                        // as a whole
                        //Log("\tTransitioning from ignited spark: " + spark.GetType().Name);
                        Statelike[] newState = handlingState.Transition(spark);

                        // If the transition succeeded, this may not be the case because a condition might have
                        // not passed!
                        if (newState != null)
                        {
                            UpdateState(handlingState, newState);
                        }
                    }
                }
            }
            this.sparkQueue.Remove(spark);
            Log("State after transition (" + spark.GetType().Name + "): " + string.Join(", ", this.state.Select(j => j.ToString()).ToArray()));
        }

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