/*
 * Copyright (C) 2009 Jian Li <jianli@chromium.org>
 * Copyright (C) 2012 Patrick Gansterer <paroga@paroga.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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 GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#include "ThreadSpecific.h"

#if OS(WIN)

#include "StdLibExtras.h"
#include "ThreadingPrimitives.h"
#include "wtf/Allocator.h"
#include "wtf/DoublyLinkedList.h"

namespace WTF {

static DoublyLinkedList<PlatformThreadSpecificKey>& destructorsList() {
  DEFINE_STATIC_LOCAL(DoublyLinkedList<PlatformThreadSpecificKey>, staticList,
                      ());
  return staticList;
}

static Mutex& destructorsMutex() {
  DEFINE_STATIC_LOCAL(Mutex, staticMutex, ());
  return staticMutex;
}

class PlatformThreadSpecificKey
    : public DoublyLinkedListNode<PlatformThreadSpecificKey> {
  USING_FAST_MALLOC(PlatformThreadSpecificKey);
  WTF_MAKE_NONCOPYABLE(PlatformThreadSpecificKey);

 public:
  friend class DoublyLinkedListNode<PlatformThreadSpecificKey>;

  PlatformThreadSpecificKey(void (*destructor)(void*))
      : m_destructor(destructor) {
    m_tlsKey = TlsAlloc();
    if (m_tlsKey == TLS_OUT_OF_INDEXES)
      CRASH();
  }

  ~PlatformThreadSpecificKey() { TlsFree(m_tlsKey); }

  void setValue(void* data) { TlsSetValue(m_tlsKey, data); }
  void* value() { return TlsGetValue(m_tlsKey); }

  void callDestructor() {
    if (void* data = value())
      m_destructor(data);
  }

 private:
  void (*m_destructor)(void*);
  DWORD m_tlsKey;
  PlatformThreadSpecificKey* m_prev;
  PlatformThreadSpecificKey* m_next;
};

long& tlsKeyCount() {
  static long count;
  return count;
}

DWORD* tlsKeys() {
  static DWORD keys[kMaxTlsKeySize];
  return keys;
}

void threadSpecificKeyCreate(ThreadSpecificKey* key,
                             void (*destructor)(void*)) {
  *key = new PlatformThreadSpecificKey(destructor);

  MutexLocker locker(destructorsMutex());
  destructorsList().push(*key);
}

void threadSpecificKeyDelete(ThreadSpecificKey key) {
  MutexLocker locker(destructorsMutex());
  destructorsList().remove(key);
  delete key;
}

void threadSpecificSet(ThreadSpecificKey key, void* data) {
  key->setValue(data);
}

void* threadSpecificGet(ThreadSpecificKey key) {
  return key->value();
}

void ThreadSpecificThreadExit() {
  for (long i = 0; i < tlsKeyCount(); i++) {
    // The layout of ThreadSpecific<T>::Data does not depend on T. So we are
    // safe to do the static cast to ThreadSpecific<int> in order to access its
    // data member.
    ThreadSpecific<int>::Data* data =
        static_cast<ThreadSpecific<int>::Data*>(TlsGetValue(tlsKeys()[i]));
    if (data)
      data->destructor(data);
  }

  MutexLocker locker(destructorsMutex());
  PlatformThreadSpecificKey* key = destructorsList().head();
  while (key) {
    PlatformThreadSpecificKey* nextKey = key->next();
    key->callDestructor();
    key = nextKey;
  }
}

}  // namespace WTF

#endif  // OS(WIN)
