/*
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

#pragma once

#include <pthread.h>
#include <errno.h>

#include <fb/assert.h>

namespace facebook {

///////////////////////////////////////////////////////////////////////////////

/**
 * A thread-local object is a "global" object within a thread. This is useful
 * for writing apartment-threaded code, where nothing is actullay shared
 * between different threads (hence no locking) but those variables are not
 * on stack in local scope. To use it, just do something like this,
 *
 *   ThreadLocal<MyClass> static_object;
 *     static_object->data_ = ...;
 *     static_object->doSomething();
 *
 *   ThreadLocal<int> static_number;
 *     int value = *static_number;
 *
 * So, syntax-wise it's similar to pointers. T can be primitive types, and if
 * it's a class, there has to be a default constructor.
 */
template<typename T>
class ThreadLocal {
public:
  /**
   * Constructor that has to be called from a thread-neutral place.
   */
  ThreadLocal() :
    m_key(0),
    m_cleanup(OnThreadExit) {
    initialize();
  }

  /**
   * As above but with a custom cleanup function
   */
  typedef void (*CleanupFunction)(void* obj);
  explicit ThreadLocal(CleanupFunction cleanup) :
    m_key(0),
    m_cleanup(cleanup) {
    FBASSERT(cleanup);
    initialize();
  }

  /**
   * Access object's member or method through this operator overload.
   */
  T *operator->() const {
    return get();
  }

  T &operator*() const {
    return *get();
  }

  T *get() const {
    return (T*)pthread_getspecific(m_key);
  }

  T* release() {
    T* obj = get();
    pthread_setspecific(m_key, NULL);
    return obj;
  }

  void reset(T* other = NULL) {
    T* old = (T*)pthread_getspecific(m_key);
    if (old != other) {
      FBASSERT(m_cleanup);
      m_cleanup(old);
      pthread_setspecific(m_key, other);
    }
  }

private:
  void initialize() {
    int ret = pthread_key_create(&m_key, m_cleanup);
    if (ret != 0) {
      const char *msg = "(unknown error)";
      switch (ret) {
      case EAGAIN:
        msg = "PTHREAD_KEYS_MAX (1024) is exceeded";
        break;
      case ENOMEM:
        msg = "Out-of-memory";
        break;
      }
      (void) msg;
      FBASSERTMSGF(0, "pthread_key_create failed: %d %s", ret, msg);
    }
  }

  static void OnThreadExit(void *obj) {
    if (NULL != obj) {
      delete (T*)obj;
    }
  }

  pthread_key_t m_key;
  CleanupFunction m_cleanup;
};

}
