// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

#ifndef  BUTIL_THREAD_KEY_H
#define  BUTIL_THREAD_KEY_H

#include <algorithm>
#include <limits>
#include <pthread.h>
#include <stdlib.h>
#include <vector>
#include "butil/scoped_lock.h"
#include "butil/type_traits.h"

namespace butil {

typedef void (*DtorFunction)(void *);

class ThreadKey {
public:
    friend int thread_key_create(ThreadKey& thread_key, DtorFunction dtor);
    friend int thread_key_delete(ThreadKey& thread_key);
    friend int thread_setspecific(ThreadKey& thread_key, void* data);
    friend void* thread_getspecific(ThreadKey& thread_key);

    static constexpr size_t InvalidID = std::numeric_limits<size_t>::max();
    static constexpr size_t InitSeq = 0;

    constexpr ThreadKey() : _id(InvalidID), _seq(InitSeq) {}

    ~ThreadKey() {
        Reset();
    }

    ThreadKey(ThreadKey&& other) noexcept
        : _id(other._id)
        , _seq(other._seq) {
        other.Reset();
    }

    ThreadKey& operator=(ThreadKey&& other) noexcept;

    ThreadKey(const ThreadKey& other) = delete;
    ThreadKey& operator=(const ThreadKey& other) = delete;

    bool Valid() const;

    void Reset() {
        _id = InvalidID;
        _seq = InitSeq;
    }

private:
    size_t _id; // Key id.
    // Sequence number form g_thread_keys set in thread_key_create.
    size_t _seq;
};

struct ThreadKeyInfo {
    ThreadKeyInfo() : seq(0), dtor(NULL) {}

    size_t seq; // Already allocated?
    DtorFunction dtor; // Destruction routine.
};

struct ThreadKeyTLS {
    ThreadKeyTLS() : seq(0), data(NULL) {}

    // Sequence number form ThreadKey,
    // set in `thread_setspecific',
    // used to check if the key is valid in `thread_getspecific'.
    size_t seq;
    void* data; // User data.
};

// pthread_key_xxx implication without num limit of key.
int thread_key_create(ThreadKey& thread_key, DtorFunction dtor);
int thread_key_delete(ThreadKey& thread_key);
int thread_setspecific(ThreadKey& thread_key, void* data);
void* thread_getspecific(ThreadKey& thread_key);


template <typename T>
class ThreadLocal {
public:
    ThreadLocal() : ThreadLocal(false) {}

    explicit ThreadLocal(bool delete_on_thread_exit);

    ~ThreadLocal();

    // non-copyable
    ThreadLocal(const ThreadLocal&) = delete;
    ThreadLocal& operator=(const ThreadLocal&) = delete;

    T* get();

    T* operator->() { return get(); }

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

    // Iterate through all thread local objects.
    // Callback, which must accept Args params and return void,
    // will be called under a thread lock.
    template <typename Callback>
    void for_each(Callback&& callback) {
        BAIDU_CASSERT(
            (is_result_void<Callback, T*>::value),
            "Callback must accept Args params and return void");
        BAIDU_SCOPED_LOCK(_mutex);
        for (auto ptr : ptrs) {
            callback(ptr);
        }
    }

    void reset(T* ptr);

    void reset() {
        reset(NULL);
    }

private:
    static void DefaultDtor(void* ptr) {
        if (ptr) {
            delete static_cast<T*>(ptr);
        }
    }

    ThreadKey _key;
    pthread_mutex_t _mutex;
    // All pointers of data allocated by the ThreadLocal.
    std::vector<T*> ptrs;
    // Delete data on thread exit or destructor of ThreadLocal.
    bool _delete_on_thread_exit;
};

template <typename T>
ThreadLocal<T>::ThreadLocal(bool delete_on_thread_exit)
        : _mutex(PTHREAD_MUTEX_INITIALIZER)
        , _delete_on_thread_exit(delete_on_thread_exit) {
    DtorFunction dtor = _delete_on_thread_exit ? DefaultDtor : NULL;
    thread_key_create(_key, dtor);
}


template <typename T>
ThreadLocal<T>::~ThreadLocal() {
    thread_key_delete(_key);
    if (!_delete_on_thread_exit) {
        BAIDU_SCOPED_LOCK(_mutex);
        for (auto ptr : ptrs) {
            DefaultDtor(ptr);
        }
    }
    pthread_mutex_destroy(&_mutex);
}

template <typename T>
T* ThreadLocal<T>::get() {
    T* ptr = static_cast<T*>(thread_getspecific(_key));
    if (!ptr) {
        ptr = new (std::nothrow) T;
        if (!ptr) {
            return NULL;
        }
        int rc = thread_setspecific(_key, ptr);
        if (rc != 0) {
            DefaultDtor(ptr);
            return NULL;
        }
        {
            BAIDU_SCOPED_LOCK(_mutex);
            ptrs.push_back(ptr);
        }
    }
    return ptr;
}

template <typename T>
void ThreadLocal<T>::reset(T* ptr) {
    T* old_ptr = get();
    if (ptr == old_ptr) {
        return;
    }
    if (thread_setspecific(_key, ptr) != 0) {
        return;
    }
    {
        BAIDU_SCOPED_LOCK(_mutex);
        if (ptr) {
            ptrs.push_back(ptr);
        }
        // Remove and delete old_ptr.
        if (old_ptr) {
            auto iter = std::remove(ptrs.begin(), ptrs.end(), old_ptr);
            if (iter != ptrs.end()) {
                ptrs.erase(iter, ptrs.end());
            }
            DefaultDtor(old_ptr);
        }
    }
}

}


#endif // BUTIL_THREAD_KEY_H
