/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2023, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */
#pragma once

#include <mrpt/serialization/CArchive.h>
#include <mrpt/serialization/metaprogramming_serialization.h>
#include <mrpt/typemeta/TTypeName_stl.h>  // TTypeName<> for STL templates, needed for serialization of STL templates

#include <algorithm>  // for_each()
#include <array>
#include <deque>
#include <list>
#include <map>
#include <set>
#include <vector>

namespace mrpt::serialization
{
/** \addtogroup mrpt_serialization_stlext_grp
 * @{ */

#define MRPTSTL_SERIALIZABLE_SEQ_CONTAINER(CONTAINER)                          \
	/** Template method to serialize a sequential STL container  */            \
	template <class T, class _Ax>                                              \
	CArchive& operator<<(CArchive& out, const CONTAINER<T, _Ax>& obj)          \
	{                                                                          \
		out << std::string(#CONTAINER) << mrpt::typemeta::TTypeName<T>::get(); \
		out.WriteAs<uint32_t>(obj.size());                                     \
		std::for_each(                                                         \
			obj.begin(), obj.end(),                                            \
			metaprogramming::ObjectWriteToStream(&out));                       \
		return out;                                                            \
	}                                                                          \
	/** Template method to deserialize a sequential STL container */           \
	template <class T, class _Ax>                                              \
	CArchive& operator>>(CArchive& in, CONTAINER<T, _Ax>& obj)                 \
	{                                                                          \
		obj.clear();                                                           \
		std::string pref, stored_T;                                            \
		in >> pref;                                                            \
		if (pref != #CONTAINER)                                                \
			THROW_EXCEPTION_FMT(                                               \
				"Error: serialized container %s<%s>'s preambles is wrong: "    \
				"'%s'",                                                        \
				#CONTAINER, mrpt::typemeta::TTypeName<T>::get().c_str(),       \
				pref.c_str());                                                 \
		in >> stored_T;                                                        \
		if (stored_T !=                                                        \
			std::string(mrpt::typemeta::TTypeName<T>::get().c_str()))          \
			THROW_EXCEPTION_FMT(                                               \
				"Error: serialized container %s< %s != %s >", #CONTAINER,      \
				stored_T.c_str(),                                              \
				mrpt::typemeta::TTypeName<T>::get().c_str());                  \
		const uint32_t n = in.ReadAs<uint32_t>();                              \
		obj.resize(n);                                                         \
		std::for_each(                                                         \
			obj.begin(), obj.end(),                                            \
			metaprogramming::ObjectReadFromStream(&in));                       \
		return in;                                                             \
	}

template <typename T>
using is_map_like = std::is_same<
	typename T::value_type,
	std::pair<const typename T::key_type, typename T::mapped_type>>;

template <typename T>
using is_map = std::is_same<
	T,
	typename std::map<
		typename T::key_type, typename T::mapped_type, typename T::key_compare,
		typename T::allocator_type>>;

template <typename T>
using is_multimap = std::is_same<
	T,
	typename std::multimap<
		typename T::key_type, typename T::mapped_type, typename T::key_compare,
		typename T::allocator_type>>;

template <typename T, std::enable_if_t<is_map<T>::value, int> = 0>
std::string containerName()
{
	return "std::map";
}
template <typename T, std::enable_if_t<is_multimap<T>::value, int> = 0>
std::string containerName()
{
	return "std::multimap";
}

/** Template method to serialize an associative STL container  */
template <class T, std::enable_if_t<is_map_like<T>::value, int> = 0>
CArchive& operator<<(CArchive& out, const T& obj)
{
	out << containerName<T>()
		<< mrpt::typemeta::TTypeName<typename T::key_type>::get()
		<< mrpt::typemeta::TTypeName<
			   std::decay_t<typename T::mapped_type>>::get();
	out.WriteAs<uint32_t>(obj.size());
	for (typename T::const_iterator it = obj.begin(); it != obj.end(); ++it)
		out << it->first << it->second;
	return out;
}
/** Template method to deserialize an associative STL container */
template <class T, std::enable_if_t<is_map_like<T>::value, int> = 0>
CArchive& operator>>(CArchive& in, T& obj)
{
	obj.clear();
	std::string pref, stored_K, stored_V;
	in >> pref;
	const auto kType = mrpt::typemeta::TTypeName<typename T::key_type>::get();
	const auto mTp = mrpt::typemeta::TTypeName<typename T::mapped_type>::get();
	if (pref != containerName<T>())
		THROW_EXCEPTION(format(
			"Error: serialized container %s<%s,%s>'s preamble is "
			"wrong: '%s'",
			containerName<T>().c_str(), kType.c_str(), mTp.c_str(),
			pref.c_str()));
	in >> stored_K;
	if (stored_K != std::string(kType.c_str()))
		THROW_EXCEPTION(format(
			"Error: serialized container %s key type %s != %s",
			containerName<T>().c_str(), stored_K.c_str(), kType.c_str()));
	in >> stored_V;
	if (stored_V != std::string(mTp.c_str()))
		THROW_EXCEPTION(format(
			"Error: serialized container %s value type %s != %s",
			containerName<T>().c_str(), stored_V.c_str(), mTp.c_str()));
	const uint32_t n = in.ReadAs<uint32_t>();
	for (uint32_t i = 0; i < n; i++)
	{
		typename T::key_type key_obj;
		in >> key_obj;
		/* Create an pair (Key, empty), then read directly into the
		 * ".second": */
		typename T::iterator it_new = obj.insert(
			obj.end(), std::make_pair(key_obj, typename T::mapped_type()));
		in >> it_new->second;
	}
	return in;
}

#define MRPTSTL_SERIALIZABLE_SIMPLE_ASSOC_CONTAINER(CONTAINER)                 \
	/** Template method to serialize an associative STL container  */          \
	template <class K, class _Pr, class _Alloc>                                \
	CArchive& operator<<(CArchive& out, const CONTAINER<K, _Pr, _Alloc>& obj)  \
	{                                                                          \
		out << std::string(#CONTAINER) << mrpt::typemeta::TTypeName<K>::get(); \
		out.WriteAs<uint32_t>(obj.size());                                     \
		for (const auto& e : obj)                                              \
			out << e;                                                          \
		return out;                                                            \
	}                                                                          \
	/** Template method to deserialize an associative STL container */         \
	template <class K, class _Pr, class _Alloc>                                \
	CArchive& operator>>(CArchive& in, CONTAINER<K, _Pr, _Alloc>& obj)         \
	{                                                                          \
		obj.clear();                                                           \
		std::string pref, stored_K;                                            \
		const auto kType = mrpt::typemeta::TTypeName<K>::get();                \
		in >> pref;                                                            \
		if (pref != #CONTAINER)                                                \
			THROW_EXCEPTION_FMT(                                               \
				"Error: serialized container %s<%s>'s preamble is wrong: "     \
				"'%s'",                                                        \
				#CONTAINER, kType.c_str(), pref.c_str());                      \
		in >> stored_K;                                                        \
		if (stored_K != std::string(kType.c_str()))                            \
			THROW_EXCEPTION_FMT(                                               \
				"Error: serialized container %s key type %s != %s",            \
				#CONTAINER, stored_K.c_str(), kType.c_str());                  \
		const uint32_t n = in.ReadAs<uint32_t>();                              \
		for (uint32_t i = 0; i < n; i++)                                       \
		{                                                                      \
			K key_obj;                                                         \
			in >> key_obj;                                                     \
			obj.insert(key_obj);                                               \
		}                                                                      \
		return in;                                                             \
	}

MRPTSTL_SERIALIZABLE_SEQ_CONTAINER(std::vector)
MRPTSTL_SERIALIZABLE_SEQ_CONTAINER(std::deque)
MRPTSTL_SERIALIZABLE_SEQ_CONTAINER(std::list)

MRPTSTL_SERIALIZABLE_SIMPLE_ASSOC_CONTAINER(std::set)
MRPTSTL_SERIALIZABLE_SIMPLE_ASSOC_CONTAINER(std::multiset)

/** Template method to serialize a std::array<T,N> */
template <class T, size_t N>
CArchive& operator<<(CArchive& out, const std::array<T, N>& obj)
{
	out << std::string("std::array") << static_cast<uint32_t>(N)
		<< mrpt::typemeta::TTypeName<T>::get();
	std::for_each(
		obj.begin(), obj.end(), metaprogramming::ObjectWriteToStream(&out));
	return out;
}

/** Template method to deserialize a std::array<T,N>  */
template <class T, size_t N>
CArchive& operator>>(CArchive& in, std::array<T, N>& obj)
{
	std::string pref, stored_T;
	uint32_t stored_N;
	in >> pref >> stored_N;
	if (pref != "std::array" || stored_N != N)
		THROW_EXCEPTION_FMT(
			"Error: serialized container %s's preambles is wrong: "
			"'%s'",
			mrpt::typemeta::TTypeName<std::array<T, N>>::get().c_str(),
			pref.c_str());
	in >> stored_T;
	if (stored_T != std::string(mrpt::typemeta::TTypeName<T>::get().c_str()))
		THROW_EXCEPTION_FMT(
			"Error: serialized container std::array< %s != %s >",
			stored_T.c_str(), mrpt::typemeta::TTypeName<T>::get().c_str());
	std::for_each(
		obj.begin(), obj.end(), metaprogramming::ObjectReadFromStream(&in));
	return in;
}

/** Template method to serialize a STL pair */
template <class T1, class T2>
CArchive& operator<<(CArchive& out, const std::pair<T1, T2>& obj)
{
	out << std::string("std::pair") << mrpt::typemeta::TTypeName<T1>::get()
		<< mrpt::typemeta::TTypeName<T2>::get();
	out << obj.first << obj.second;
	return out;
}
/** Template method to deserialize a STL pair */
template <class T1, class T2>
CArchive& operator>>(CArchive& in, std::pair<T1, T2>& obj)
{
	std::string pref, stored_K, stored_V;
	in >> pref;
	if (pref != "std::pair")
		THROW_EXCEPTION(format(
			"Error: serialized std::pair<%s,%s>'s preamble is wrong: '%s'",
			mrpt::typemeta::TTypeName<T1>::get().c_str(),
			mrpt::typemeta::TTypeName<T2>::get().c_str(), pref.c_str()));
	in >> stored_K;
	if (stored_K != std::string(mrpt::typemeta::TTypeName<T1>::get().c_str()))
		THROW_EXCEPTION(format(
			"Error: serialized std::pair first type %s != %s", stored_K.c_str(),
			mrpt::typemeta::TTypeName<T1>::get().c_str()));
	in >> stored_V;
	if (stored_V != std::string(mrpt::typemeta::TTypeName<T2>::get().c_str()))
		THROW_EXCEPTION(format(
			"Error: serialized std::pair second type %s != %s",
			stored_V.c_str(), mrpt::typemeta::TTypeName<T2>::get().c_str()));
	in >> obj.first >> obj.second;
	return in;
}

/** @} */  // end of grouping
}  // namespace mrpt::serialization
