/*
 * $Id: rwlock.h 4661 2017-08-23 20:40:02Z antonin.misek-skoda-auto.cz $
 *
 * Description  : One line description of file.
 * Author       : Vaclav Kyba <mail/Jabber: vaseo1@gmail.com>  <ICQ: 98576293>
 * 
 * Purpose      :
 *     Long description of what the file is for.
 */


#ifndef __RW_LOCK__H__
#define __RW_LOCK__H__

#include <wx/thread.h>
#include <list>
#include "countable.h"
#include "flexilog.h"
#include "VrutApi.h"

namespace VRUT
{
	///Multiple clients may read simultaneously but write access is exclusive. Recursive locking is possible with limitations
	class VRUT_CORE_API ReadWriteMutex
	{
	public:
		///Class constructor
		ReadWriteMutex() : rwMutex(), readCond(rwMutex), writeCond(rwMutex), readerCount(0)
		{
			writer.recursiveLocks = 0;
			writer.threadID = 0;
		}

		virtual ~ReadWriteMutex() {}
			  
	private:
		///Thread ID and recursive locks data
		struct LockOwner
		{
			///Owner thread's ID
			wxThreadIdType threadID;
			///Number of recursive locks
			unsigned recursiveLocks;
		};
		typedef std::list<LockOwner> LockOwnerList;

		///Main mutex
		mutable wxCriticalSection rwMutex;
		///The reader's condition
		mutable VRUTCondition readCond;
		///The writer's condition
		mutable VRUTCondition writeCond;
		///List of current readers info
		mutable LockOwnerList readers;
		///Current number of readers
		mutable int readerCount;
		///Current writer info
		mutable LockOwner writer;

		/*///Get list iterator for current reader if any
		LockOwnerList::iterator getCurrentReader() const
		{
			wxThreadIdType threadID = wxThread::GetCurrentId();
			for (LockOwnerList::iterator it = readers.begin(); it != readers.end(); ++it)
				if (it->threadID == threadID)
					return it;
			return readers.end();
		}*/
	
	public:
		///Acquire read lock
		void ReadLock() const
		{
			wxCriticalSectionLocker lock(rwMutex);
			///Do we need to wait or is it our thread that owns it
			wxThreadIdType threadID = wxThread::GetCurrentId();
			///If this thread owns the write lock, there is no need to wait and all this can be ignored
			if (threadID==writer.threadID)
				return;
			readerCount++;
			while (writer.recursiveLocks>0)
				readCond.Wait();
		}
		
		///Release read lock
		void ReadUnlock() const
		{
			wxCriticalSectionLocker lock(rwMutex);
			///if this thread currently holds the writelock, all this can be ignored
			if (writer.threadID == wxThread::GetCurrentId())
				return;
			if (--readerCount==0)
				writeCond.Signal();
		}
		
		///Acquire write lock
		void WriteLock() const
		{
			rwMutex.Enter();
			
			///Do we need to wait or is it our thread that owns it
			wxThreadIdType threadID = wxThread::GetCurrentId();
			if (writer.recursiveLocks && writer.threadID == threadID)
				writer.recursiveLocks++;
			else
			{
				///If there are readers or a writer, wait for them to finish
				while (/*readers.size()>0*/ readerCount>0 || writer.recursiveLocks>0)
					writeCond.Wait();
				writer.threadID = threadID;
				writer.recursiveLocks = 1;
			}
			rwMutex.Leave();
		}
		
		///Release write lock
		void WriteUnlock() const
		{
			wxCriticalSectionLocker lock(rwMutex);
			wxThreadIdType threadID = wxThread::GetCurrentId();
			wxASSERT_MSG(writer.threadID == threadID, wxT("<ReadWriteMutex>Unlocking write lock locked by different thread"));
			if (--writer.recursiveLocks == 0)
			{
				///Signal the waiting readers AND writers
				writer.threadID = 0;
				writeCond.Signal();
				readCond.Broadcast();
			}
		}
	};

	/// This class has the same interface as ReadWriteMutex, but does nothing. Useful for a class
	/// which can say whether to use locks or not at running time.
	class ReadWriteMutexOptional
	{
	protected:
		ReadWriteMutex rwMutex;
		const bool useLock;

	public:
		ReadWriteMutexOptional(bool useLock): useLock(useLock) {}
		/// Copy interface of ReadWriteMutex

		void ReadLock() const		{		if (useLock)		rwMutex.ReadLock();			}
		void ReadUnlock() const		{		if (useLock)		rwMutex.ReadUnlock();		}
		void WriteLock() const		{		if (useLock)		rwMutex.WriteLock();		}
		void WriteUnlock() const	{		if (useLock)		rwMutex.WriteUnlock();		}
	};

	/// This class has the same interface as ReadWriteMutex, but does nothing. Useful as a template parameter for a class
	/// which can say whether to use locks or not at compile time.
	class ReadWriteFakeMutex
	{
	public:
		/// Copy interface of ReadWriteMutex

