/*************************************************************
 *       Unity Singleton Class (c) by ClockStone 2011        *
 * 
 * Allows to use a script components like a singleton
 * 
 * Usage:
 * 
 * Derive your script class MyScriptClass from 
 * SingletonMonoBehaviour<MyScriptClass>
 * 
 * Access the script component using the static function
 * MyScriptClass.Instance
 * 
 * use the static function SetSingletonAutoCreate( GameObject )
 * to specify a GameObject - containing the MyScriptClass component -  
 * that should be instantiated in case an instance is requested and 
 * and no objects exists with the MyScriptClass component.
 * 
 * ***********************************************************/

using System;
using UnityEngine;

#pragma warning disable 1591 // undocumented XML code warning

public class UnitySingleton<T>
    where T : MonoBehaviour
{

#if UNITY_FLASH
    static UnityEngine.Object _instance;
#else
    static T _instance;
    
#endif

    static internal Type _myType = typeof( T ); // not working for Flash builds. Requires SetSingletonType() for correct Flash support

    static internal GameObject _autoCreatePrefab;
    static private  int _GlobalInstanceCount = 0;
    static private bool _awakeSingletonCalled = false;


    static public T GetSingleton( bool throwErrorIfNotFound, bool autoCreate )
    {
        if ( !_instance ) // Unity operator to check if object was destroyed, 
        {
            UnityEngine.Object component;

#if UNITY_FLASH
            if( _myType == null )
            {
                Debug.LogError( "Flash builds require SetSingletonType() to be called!" );
                return null;
            }
#endif 
            component = GameObject.FindObjectOfType( _myType );

            if ( !component )
            {
                if ( autoCreate && _autoCreatePrefab != null )
                {
                    GameObject go = (GameObject) GameObject.Instantiate( _autoCreatePrefab );
                    go.name = _autoCreatePrefab.name; // removes "(clone)"

                    component = GameObject.FindObjectOfType( _myType );

                    if ( !component )
                    {
                        Debug.LogError( "Auto created object does not have component " + _myType.Name );
                        return null;
                    }
                }
                else
                {
                    if ( throwErrorIfNotFound )
                    {
                        Debug.LogError( "No singleton component " + _myType.Name + " found in the scene." );
                    }
                    return null;
                }
            }
            else
            {
                _AwakeSingleton( component as T);
            }

#if UNITY_FLASH
            _instance = component;
#else
            _instance = (T) component;
#endif
        }

        return (T)_instance;
    }

    private UnitySingleton( )
    { }

#if UNITY_FLASH
    static internal void _Awake( UnityEngine.Object instance )
#else
    static internal void _Awake( T instance )
#endif
    {
        _GlobalInstanceCount++;
        if ( _GlobalInstanceCount > 1 )
        {
            Debug.LogError( "More than one instance of SingletonMonoBehaviour " + typeof( T ).Name );
        } else 
            _instance = instance;

        _AwakeSingleton( instance as T);
    }

    static internal void _Destroy()
    {
        if ( _GlobalInstanceCount > 0 )
        {
            _GlobalInstanceCount--;
            if ( _GlobalInstanceCount == 0 )
            {
                _awakeSingletonCalled = false;
                _instance = null;
            }
        }
        else
        {
            // can happen if an exception occured that prevented correct instance counting 
            // Debug.LogWarning( "_GlobalInstanceCount < 0" );
        }
    }

    static private void _AwakeSingleton( T instance )
    {
        if ( !_awakeSingletonCalled )
        {
            _awakeSingletonCalled = true;
            instance.SendMessage( "AwakeSingleton", SendMessageOptions.DontRequireReceiver );
        }
    }
}


/// <summary>
/// Provides singleton-like access to a unique instance of a MonoBehaviour. <para/>
/// </summary>
/// <example>
/// Derive your own class from SingletonMonoBehaviour. <para/>
/// <code>
/// public class MyScriptClass : SingletonMonoBehaviour&lt;MyScriptClass&gt;
/// {
///     public MyScriptClass()
///     {
///         MyScriptClass.SetSingletonType( typeof( MyScriptClass ) ); // workaround for Flash
///     }
///     public void MyFunction() { }
///     protected override void Awake()
///     {
///         base.Awake();
///     }
///     void AwakeSingleton()
///     {
///         // all initialisation code here. Will get called from Awake() by singleton.
///         // Can get called before Awake() if an instance is accessed in an Awake() function which
///         // was called earlier
///     }
/// }
/// </code>
/// <para/>
/// access the instance by writing
/// <code>
/// MyScriptClass.Instance.MyFunction();
/// </code>
/// </example>
/// <typeparam name="T">Your singleton MonoBehaviour</typeparam>
/// <remarks>
/// Makes sure that an instance is available from other Awake() calls even before the singleton's Awake()
/// was called. ( Requires AwakeSingleton() !)
/// </remarks>
public abstract class SingletonMonoBehaviour<T> : MonoBehaviour
    where T : SingletonMonoBehaviour<T>
{

    /// <summary>
    /// Gets the singleton instance.
    /// </summary>
    /// <returns>
    /// A reference to the instance if it exists, otherwise <c>null</c>
    /// </returns>
    /// <remarks>
    /// Outputs an error to the debug log if no instance was found.
    /// </remarks>
    static public T Instance
    { get { return UnitySingleton<T>.GetSingleton( true, true ); } }

    /// <summary>
    /// Checks if an instance of this MonoBehaviour exists.
    /// </summary>
    /// <returns>
    /// A reference to the instance if it exists, otherwise <c>null</c>
    /// </returns>
    static public T DoesInstanceExist( )
    {
        return UnitySingleton<T>.GetSingleton( false, false );
    }

    /// <summary>
    /// Activates the singleton instance.
    /// </summary>
    /// <remarks>
    /// Call this function if you set an singleton object inactive before ever accessing the <c>Instance</c>. This is 
    /// required because Unity does not (yet) offer a way to find inactive game objects.
    /// </remarks>
    static public void ActivateSingletonInstance() // 
    {
        UnitySingleton<T>.GetSingleton( true, true );
    }

    /// <summary>
    /// Sets the object to be instantiated automatically if no instance of the singleton is found.
    /// </summary>
    /// <param name="autoCreatePrefab">The prefab to be instantiated automatically.</param>
    /// <remarks>
    /// Either the game object itself or one of its child objects must contain the singleton component
    /// </remarks>
    static public void SetSingletonAutoCreate( GameObject autoCreatePrefab )
    {
        UnitySingleton<T>._autoCreatePrefab = autoCreatePrefab;
    }

    /// <summary>
    /// Only required for Flash builds. If this function is not called by the class deriving from 
    /// SingletonMonoBehaviour in the constructor the singleton can not be found by GetSingleton(...)
    /// </summary>
    /// <param name="type"></param>
    static public void SetSingletonType( Type type )
    {
        UnitySingleton<T>._myType = type;
    }

    protected virtual void Awake() // should be called in derived class
    {
        UnitySingleton<T>._Awake( (T) this );
        //Debug.Log( "Awake: " + this.GetType().Name );
    }

    protected virtual void OnDestroy()  // should be called in derived class
    {
        UnitySingleton<T>._Destroy();
    }
}



