// (C) Copyright 2012 Vicente J. Botet Escriba
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)


#ifndef BOOST_THREAD_TESTABLE_LOCKABLE_HPP
#define BOOST_THREAD_TESTABLE_LOCKABLE_HPP

#include <boost/thread/detail/config.hpp>

#include <boost/thread/thread_only.hpp>

#include <boost/atomic.hpp>
#include <boost/assert.hpp>

#include <boost/config/abi_prefix.hpp>

namespace boost
{
  /**
   * Based on Associate Mutexes with Data to Prevent Races, By Herb Sutter, May 13, 2010
   * http://www.drdobbs.com/windows/associate-mutexes-with-data-to-prevent-r/224701827?pgno=3
   *
   * Make our mutex testable if it isn't already.
   *
   * Many mutex services (including boost::mutex) don't provide a way to ask,
   * "Do I already hold a lock on this mutex?"
   * Sometimes it is needed to know if a method like is_locked to be available.
   * This wrapper associates an arbitrary lockable type with a thread id that stores the ID of the thread that
   * currently holds the lockable. The thread id initially holds an invalid value that means no threads own the mutex.
   * When we acquire a lock, we set the thread id; and when we release a lock, we reset it back to its default no id state.
   *
   */
  template <typename Lockable>
  class testable_mutex
  {
    Lockable mtx_;
    atomic<thread::id> id_;
  public:
    /// the type of the wrapped lockable
    typedef Lockable lockable_type;

    /// Non copyable
    BOOST_THREAD_NO_COPYABLE(testable_mutex)

    testable_mutex() : id_(thread::id()) {}

    void lock()
    {
      mtx_.lock();
      id_ = this_thread::get_id();
    }

    void unlock()
    {
      BOOST_ASSERT(is_locked_by_this_thread());
      id_ = thread::id();
      mtx_.unlock();
    }

    bool try_lock()
    {
      if (mtx_.try_lock())
      {
        id_ = this_thread::get_id();
        return true;
      }
      else
      {
        return false;
      }
    }
#ifdef BOOST_THREAD_USES_CHRONO
    template <class Rep, class Period>
    bool try_lock_for(const chrono::duration<Rep, Period>& rel_time)
    {
      if (mtx_.try_lock_for(rel_time))
      {
        id_ = this_thread::get_id();
        return true;
      }
      else
      {
        return false;
      }
    }
    template <class Clock, class Duration>
    bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time)
    {
      if (mtx_.try_lock_until(abs_time))
      {
        id_ = this_thread::get_id();
        return true;
      }
      else
      {
        return false;
      }
    }
#endif

    bool is_locked_by_this_thread() const
    {
      return this_thread::get_id() == id_;
    }
    bool is_locked() const
    {
      return ! (thread::id() == id_);
    }

    thread::id get_id() const
    {
      return id_;
    }

    // todo add the shared and upgrade mutex functions
  };

  template <typename Lockable>
  struct is_testable_lockable : false_type
  {};

  template <typename Lockable>
  struct is_testable_lockable<testable_mutex<Lockable> > : true_type
  {};

//  /**
//   * Overloaded function used to check if the mutex is locked when it is testable and do nothing otherwise.
//   *
//   * This function is used usually to assert the pre-condition when the function can only be called when the mutex
//   * must be locked by the current thread.
//   */
//  template <typename Lockable>
//  bool is_locked_by_this_thread(testable_mutex<Lockable> const& mtx)
//  {
//    return mtx.is_locked();
//  }
//  template <typename Lockable>
//  bool is_locked_by_this_thread(Lockable const&)
//  {
//    return true;
//  }
}

#include <boost/config/abi_suffix.hpp>

#endif // header
