/*
 * (C) Copyright 1996- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

/// @file ThreadSingleton.h
/// @author Baudouin Raoult

#ifndef eckit_ThreadSingleton_h
#define eckit_ThreadSingleton_h

#include <pthread.h>

#include "eckit/exception/Exceptions.h"
#include "eckit/memory/NonCopyable.h"


namespace eckit {

//----------------------------------------------------------------------------------------------------------------------

template <typename TYPE>
struct NewAlloc0 {
    TYPE* operator()() { return new TYPE(); }
};

template <typename TYPE, typename P1>
struct NewAlloc1 {
    NewAlloc1(const P1& p1) :
        p1_(p1) {}
    TYPE* operator()() { return new TYPE(p1_); }
    P1 p1_;
};

//----------------------------------------------------------------------------------------------------------------------

template <typename T, typename A = NewAlloc0<T> >
class ThreadSingleton : private NonCopyable {
public:
    ThreadSingleton();
    ThreadSingleton(const A& alloc);

    ~ThreadSingleton();

    T& instance();

private:  // members
    A alloc_;

private:  // class members
    static pthread_once_t once_;
    static pthread_key_t key_;

private:  // class methods
    static void init(void);
    static void cleanUp(void*);
};

//----------------------------------------------------------------------------------------------------------------------

template <typename T, typename A>
pthread_once_t ThreadSingleton<T, A>::once_ = PTHREAD_ONCE_INIT;
template <typename T, typename A>
pthread_key_t ThreadSingleton<T, A>::key_;

template <typename T, typename A>
ThreadSingleton<T, A>::ThreadSingleton() :
    alloc_(A()) {}

template <typename T, typename A>
ThreadSingleton<T, A>::ThreadSingleton(const A& alloc) :
    alloc_(alloc) {}

template <typename T, typename A>
ThreadSingleton<T, A>::~ThreadSingleton() {
    ::pthread_once(&once_, init);

    T* value = (T*)::pthread_getspecific(key_);
    if (value) {
        ::pthread_key_delete(key_);
        delete value;
    }
}

template <typename T, typename A>
T& ThreadSingleton<T, A>::instance() {
    ::pthread_once(&once_, init);

    T* value = (T*)::pthread_getspecific(key_);
    if (!value) {
        value = alloc_();
        THRCALL(::pthread_setspecific(key_, value));
    }

    return *value;
}

template <typename T, typename A>
void ThreadSingleton<T, A>::cleanUp(void* data) {
    delete (T*)data;
    ::pthread_setspecific(key_, 0);
}

template <typename T, typename A>
void ThreadSingleton<T, A>::init() {
    ::pthread_key_create(&key_, cleanUp);
}

//----------------------------------------------------------------------------------------------------------------------

}  // namespace eckit

#endif
