using System;
using System.Collections.Generic;
using System.Linq;

namespace AxibugEmuOnline.Client.Tools
{
    public class SimpleFSM<HOST>
    {
        private Dictionary<Type, State> m_states = new Dictionary<Type, State>();
        private State m_current;

        public HOST Host { get; private set; }

        public SimpleFSM(HOST host)
        {
            Host = host;
        }

        public State CurrentState => m_current;
        public T AddState<T>() where T : State, new()
        {
            var stateType = typeof(T);

            if (m_states.ContainsKey(stateType)) { return null; }

            var state = State.CreateState<T>(this);
            m_states.Add(typeof(T), state);

            return (T)state;
        }

        public void BackToLast()
        {
            if (m_current == null) return;
            if (m_current.LastState == null) return;

            if (m_states.Values.FirstOrDefault(s => s == m_current.LastState) is State lastState)
            {
                m_current.LastState = null;
                m_current.OnExit(lastState);

                lastState.OnEnter(m_current);
                m_current = lastState;
            }
        }

        public void Stop()
        {
            if (m_current != null)
            {
                m_current.OnExit(null);
                m_current = null;
            }

            foreach (var state in m_states.Values)
                state.LastState = null;
        }

        public void ChangeState<T>() where T : State, new()
        {
            var stateType = typeof(T);
            m_states.TryGetValue(stateType, out State nextState);

            if (nextState == null) return;

            if (m_current != null) m_current.OnExit(nextState);
            nextState.LastState = m_current;
            nextState.OnEnter(m_current);
            m_current = nextState;
        }

        public T GetState<T>() where T : State, new()
        {
            m_states.TryGetValue(typeof(T), out var value);
            return value as T;
        }

        public void Update()
        {
            m_current?.OnUpdate();
            foreach (var state in m_states.Values)
            {
                if (state == m_current) continue;
                state.AnyStateUpdate(m_current);
            }
        }

        public abstract class State
        {
            public SimpleFSM<HOST> FSM { get; private set; }
            public HOST Host => FSM.Host;
            protected virtual void OnInit() { }
            public virtual void OnEnter(State preState) { }
            public virtual void OnExit(State nextState) { }
            public virtual void OnUpdate() { }
            public virtual void AnyStateUpdate(State currentState) { }

            public State LastState { get; set; }

            protected State() { }
            public static State CreateState<T>(SimpleFSM<HOST> fsm) where T : State, new()
            {
                var state = new T()
                {
                    FSM = fsm
                };

                state.OnInit();

                return state;
            }

        }
    }
}