#ifndef FZ_SHARED_CONTEXT_HPP
#define FZ_SHARED_CONTEXT_HPP

#include <cassert>

#include <memory>

#include "util/traits.hpp"
#include "borrow.hpp"

namespace fz {

namespace detail {

struct shared_context_data_do_not_delete
{
	template <typename T>
	void operator()(T *){}
};

}

template <typename T>
struct shared_context_data_impl;

template <typename T>
struct shared_context_data;

template <typename T>
struct shared_context;

FZ_UTIL_TRAITS_MAKE_ACCESS_TEMPLATE(shared_context)

template <typename T>
struct shared_context_data_impl<T *>
{
	using value_type = T;

	shared_context_data_impl(T *v)
		: v_(v)
	{}

	std::unique_ptr<T, detail::shared_context_data_do_not_delete> v_{};
};

template <typename T>
struct shared_context_data_impl<std::unique_ptr<T>>
{
	using value_type = T;

	shared_context_data_impl(std::unique_ptr<T> v)
		: v_(std::move(v))
	{}

	std::unique_ptr<T> v_;
};


template <typename T>
struct shared_context_data_impl<std::optional<T>>
{
	using value_type = T;

	template <typename U>
	shared_context_data_impl(U && v)
		: v_(std::forward<U>(v))
	{}

	std::optional<T> v_;
};

template <typename T>
struct shared_context_data_impl
{
	using value_type = T;

	template <typename U>
	shared_context_data_impl(U && v)
		: v_(std::forward<U>(v))
	{}

	std::optional<T> v_;
};

template <typename T>
struct shared_context_data: shared_context_data_impl<T>, private borrowable
{
	using value_type = typename shared_context_data_impl<T>::value_type;

	template <typename U>
	shared_context_data(U && v)
		: shared_context_data_impl<T>(std::forward<U>(v))
	{}

	scoped_borrow<const value_type> lock() const
	{
		if (this->v_) {
			return scoped_borrow<const value_type>(std::addressof(*this->v_), this);
		}

		return {};
	}

	scoped_borrow<value_type> lock()
	{
		if (this->v_) {
			return scoped_borrow<value_type>(std::addressof(*this->v_), this);
		}

		return {};
	}

	explicit operator bool() const noexcept
	{
		return this->v_ && is_borrowable();
	}

	void stop_sharing()
	{
		stop_borrowing();
		wait_until_no_active_borrows();
		this->v_.reset();
	}
};

template <typename T>
struct shared_context final
{
	using data_type = shared_context_data<T>;
	using value_type = typename data_type::value_type;

	shared_context() = default;

	shared_context(shared_context &&) = default;
	shared_context(const shared_context &) = default;

	shared_context &operator=(shared_context &&) = default;
	shared_context &operator=(const shared_context &) = default;

	template <typename U, typename = std::enable_if_t<!trait::is_a_shared_context_v<U>>>
	shared_context(U && v)
		: data_(std::make_shared<shared_context_data<T>>(std::forward<U>(v)))
	{}

	scoped_borrow<const value_type> lock() const
	{
		return data_->lock();
	}


	scoped_borrow<value_type> lock()
	{
		return data_->lock();
	}

	explicit operator bool() const noexcept
	{
		return bool(*data_);
	}

	void stop_sharing() noexcept {
		data_->stop_sharing();
	}

private:
	std::shared_ptr<shared_context_data<T>> data_;
};


}

#endif // FZ_SHARED_CONTEXT_HPP
