/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2004, 2005, 2007 StatPro Italia srl

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

/*! \file singleton.hpp
    \brief basic support for the singleton pattern
*/

#ifndef quantlib_singleton_hpp
#define quantlib_singleton_hpp

#include <ql/qldefines.hpp>

#ifdef QL_ENABLE_SESSIONS
#    include <boost/thread/locks.hpp>
#    include <boost/thread/shared_mutex.hpp>
#else
#    ifdef QL_ENABLE_SINGLETON_THREAD_SAFE_INIT
#        include <boost/atomic.hpp>
#        include <boost/thread/mutex.hpp>
#        if !defined(BOOST_ATOMIC_ADDRESS_LOCK_FREE)
#            ifdef BOOST_MSVC
#                pragma message("Thread-safe singleton initialization may degrade performances.")
#            else
#                warning Thread-safe singleton initialization may degrade performances.
#            endif
#        endif
#    endif
#endif

#include <ql/shared_ptr.hpp>
#include <ql/types.hpp>
#include <map>

namespace QuantLib {

// This allows to define a different type if needed, while keeping
// backwards compatibility with the current implementation.
// For instance, one might create a file threadkey.hpp with:
//
// #include <pthread.h>
// #define QL_THREAD_KEY pthread_t
//
// and then compile QuantLib with the option -DQL_INCLUDE_FIRST=threadkey.hpp
// to have that file included by qldefines.hpp and thus this one.
#if defined(QL_THREAD_KEY)
    typedef QL_THREAD_KEY ThreadKey;
#else
    typedef Integer ThreadKey;
#endif

#if defined(QL_ENABLE_SESSIONS)
    // definition must be provided by the user
    ThreadKey sessionId();
#endif

    //! Basic support for the singleton pattern.
    /*! The typical use of this class is:
        \code
        class Foo : public Singleton<Foo> {
            friend class Singleton<Foo>;
          private:
            Foo() {}
          public:
            ...
        };
        \endcode
        which, albeit sub-optimal, frees one from the concerns of
        creating and managing the unique instance and can serve later
        as a single implemementation point should synchronization
        features be added.

        Global can be used to distinguish Singletons that are local to a session
        (Global = false) or that are global across all sessions (B = true).
        This is only relevant if QL_ENABLE_SESSIONS is enabled.

        \ingroup patterns
    */
    template <class T, class Global = std::integral_constant<bool, false> >
    class Singleton {
      public:
        // disable copy/move
        Singleton(const Singleton&) = delete;
        Singleton(Singleton&&) = delete;
        Singleton& operator=(const Singleton&) = delete;
        Singleton& operator=(Singleton&&) = delete;

        //! access to the unique instance
        static T& instance();

      protected:
        Singleton() = default;

      private:
#ifdef QL_ENABLE_SESSIONS
        // construct on first use to avoid static initialization order fiasco
        static std::map<ThreadKey, ext::shared_ptr<T> >& m_instances() {
            static std::map<ThreadKey, ext::shared_ptr<T> > instances;
            return instances;
        }
        static boost::shared_mutex& m_mutex() {
            static boost::shared_mutex mutex;
            return mutex;
        }
#else
#    ifdef QL_ENABLE_SINGLETON_THREAD_SAFE_INIT
        static boost::atomic<T*>& m_instance() {
            static boost::atomic<T*> instance;
            return instance;
        }
        static boost::mutex& m_mutex() {
            static boost::mutex mutex;
            return mutex;
        }
#    else
        static ext::shared_ptr<T>& m_instance() {
            static ext::shared_ptr<T> instance;
            return instance;
        }
#    endif
#endif
    };

    // template definitions

    template <class T, class Global>
    T& Singleton<T, Global>::instance() {

#ifdef QL_ENABLE_SESSIONS
        ThreadKey id = sessionId();
        {
            boost::shared_lock<boost::shared_mutex> shared_lock(m_mutex());
            auto instance = Global() ? m_instances().begin() : m_instances().find(id);
            if (instance != m_instances().end())
                return *instance->second;
        }
        {
            boost::unique_lock<boost::shared_mutex> uniqueLock(m_mutex());
            auto instance = Global() ? m_instances().begin() : m_instances().find(id);
            if (instance != m_instances().end())
                return *instance->second;
            auto tmp = ext::shared_ptr<T>(new T);
            m_instances()[id] = tmp;
            return *tmp;
        }
#else
#    ifdef QL_ENABLE_SINGLETON_THREAD_SAFE_INIT
        // thread safe double checked locking pattern with atomic memory calls
        T* instance = m_instance().load(boost::memory_order_consume);
        if (!instance) {
            boost::mutex::scoped_lock guard(m_mutex());
            instance = m_instance().load(boost::memory_order_consume);
            if (!instance) {
                instance = new T();
                m_instance().store(instance, boost::memory_order_release);
            }
        }
        return *instance;
#    else
        if (m_instance() == nullptr)
            m_instance() = ext::shared_ptr<T>(new T);
        return *m_instance();
#    endif
#endif
    }
}

#endif
