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

namespace mgaia.Utils.StateMachine
{
    /// <summary>
    /// Classe que representa uma máquina de estados
    /// </summary>
    /// <owner>Victor</owner>
    public class StateMachine<TOwner> : IStateMachine<TOwner>
    {
        /// <summary>
        /// Classe interna que armazena um estado e um flag
        /// </summary>
        /// <owner>Victor</owner>
        private class StateContainer
        {
            private bool _mIsPrioritary;
            private IState<TOwner> _mContainedState;

            public StateContainer(bool isPrioritary, IState<TOwner> containedState)
            {
                _mIsPrioritary = isPrioritary;
                _mContainedState = containedState;
            }

            /// <summary>
            /// Estado armazenado no container
            /// </summary>
            public IState<TOwner> ContainedState
            {
                get { return _mContainedState; }
            }

            /// <summary>
            /// Flag indicando se esse estado é prioritário (não pode ser cancelado)
            /// </summary>
            public bool IsPrioritary
            {
                get { return _mIsPrioritary; }
            }
        }

        private Dictionary<object, StateContainer> mStates = new Dictionary<object, StateContainer>();

        private object _mRunningStateKey;

        private TOwner mOwner;
        private bool mAlreadyCompletedState = false;
        private Queue<object> mNextStateKeys = new Queue<object>();

        /// <summary>
        /// Retorna o StateContainer do estado que está rodando atualmente
        /// </summary>
        private StateContainer RunningStateContainer
        {
            get { return mStates[_mRunningStateKey]; }
        }

        /// <summary>
        /// Retorna a chave do estado que está rodando atualmente
        /// </summary>
        public object RunningStateKey
        { 
            get { return _mRunningStateKey; }
        }

        public StateMachine(TOwner owner)
        {
            mOwner = owner;
        }

        /// <summary>
        /// Roda o próximo estado caso haja
        /// </summary>
        private void RunNextState()
        {
            mAlreadyCompletedState = true;

            if (mNextStateKeys.Count > 0)
            {
                SetState(mNextStateKeys.Dequeue());
            }
        }

        /// <summary>
        /// Adiciona um novo estado
        /// </summary>
        public void AddState(object key, bool isPrioritary, IState<TOwner> state)
        {
            Assert.AssertTrue(!mStates.ContainsKey(key), "Já existe um estado de chave chamada " + key + "!");

            if (state is ObjectState<TOwner>)
            {
                ObjectState<TOwner> objectState = (ObjectState<TOwner>)state;
                objectState.StateMachine = this;
            }

            mStates.Add(key, new StateContainer(isPrioritary, state));
        }

        /// <summary>
        /// Seta o estado que deve rodar atualmente, e caso já haja outro estado rodando, empilha este em uma fila
        /// </summary>
        public void SetState(object key)
        {
            Assert.AssertTrue(mStates.ContainsKey(key), "Não existe um estado de chave chamada " + key + "!");

            if (_mRunningStateKey == null || mAlreadyCompletedState)
            {
                _mRunningStateKey = key;

                mAlreadyCompletedState = false;

                RunningStateContainer.ContainedState.Run(mOwner);

                if (!RunningStateContainer.IsPrioritary)
                {
                    RunNextState();
                }
            }
            else
            {
                mNextStateKeys.Enqueue(key);
            }
        }

        /// <summary>
        /// Notifica a state machine de que o estado atual foi completado
        /// </summary>
        public void NotifyCompletion(object key)
        {
            Assert.AssertTrue(key.Equals(RunningStateKey), "O estado de chave " + key + " não é o estado que está rodando atualmente! Estado rodando atualmente : " + RunningStateKey);

            RunNextState();
        }
    }
}