/*
 * Copyright (C) by
 *   MetraLabs GmbH (MLAB), GERMANY
 * and
 *   Neuroinformatics and Cognitive Robotics Labs (NICR) at TU Ilmenau, GERMANY
 * All rights reserved.
 *
 * Contact: info@mira-project.org
 *
 * Commercial Usage:
 *   Licensees holding valid commercial licenses may use this file in
 *   accordance with the commercial license agreement provided with the
 *   software or, alternatively, in accordance with the terms contained in
 *   a written agreement between you and MLAB or NICR.
 *
 * GNU General Public License Usage:
 *   Alternatively, this file may be used under the terms of the GNU
 *   General Public License version 3.0 as published by the Free Software
 *   Foundation and appearing in the file LICENSE.GPL3 included in the
 *   packaging of this file. Please review the following information to
 *   ensure the GNU General Public License version 3.0 requirements will be
 *   met: http://www.gnu.org/copyleft/gpl.html.
 *   Alternatively you may (at your option) use any later version of the GNU
 *   General Public License if such license has been publicly approved by
 *   MLAB and NICR (or its successors, if any).
 *
 * IN NO EVENT SHALL "MLAB" OR "NICR" BE LIABLE TO ANY PARTY FOR DIRECT,
 * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF
 * THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF "MLAB" OR
 * "NICR" HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * "MLAB" AND "NICR" SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND "MLAB" AND "NICR" HAVE NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS OR MODIFICATIONS.
 */

/**
 * @file variant
 *    Reflect for std::variant
 *    This reflect requires that all the types the variant consists of are public default constructible.
 *
 * @author Tom Mehner
 * @date   Mar 10, 2023
 */

#ifndef _MIRA_BASE_INCLUDE_SERIALIZATION_ADAPTERS_STD_VARIANT_HPP_
#define _MIRA_BASE_INCLUDE_SERIALIZATION_ADAPTERS_STD_VARIANT_HPP_

#include <variant>

#include <serialization/PropertySerializer.h>
#include <serialization/SplitReflect.h>
#include <serialization/XMLSerializer.h>

namespace mira {

///////////////////////////////////////////////////////////////////////////////

namespace Private {

template<typename Head, typename... Tail, typename... Types, typename Reflector>
void fillVariant(std::variant<Types...>& t, Reflector& r, const int index)
{
	constexpr auto N = sizeof...(Types) - (sizeof...(Tail) + 1);
	if (index == N) {
		Head val;
		r.property("Content", val, "");
		t = std::move(val);
		return;
	}
	if constexpr (sizeof...(Tail) > 0) {
		fillVariant<Tail...>(t, r, index);
	}
	else {
		MIRA_THROW(XInvalidConfig,
		           "Invalid type index " << index << " for " << typeName<std::variant<Types...>>());
	}
}

template<typename Head, typename... Tail, typename... Types, typename Reflector>
void fillVariant(std::variant<Types...>& t, Reflector& r, const Typename& typeName)
{
	if (mira::typeName<Head>() == typeName) {
		Head val;
		r.property("Content", val, "");
		t = std::move(val);
		return;
	}
	if constexpr (sizeof...(Tail) > 0) {
		fillVariant<Tail...>(t, r, typeName);
	}
	else {
		MIRA_THROW(XInvalidConfig,
		           "Invalid type name " << typeName << " for " << mira::typeName<std::variant<Types...>>());
	}
}

template<typename Head, typename... Tail>
std::string createTypeHints()
{
	if constexpr (sizeof...(Tail) == 0) {
		return typeName<Head>();
	}
	else {
		return typeName<Head>() + ";" + createTypeHints<Tail...>();
	}
}

} // namespace Private

///////////////////////////////////////////////////////////////////////////////

template<typename Reflector, typename... Types>
void reflectRead(Reflector& r, std::variant<Types...>& t)
{
	int index = t.index();
	// store which type the variant contains
	r.member("Type", index, "The actual type stored in the variant");
	// reflect the corresponding type

	auto readVisitor = [&](auto& operand) { r.property("Content", operand, ""); };
	std::visit(readVisitor, t);
}

template<typename Reflector, typename... Types>
void reflectWrite(Reflector& r, std::variant<Types...>& t)
{
	int index;
	// retrieve the type the variant contains
	r.member("Type", index, "The actual type stored in the variant");
	// create and deserialize the type that corresponds to index.
	Private::fillVariant<Types...>(t, r, index);
}

///////////////////////////////////////////////////////////////////////////////

/**
 *  Specialization for PropertySerializer: 'Type' is a readonly-property
 *  with hint to show the actual typename in a PropertyEditor.
 */
template<typename... Types>
void reflectRead(PropertySerializer& r, std::variant<Types...>& t)
{
	// store which type the variant contains
	r.roproperty("Type", t.index(), "The actual type stored in the variant",
	             PropertyHints::enumeration(Private::createTypeHints<Types...>()));
	// reflect the corresponding type
	auto readVisitor = [&](auto& operand) { r.property("Content", operand, ""); };
	std::visit(readVisitor, t);
}

///////////////////////////////////////////////////////////////////////////////

/**
 *  Specialization for XMLSerializer: 'Type' is the name of the contained type as returned by mira::typeName.
 *  Used to make configurations more readable and less error-prone against adding new types to the variant.
 */

template<typename... Types>
void reflectRead(XMLSerializer& r, std::variant<Types...>& t)
{
	auto typeName = std::visit([&](auto& operand) { return mira::typeName(operand); }, t);
	r.member("Type", typeName, "The actual type stored in the variant");
	auto readVisitor = [&](auto& operand) { r.property("Content", operand, ""); };
	std::visit(readVisitor, t);
}

template<typename... Types>
void reflectWrite(XMLDeserializer& r, std::variant<Types...>& t)
{
	Typename typeName;
	// retrieve the type the variant contains
	r.member("Type", typeName, "The actual type stored in the variant");
	// create and deserialize the type
	Private::fillVariant<Types...>(t, r, typeName);
}

///////////////////////////////////////////////////////////////////////////////

template<typename Reflector, typename... Types>
void reflect(Reflector& r, std::variant<Types...>& t)
{
	splitReflect(r, t);
}

///////////////////////////////////////////////////////////////////////////////

} // namespace mira

#endif
