// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.

#pragma once
#define HAVE_IRCD_CTX_UPGRADE_LOCK_H

namespace ircd::ctx
{
	template<class mutex> struct upgrade_lock;
}

/// Upgrade a lock from a shared reader to become a unique writer. This is a
/// two phase process. The first phase is the upgrade, which does not immediately
/// interfere with the shared readers but only one upgrade can exist at a time.
///
/// The second phase is further upgrading the upgrade lock to a unique-lock
/// which blocks out the readers from reacquiring their shared-locks. This lock
/// then has exclusive access to the critical section.
///
/// Upon release of the second phase the readers can reacquire their
/// shared-lock again. Upon release of the first phase another writer can
/// acquire an upgrade-lock. Sometimes it is desirable to not release the first
/// phase to ensure nothing is ABA'ed by another writer while allowing the
/// readers to continue consuming.
///
template<class mutex>
struct ircd::ctx::upgrade_lock
{
	mutex *m {nullptr};

	bool owns_lock() const;

	void lock();
	bool try_lock();
	template<class duration> bool try_lock_for(duration&&);
	template<class time_point> bool try_lock_until(time_point&&);
	void unlock();

	decltype(auto) release() noexcept;

	template<class r, class p> upgrade_lock(mutex &, const std::chrono::time_point<r, p> &);
	template<class r, class p> upgrade_lock(mutex &, const std::chrono::duration<r, p> &);
	upgrade_lock(mutex &, std::try_to_lock_t);
	upgrade_lock(mutex &, std::defer_lock_t);
	upgrade_lock(mutex &, std::adopt_lock_t);
	explicit upgrade_lock(mutex &);
	upgrade_lock() = default;
	upgrade_lock(upgrade_lock &&) noexcept;
	upgrade_lock(const upgrade_lock &) = delete;
	~upgrade_lock() noexcept;
};

namespace ircd
{
	using ctx::upgrade_lock;
}

template<class mutex_type>
inline
ircd::ctx::upgrade_lock<mutex_type>::upgrade_lock(mutex_type &m)
:m{&m}
{
	lock();
}

template<class mutex>
inline
ircd::ctx::upgrade_lock<mutex>::upgrade_lock(mutex &m,
                                             std::defer_lock_t)
:m{&m}
{
}

template<class mutex>
inline
ircd::ctx::upgrade_lock<mutex>::upgrade_lock(mutex &m,
                                             std::adopt_lock_t)
:m{&m}
{
	if(!m->upgrade())
		lock();
}

template<class mutex>
template<class rep,
         class period>
inline
ircd::ctx::upgrade_lock<mutex>::upgrade_lock(mutex &m,
                                             const std::chrono::duration<rep, period> &rel)
:m{&m}
{
	try_lock_for(rel);
}

template<class mutex>
template<class rep,
         class period>
inline
ircd::ctx::upgrade_lock<mutex>::upgrade_lock(mutex &m,
                                             const std::chrono::time_point<rep, period> &abs)
:m{&m}
{
	try_lock_until(abs);
}

template<class mutex>
inline
ircd::ctx::upgrade_lock<mutex>::upgrade_lock(upgrade_lock &&other)
noexcept
:m{other.release()}
{
}

template<class mutex>
inline
ircd::ctx::upgrade_lock<mutex>::~upgrade_lock()
noexcept
{
	if(owns_lock())
		unlock();
}

template<class mutex>
inline decltype(auto)
ircd::ctx::upgrade_lock<mutex>::release()
noexcept
{
	auto *const m{this->m};
	this->m = nullptr;
	return m;
}

template<class mutex>
template<class time_point>
inline bool
ircd::ctx::upgrade_lock<mutex>::try_lock_until(time_point&& tp)
{
	assert(m);
	return m->try_lock_upgrade_until(std::forward<time_point>(tp));
}

template<class mutex>
template<class duration>
inline bool
ircd::ctx::upgrade_lock<mutex>::try_lock_for(duration&& d)
{
	assert(m);
	return m->try_lock_upgrade_for(std::forward<duration>(d));
}

template<class mutex>
inline bool
ircd::ctx::upgrade_lock<mutex>::try_lock()
{
	assert(m);
	return m->try_lock_upgrade();
}

template<class mutex>
inline void
ircd::ctx::upgrade_lock<mutex>::lock()
{
	assert(m);
	m->lock_upgrade();
}

template<class mutex>
inline bool
ircd::ctx::upgrade_lock<mutex>::owns_lock()
const
{
	return m? m->upgrade() : false;
}
