#include <tuple>
#include <numeric>
#include <vector>
#include <string>

// forward declaration of get_size() method
template <class T>
size_t get_size(const T& obj);

namespace detail {
 
	template <class T>
	struct get_size_helper; 
 
	/** 
	 * Specialization for generic std::vector<T>, a vector is represented 
	 * in the stream by storing the number of elements (v.size()) followed
	 * by the serialized elements (notice that we can have nested structures
	 */
	template <class T>
	struct get_size_helper<std::vector<T>> {
		static size_t value(const std::vector<T>& obj) {
			return std::accumulate(obj.begin(), obj.end(), sizeof(size_t), 
					[](const size_t& acc, const T& cur) { return acc+get_size(cur); });
		}
	};
	
	/**
	 * Specialization for an std::string. Similarly to the vector, we store 
	 * the number of elements of the string (str.length()), followed by the
	 * chars. In this case we are sure the elements of the string are bytes
	 * therefore we can compute the size without recurring
	 */
	template <>
	struct get_size_helper<std::string> {
		static size_t value(const std::string& obj) {
			return sizeof(size_t) + obj.length()*sizeof(uint8_t);
		}
	};
 
	template <class tuple_type>
	inline size_t get_tuple_size(const tuple_type& obj, int_<0>) {
		constexpr size_t idx = std::tuple_size<tuple_type>::value-1;
		return get_size(std::get<idx>(obj));
	}
 
	template <class tuple_type, size_t pos>
	inline size_t get_tuple_size(const tuple_type& obj, int_<pos>) {
		
		constexpr size_t idx = std::tuple_size<tuple_type>::value-pos-1;
		size_t acc = get_size(std::get<idx>(obj));
 
		return acc+get_tuple_size(obj, int_<pos-1>());
	}
	
	/** 
	 * specialization for std::tuple<T...>. In this case we don't need to 
	 * store the number of elements since this information is explicit 
	 * with the type itself. Therefore we simply serialize the elements 
	 * of the tuple
	 */
	template <class ...T>
	struct get_size_helper<std::tuple<T...>> {
		static size_t value(const std::tuple<T...>& obj) {
			return get_tuple_size(obj, int_<sizeof...(T)-1>());
		}
	};
 
	/** 
	 * Specialization for any remaining type, simply use the value of 
	 * sizeof()
	 */
	template <class T>
	struct get_size_helper {
		static size_t value(const T& obj) { return sizeof(T); }
	};
 
} // end detail namespace 
 
template <class T>
inline size_t get_size(const T& obj) { 
	return detail::get_size_helper<T>::value(obj); 
}