using System;
using System.Collections.Generic;

/// <summary>
///     Simple FSM with callbacks.
///     Uses class types to navigate.
/// </summary>
public class ClassFiniteStateMachine<TState> where TState : State
{
    /// <summary>
    ///     Collection of <see cref="State" /> cached by their <see cref="Type" />.
    /// </summary>
    private readonly Dictionary<Type, TState> _states;

    public ClassFiniteStateMachine()
    {
        _states = new Dictionary<Type, TState>();
        Locked = false;
    }

    /// <summary>
    ///     Allows control to lock the current <see cref="State" />, so that it cannot change.
    /// </summary>
    public bool Locked { get; set; }

    /// <summary>
    ///     Returns the active <see cref="State" />'s <see cref="Type" />.
    /// </summary>
    public Type ActiveType
    {
        get { return ActiveState.GetType(); }
    }

    /// <summary>
    ///     Returns the active <see cref="State" />.
    /// </summary>
    public TState ActiveState { get; private set; }

    /// <summary>
    ///     Called on every <see cref="State" /> change.
    ///     The first state will say it's switched from itself to itself.
    /// </summary>
    public event Action<TState, TState> StateChanged;

    /// <summary>
    ///     Pass in the instance of the new <see cref="State" /> you wish to create.
    ///     If an instance of the same <see cref="Type" /> already exists, an <see cref="Exception" /> will be thrown.
    /// </summary>
    /// <param name="instance">Instance of new state.</param>
    public void AddState(TState instance)
    {
        var type = instance.GetType();

        if (!_states.ContainsKey(type))
        {
            //Debug.Log("Creating state: " + type);
            _states.Add(type, instance);
        }
        else
            throw new Exception("You have already defined a state of type: " + type + "!");
    }

    /// <summary>
    ///     Utility for calling the <see cref="State.StateUpdate" /> delegate of the <see cref="ActiveState" />.
    /// </summary>
    public void UpdateActiveState()
    {
        if (ActiveState != null)
            if (ActiveState.StateUpdate != null)
                ActiveState.StateUpdate();
    }

    /// <summary>
    ///     Attempts to set a new state.
    ///     It will exit the previous state, including calling of its exit state event.
    /// </summary>
    public T SetState<T>() where T : State
    {
        if (Locked)
            return null;

        var oldState = ActiveState;

        var newStateType = typeof (T);

        // Find and set new state.
        if (!_states.ContainsKey(newStateType))
            throw new Exception("You are trying to enter state: " + newStateType + ", but it does not exist!");

        ActiveState = _states[newStateType];

        // Show the last state to the next.
        if (StateChanged != null)
        {
            // Changing the code below to oldState ?? ActiveState causes this:
            // VerificationException: Error verifying ClassFiniteStateMachine`1:SetState<T> (): 
            // Argument type Complex not valid for brtrue/brfalse at 0x006a
            // ReSharper disable once ConvertConditionalTernaryToNullCoalescing
            StateChanged(oldState != null ? oldState : ActiveState, ActiveState);
        }

        // Call exit on old.
        if (oldState != null)
        {
            // Call exit on old.
            if (oldState.StateExit != null)
            {
                oldState.StateExit();
            }
        }

        // Call enter on new.
        if (ActiveState.StateEnter != null)
            ActiveState.StateEnter();

        return ActiveState as T;
    }
}