// @(#)root/thread:$Id$
// Authors: Enric Tejedor CERN  12/09/2016
//          Philippe Canal FNAL 12/09/2016

/*************************************************************************
 * Copyright (C) 1995-2016, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/

#ifndef ROOT_TReentrantRWLock
#define ROOT_TReentrantRWLock

#include "ThreadLocalStorage.h"
#include "ROOT/TSpinMutex.hxx"
#include "TVirtualRWMutex.h"

#if defined(__linux__) && defined(__GNUC__)
#include <climits>
#ifndef _POSIX_SEM_VALUE_MAX
// workaround for cases where gcc's limits.h is pulled in by clang's include_next,
// as opposed to the system limits.h that defines the posix limits
#if __has_include (<syslimits.h>)
#include <syslimits.h>
#endif
#endif
#endif

#include <atomic>
#include <condition_variable>
#include <thread>
#include <unordered_map>

namespace CppyyLegacy {
namespace Internal {
struct UniqueLockRecurseCount {
   using Hint_t = TVirtualRWMutex::Hint_t;

   struct LocalCounts {
      size_t fReadersCount = 0;
      bool fIsWriter = false;
   };
   size_t fWriteRecurse = 0; ///<! Number of re-entry in the lock by the same thread.

   UniqueLockRecurseCount();

   using local_t = LocalCounts*;

   local_t GetLocal(){
      TTHREAD_TLS_DECL(LocalCounts, gLocal);
      return &gLocal;
   }

   Hint_t *IncrementReadCount(local_t &local) {
      ++(local->fReadersCount);
      return reinterpret_cast<TVirtualRWMutex::Hint_t *>(&(local->fReadersCount));
   }

   template <typename MutexT>
   Hint_t *IncrementReadCount(local_t &local, MutexT &) {
      return IncrementReadCount(local);
   }

   Hint_t *DecrementReadCount(local_t &local) {
      --(local->fReadersCount);
      return reinterpret_cast<TVirtualRWMutex::Hint_t *>(&(local->fReadersCount));
   }

   template <typename MutexT>
   Hint_t *DecrementReadCount(local_t &local, MutexT &) {
      return DecrementReadCount(local);
   }

   void ResetReadCount(local_t &local, int newvalue) {
      local->fReadersCount = newvalue;
   }

   bool IsCurrentWriter(local_t &local) { return local->fIsWriter; }
   bool IsNotCurrentWriter(local_t &local) { return !local->fIsWriter; }

   void SetIsWriter(local_t &local)
   {
      ++fWriteRecurse;
      local->fIsWriter = true;
   }

   void DecrementWriteCount() { --fWriteRecurse; }

   void ResetIsWriter(local_t &local) { local->fIsWriter = false; }

   size_t &GetLocalReadersCount(local_t &local) { return local->fReadersCount; }
};

struct RecurseCounts {
   using Hint_t = TVirtualRWMutex::Hint_t;
   using ReaderColl_t = std::unordered_map<std::thread::id, size_t>;
   size_t fWriteRecurse; ///<! Number of re-entry in the lock by the same thread.

   std::thread::id fWriterThread; ///<! Holder of the write lock
   ReaderColl_t fReadersCount;    ///<! Set of reader thread ids

   using local_t = std::thread::id;

   local_t GetLocal() const { return std::this_thread::get_id(); }

   Hint_t *IncrementReadCount(local_t &local) {
      auto &count = fReadersCount[local];
      ++(count);
      return reinterpret_cast<TVirtualRWMutex::Hint_t *>(&count);
   }

   template <typename MutexT>
   Hint_t *IncrementReadCount(local_t &local, MutexT &mutex)
   {
      std::unique_lock<MutexT> lock(mutex);
      return IncrementReadCount(local);
   }

   Hint_t *DecrementReadCount(local_t &local) {
      auto &count = fReadersCount[local];
      --count;
      return reinterpret_cast<TVirtualRWMutex::Hint_t *>(&count);
   }

   template <typename MutexT>
   Hint_t *DecrementReadCount(local_t &local, MutexT &mutex)
   {
      std::unique_lock<MutexT> lock(mutex);
      return DecrementReadCount(local);
   }

   void ResetReadCount(local_t &local, int newvalue) {
      fReadersCount[local] = newvalue;
   }

   bool IsCurrentWriter(local_t &local) const { return fWriterThread == local; }
   bool IsNotCurrentWriter(local_t &local) const { return fWriterThread != local; }

   void SetIsWriter(local_t &local)
   {
      ++fWriteRecurse;
      fWriterThread = local;
   }

   void DecrementWriteCount() { --fWriteRecurse; }

   void ResetIsWriter(local_t & /* local */) { fWriterThread = std::thread::id(); }

   size_t &GetLocalReadersCount(local_t &local) { return fReadersCount[local]; }
};

} // Internal

template <typename MutexT = CppyyLegacy::TSpinMutex, typename RecurseCountsT = Internal::RecurseCounts>
class TReentrantRWLock {
private:
   std::atomic<int> fReaders;           ///<! Number of readers
   std::atomic<int> fReaderReservation; ///<! A reader wants access
   std::atomic<int> fWriterReservation; ///<! A writer wants access
   std::atomic<bool> fWriter;           ///<! Is there a writer?
   MutexT fMutex;                       ///<! RWlock internal mutex
   std::condition_variable_any fCond;   ///<! RWlock internal condition variable

   RecurseCountsT fRecurseCounts;        ///<! Trackers for re-entry in the lock by the same thread.

   // size_t fWriteRecurse;                ///<! Number of re-entry in the lock by the same thread.

   // std::thread::id fWriterThread; ///<! Holder of the write lock
   // ReaderColl_t fReadersCount;    ///<! Set of reader thread ids

   void AssertReadCountLocIsFromCurrentThread(const size_t* presumedLocalReadersCount);

public:
   using State = TVirtualRWMutex::State;
   using StateDelta = TVirtualRWMutex::StateDelta;

   ////////////////////////////////////////////////////////////////////////
   /// Regular constructor.
   TReentrantRWLock() : fReaders(0), fReaderReservation(0), fWriterReservation(0), fWriter(false) {}

   TVirtualRWMutex::Hint_t *ReadLock();
   void ReadUnLock(TVirtualRWMutex::Hint_t *);
   TVirtualRWMutex::Hint_t *WriteLock();
   void WriteUnLock(TVirtualRWMutex::Hint_t *);

   std::unique_ptr<State> GetStateBefore();
   std::unique_ptr<StateDelta> Rewind(const State &earlierState);
   void Apply(std::unique_ptr<StateDelta> &&delta);
};

} // namespace CppyyLegacy

#endif
