/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#pragma once

#include "Spintex.hpp"
#include <mutex>
#include <shared_mutex>

namespace OpenVulkano
{
	template<typename T, typename MUTEX = std::mutex>
	class MutexProtectedObject
	{
	protected:
		T object;
		MUTEX mutex;

	public:
		class Accessor
		{
			std::unique_lock<MUTEX> lock;
			T& ref;

		public:
			Accessor(MUTEX& m, T& obj)
					: lock(m), ref(obj)
			{}

			T* operator->() { return &ref; }

			T& Get() { return ref; }
		};

		MutexProtectedObject() = default;

		MutexProtectedObject(const T& initValue) : object(initValue) {}

		template<typename... Args>
		MutexProtectedObject(Args&&... args)
				: object(std::forward<Args>(args)...)
		{}

		MutexProtectedObject(const MutexProtectedObject&) = delete;

		Accessor Access()
		{
			return Accessor(mutex, object);
		}

		MutexProtectedObject& operator =(const T& value)
		{
			Accessor accessor = Access();
			object = value;
			return *this;
		}

		operator T()
		{
			Accessor accessor = Access();
			return accessor.Get();
		}

		const T& UnsafeAccess() const { return object; }
	};

	template<typename T> using SpintexProtectedObject = MutexProtectedObject<T, Spintex>;

	template<typename T, typename MUTEX = std::shared_mutex>
	class SharedMutexProtectedObject : public MutexProtectedObject<T, MUTEX>
	{
	public:
		class SharedAccessor
		{
			std::shared_lock<MUTEX> lock;
			T& ref;
		public:
			SharedAccessor(MUTEX& m, T& obj)
					: lock(m), ref(obj)
			{}


			const T* operator->() const { return &ref; }

			const T& Get() const { return ref; }

			T* operator->() { return &ref; }

			T& Get() { return ref; }
		};

		SharedMutexProtectedObject() = default;

		SharedMutexProtectedObject(const T& initValue) : MutexProtectedObject<T, MUTEX>(initValue) {}

		template<typename... Args>
		SharedMutexProtectedObject(Args&&... args)
			: MutexProtectedObject<T, MUTEX>(std::forward<Args>(args)...)
		{}

		SharedMutexProtectedObject(const SharedMutexProtectedObject&) = delete;

		SharedAccessor SharedAccess()
		{
			return SharedAccessor(MutexProtectedObject<T, MUTEX>::mutex, MutexProtectedObject<T, MUTEX>::object);
		}
	};
}
