#pragma once

#include <Core/Common/NonCopyable.h>
#include <Core/Common/NonMovable.h>
#include <Core/Common/NonNullPtr.h>

namespace Juliet
{
    // Two usages : Inherit from Singleton or use the static methods to register to it when you want.
    // 1)
    // class IThing {};
    // class Thing : public Singleton::AutoRegister<IThing> {};
    template <typename Type>
    class Singleton final : public NonMovable, public NonCopyable
    {
      public:
        static void  Register(NonNullPtr<Type> type);
        static void  Unregister(NonNullPtr<Type> type);
        static Type* Get();

        class AutoRegister : public Type
        {
          public:
            AutoRegister();
            virtual ~AutoRegister();
        };

      private:
        static Type* sInstance;
        static bool  sIsRegistered;
    };

    template <typename Type>
    bool Singleton<Type>::sIsRegistered = false;

    template <typename Type>
    Type* Singleton<Type>::sInstance = nullptr;

    template <typename Type>
    void Singleton<Type>::Register(NonNullPtr<Type> type)
    {
        Assert(!Get() && "Singleton is already registered. Make sure to only register it once");
        sInstance     = type.Get();
        sIsRegistered = true;
    }

    template <typename Type>
    void Singleton<Type>::Unregister(NonNullPtr<Type> type)
    {
        Assert(sIsRegistered && "Trying to unregister a singleton that is not registered");
        Assert(sInstance == type &&
               "Trying to unregister an instance that is different from the one currently registered");

        sInstance     = nullptr;
        sIsRegistered = false;
    }

    template <typename Type>
    Type* Singleton<Type>::Get()
    {
        if (sIsRegistered)
        {
            return sInstance;
        }
        return nullptr;
    }

    template <typename Type>
    Singleton<Type>::AutoRegister::AutoRegister()
    {
        Singleton<Type>::Register(this);
    }

    template <typename Type>
    Singleton<Type>::AutoRegister::~AutoRegister()
    {
        Singleton<Type>::Unregister(this);
    }
} // namespace Juliet