		void ReadLock() const		{	/* Do nothing */	}
		void ReadUnlock() const		{	/* Do nothing */	}
		void WriteLock() const		{	/* Do nothing */	}
		void WriteUnlock() const	{	/* Do nothing */	}
	};

	///Acquire lock for reading and release lock implicitly in scope
	class ReadLocker
	{
	public:
		///Class constructor
		ReadLocker(const ReadWriteMutex & rwLock) : m_rwLock(rwLock)
		{
			m_rwLock.ReadLock();
		}

		///Class destructor
		~ReadLocker()
		{
			m_rwLock.ReadUnlock();
		}
	
	private:
		///\c ReadWriteMutex to be locked/unlocked
		const ReadWriteMutex & m_rwLock;
	};

	///Acquire lock for writing and release lock implicitly in scope
	class WriteLocker
	{
	public:
		///Class constructor
		WriteLocker(const ReadWriteMutex& rwLock) : m_rwLock(rwLock)
		{
			m_rwLock.WriteLock();
		}
		
		///Class destructor
		~WriteLocker()
		{
			m_rwLock.WriteUnlock();
		}
	
	private:
		///\c ReadWriteMutex to be locked/unlocked
		const ReadWriteMutex& m_rwLock;
	};

	///Acquire lock for reading and release lock implicitly in scope
	template <class ReadWriteMutexClass> class ReadLockerTemplate
	{
	public:
		/// Class constructor
		ReadLockerTemplate(const ReadWriteMutexClass &rwLock) : m_rwLock(&rwLock)
		{
			m_rwLock->ReadLock();
		}
		/// Move constructor - does not relock the mutex
		ReadLockerTemplate(ReadLockerTemplate &&locker)
		{
			m_rwLock = locker.m_rwLock;
			locker.m_rwLock = nullptr;
		}

		///Class destructor
		~ReadLockerTemplate()
		{
			if (m_rwLock)	m_rwLock->ReadUnlock();
		}
	
	private:
		ReadLockerTemplate(const ReadLockerTemplate &);				// No copy constuctor - to avoid dead lock
		ReadLockerTemplate &operator =(const ReadLockerTemplate &);	// No assign operator - to avoid dead lock
		ReadLockerTemplate &operator =(ReadLockerTemplate &&);		// No move assign operator - should not be necessary

		///\c ReadWriteMutex to be locked/unlocked
		const ReadWriteMutexClass *m_rwLock;
	};
	template <class ReadWriteMutexClass> ReadLockerTemplate<ReadWriteMutexClass> CreateReadLocker(const ReadWriteMutexClass &rwLock)
	{
		// Value is returned via RVO (return value optimalization) or move constructor, in both cases only one lock is used
		return ReadLockerTemplate<ReadWriteMutexClass>(rwLock);
	}
	#define CREATE_READ_LOCKER(locker_name, lock)			VRUT::ReadLockerTemplate<decltype(lock)> locker_name(lock)
	
	///Acquire lock for writing and release lock implicitly in scope
	template <class ReadWriteMutexClass> class WriteLockerTemplate
	{
	public:
		///Class constructor
		WriteLockerTemplate(const ReadWriteMutexClass &rwLock) : m_rwLock(&rwLock)
		{
			m_rwLock->WriteLock();
		}
		/// Move constructor - does not relock the mutex
		WriteLockerTemplate(WriteLockerTemplate &&locker)
		{
			m_rwLock = locker.m_rwLock;
			locker.m_rwLock = nullptr;
		}
		
		///Class destructor
		~WriteLockerTemplate()
		{
			if (m_rwLock)	m_rwLock->WriteUnlock();
		}
	
	private:
		WriteLockerTemplate(const WriteLockerTemplate &);				// No copy constuctor - to avoid dead lock
		WriteLockerTemplate &operator =(const WriteLockerTemplate &);	// No assign operator - to avoid dead lock
		WriteLockerTemplate &operator =(WriteLockerTemplate &&);		// No move assign operator - should not be necessary

		///\c ReadWriteMutex to be locked/unlocked
		const ReadWriteMutexClass *m_rwLock;
	};
	template <class ReadWriteMutexClass> WriteLockerTemplate<ReadWriteMutexClass> CreateWriteLocker(const ReadWriteMutexClass &rwLock)
	{
		// Value is returned via RVO (return value optimalization) or move constructor, in both cases only one lock is used
		return WriteLockerTemplate<ReadWriteMutexClass>(rwLock);
	}
	#define CREATE_WRITE_LOCKER(locker_name, lock)			VRUT::WriteLockerTemplate<decltype(lock)> locker_name(lock)

	// To use lockers, you can use the class and define its type, however, there are two ways to avoid the type declaration.
	// First, you can use CREATE_*_LOCKER macro, which defines the type, or Create*Locker function, which deduces the type.
	// The latter one may be a bit slower (two objects are created on stack (when inlining) instead of one), but a compiler
	// should optimize this.

}

#endif
