/*******************************************************************************
***  This file is part of Dux Rei Nummariae.                                 ***
***                                                                          ***
***  Copyright (C) 2017, 2018, 2019, 2020, 2021                              ***
***  John Schneiderman <Licensing _AT_ Schneiderman _DOT_ me>                ***
***                                                                          ***
***  This program is free software: you can redistribute it and/or modify it ***
***  under the terms of the GNU Affero General Public License as published   ***
***  by the Free Software Foundation, either version 3 of the License, or    ***
***  (at your option) any later version.                                     ***
***                                                                          ***
***  This program is distributed in the hope that it will be useful, but     ***
***  WITHOUT ANY WARRANTY; without even the implied warranty of              ***
***  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                    ***
***  See the GNU Affero General Public License for more details.             ***
***                                                                          ***
***  You should have received a copy of the GNU Affero General Public License***
***  along with this program. If not, see <http://www.gnu.org/licenses/>.    ***
*******************************************************************************/
#ifndef DRN_FOUNDATION_QTMEMORY_HPP_
#define DRN_FOUNDATION_QTMEMORY_HPP_

#include <QObject>

#include <memory>
#include <type_traits>

#include <foundation/ObserverPtr.hpp>


namespace drn
{
namespace foundation
{

/**
 * @brief A representation of a Qt object that is freed using the Qt QObject release mechanism.
 *
 * @tparam ObjectType The type of the object stored.
 */
template<typename ObjectType>
struct QtUniquePtr final : std::unique_ptr<ObjectType, void(*)(ObjectType*)>
{
	explicit QtUniquePtr(ObjectType* const obj);
};

/**
 * @brief Creates an object using the Qt memory pattern. Guarantees the object is of a Qt type that
 * can participate in the Qt memory management and is owned by a parent.
 *
 * @tparam ObjectType The type of the object to create.
 * @tparam ObjectArgTypes The argument types for the object constructor to invoke.
 *
 * @param objectArgs The values of to use in the object creation.
 *
 * @return The Qt object that is created on the heap.
 */
template<typename ObjectType, typename ... ObjectArgTypes>
ObserverPtr<ObjectType> makeQtPtr(ObjectArgTypes&& ... objectArgs);

namespace internal
{

constexpr bool hasQtParentLast();
template<typename ObjectArgType>
constexpr bool hasQtParentLast(ObjectArgType&&);
template<typename ObjectArgType, typename ... ObjectArgTypes>
constexpr bool hasQtParentLast(ObjectArgType&&, ObjectArgTypes&& ... rest);
template<typename ... ObjectArgTypes>
constexpr bool hasQtParent(ObjectArgTypes&& ... args);

}}}

template<typename ObjectType>
drn::foundation::QtUniquePtr<ObjectType>::QtUniquePtr(ObjectType* const obj) :
	std::unique_ptr<ObjectType, void(*)(ObjectType*)>{
		obj,
		[] (ObjectType* o)
		{
			if (o != nullptr)
				o->deleteLater();
		}
	}
{
	static_assert(
		std::is_base_of<::QObject, ObjectType>::value,
		"Only pointers to Qt objects that can be released by Qt are allowed."
	);
}

template<typename ObjectType, typename ... ObjectArgTypes>
drn::foundation::ObserverPtr<ObjectType> drn::foundation::makeQtPtr(ObjectArgTypes&& ... objectArgs)
{
/* TODO: Re-enable when internal::hasQtParent is fixed.
	static_assert(
		internal::hasQtParent(std::forward<ObjectArgTypes>(objectArgs)...),
		"Only Qt objects that accept a parent may be created via this function."
	);*/
	return new ObjectType{std::forward<ObjectArgTypes>(objectArgs)...};
}

constexpr bool hasQtParentLast()
{
	return false;
}

template<typename ObjectArgType>
constexpr bool drn::foundation::internal::hasQtParentLast(ObjectArgType&&)
{
	return std::is_convertible<
			typename std::remove_cv<typename std::remove_reference<ObjectArgType>::type>::type*,
			::QObject*
		>::value;
}

template<typename ObjectArgType, typename ... ObjectArgTypes>
constexpr bool drn::foundation::internal::hasQtParentLast(
	ObjectArgType&&,
	ObjectArgTypes&& ... rest
)
{
	return hasQtParentLast(std::forward<ObjectArgTypes>(rest)...);
}

template<typename ... ObjectArgTypes>
constexpr bool drn::foundation::internal::hasQtParent(ObjectArgTypes&& ... args)
{
	return hasQtParentLast(std::forward<ObjectArgTypes>(args)...);
}

#endif
