#pragma once

#include "traits.hpp"

namespace tf {

// ----------------------------------------------------------------------------
// Supported C++ STL type
// ----------------------------------------------------------------------------

// std::basic_string
template <typename T> 
struct is_std_basic_string : std::false_type {};

template <typename... ArgsT> 
struct is_std_basic_string <std::basic_string<ArgsT...>> : std::true_type {};

template <typename T> 
constexpr bool is_std_basic_string_v = is_std_basic_string<T>::value;

// std::array
template <typename T> 
struct is_std_array : std::false_type {};

template <typename T, size_t N> 
struct is_std_array <std::array<T, N>> : std::true_type {};

template <typename T> 
constexpr bool is_std_array_v = is_std_array<T>::value;

// std::vector
template <typename T> 
struct is_std_vector : std::false_type {};

template <typename... ArgsT> 
struct is_std_vector <std::vector<ArgsT...>> : std::true_type {};

template <typename T> 
constexpr bool is_std_vector_v = is_std_vector<T>::value;

// std::deque
template <typename T> 
struct is_std_deque : std::false_type {};

template <typename... ArgsT> 
struct is_std_deque <std::deque<ArgsT...>> : std::true_type {};

template <typename T> 
constexpr bool is_std_deque_v = is_std_deque<T>::value;

// std::list
template <typename T> 
struct is_std_list : std::false_type {};

template <typename... ArgsT> 
struct is_std_list <std::list<ArgsT...>> : std::true_type {};

template <typename T> 
constexpr bool is_std_list_v = is_std_list<T>::value;

// std::forward_list
template <typename T> 
struct is_std_forward_list : std::false_type {};

template <typename... ArgsT> 
struct is_std_forward_list <std::forward_list<ArgsT...>> : std::true_type {};

template <typename T> 
constexpr bool is_std_forward_list_v = is_std_forward_list<T>::value;

// std::map
template <typename T> 
struct is_std_map : std::false_type {};

template <typename... ArgsT> 
struct is_std_map <std::map<ArgsT...>> : std::true_type {};

template <typename T> 
constexpr bool is_std_map_v = is_std_map<T>::value;

// std::unordered_map
template <typename T> 
struct is_std_unordered_map : std::false_type {};

template <typename... ArgsT> 
struct is_std_unordered_map <std::unordered_map<ArgsT...>> : std::true_type {};

template <typename T> 
constexpr bool is_std_unordered_map_v = is_std_unordered_map<T>::value;

// std::set
template <typename T> 
struct is_std_set : std::false_type {};

template <typename... ArgsT> 
struct is_std_set <std::set<ArgsT...>> : std::true_type {};

template <typename T> 
constexpr bool is_std_set_v = is_std_set<T>::value;

// std::unordered_set
template <typename T> 
struct is_std_unordered_set : std::false_type {};

template <typename... ArgsT> 
struct is_std_unordered_set <std::unordered_set<ArgsT...>> : std::true_type {};

template <typename T> 
constexpr bool is_std_unordered_set_v = is_std_unordered_set<T>::value;

// std::variant
template <typename T> 
struct is_std_variant : std::false_type {};

template <typename... ArgsT> 
struct is_std_variant <std::variant<ArgsT...>> : std::true_type {};

template <typename T> 
constexpr bool is_std_variant_v = is_std_variant<T>::value;

// std::optional
template <typename T> 
struct is_std_optional : std::false_type {};

template <typename... ArgsT> 
struct is_std_optional <std::optional<ArgsT...>> : std::true_type {};

template <typename T> 
constexpr bool is_std_optional_v = is_std_optional<T>::value;

// std::unique_ptr
template <typename T> 
struct is_std_unique_ptr : std::false_type {};

template <typename... ArgsT> 
struct is_std_unique_ptr <std::unique_ptr<ArgsT...>> : std::true_type {};

template <typename T> 
constexpr bool is_std_unique_ptr_v = is_std_unique_ptr<T>::value;

// std::shared_ptr
template <typename T> 
struct is_std_shared_ptr : std::false_type {};

template <typename... ArgsT> 
struct is_std_shared_ptr <std::shared_ptr<ArgsT...>> : std::true_type {};

template <typename T> 
constexpr bool is_std_shared_ptr_v = is_std_shared_ptr<T>::value;

// std::duration
template <typename T> struct is_std_duration : std::false_type {};

template <typename... ArgsT> 
struct is_std_duration<std::chrono::duration<ArgsT...>> : std::true_type {};

template <typename T> 
constexpr bool is_std_duration_v = is_std_duration<T>::value;

// std::time_point
template <typename T> 
struct is_std_time_point : std::false_type {};

template <typename... ArgsT> 
struct is_std_time_point<std::chrono::time_point<ArgsT...>> : std::true_type {};

template <typename T> 
constexpr bool is_std_time_point_v = is_std_time_point<T>::value;

// std::tuple
template <typename T> 
struct is_std_tuple : std::false_type {};

template <typename... ArgsT> 
struct is_std_tuple<std::tuple<ArgsT...>> : std::true_type {};

template <typename T> 
constexpr bool is_std_tuple_v = is_std_tuple<T>::value;

//-----------------------------------------------------------------------------
// Type extraction.
//-----------------------------------------------------------------------------

// ExtractType: forward declaration
template <size_t, typename> 
struct ExtractType;

// ExtractType_t: alias interface
template <size_t idx, typename C>
using ExtractType_t = typename ExtractType<idx, C>::type;

// ExtractType: base
template <template <typename...> typename C, typename T, typename... RestT>
struct ExtractType <0, C<T, RestT...>> {
  using type = T;
};

// ExtractType: base
template <typename T>
struct ExtractType <0, T> {
  using type = T;
};

// ExtractType: recursive definition.
template <size_t idx, template <typename...> typename C, typename T, typename... RestT>
struct ExtractType <idx, C<T, RestT...>> : ExtractType<idx-1, C<RestT...>> {
};

// ----------------------------------------------------------------------------
// Size Wrapper
// ----------------------------------------------------------------------------

// Struct: SizeTag
// Class that wraps a given size item which can be customized. 
template <typename T>
class SizeTag {

  public: 
  
    using type = std::conditional_t<std::is_lvalue_reference_v<T>, T, std::decay_t<T>>;
    
    SizeTag(T&& item) : _item(std::forward<T>(item)) {}
    
    SizeTag& operator = (const SizeTag&) = delete;

    inline const T& get() const {return _item;}

    template <typename ArchiverT>
    auto save(ArchiverT & ar) const { return ar(_item); }
    
    template <typename ArchiverT>
    auto load(ArchiverT & ar) { return ar(_item); }

  private:

    type _item;
};

// Function: make_size_tag
template <typename T>
SizeTag<T> make_size_tag(T&& t) {
  return { std::forward<T>(t) };
}

// ----------------------------------------------------------------------------
// Size Wrapper
// ----------------------------------------------------------------------------

// Class: MapItem
template <typename KeyT, typename ValueT>
class MapItem {
  
  public:
  
    using KeyType = std::conditional_t <std::is_lvalue_reference_v<KeyT>, KeyT, std::decay_t<KeyT>>;
    using ValueType = std::conditional_t <std::is_lvalue_reference_v<ValueT>, ValueT, std::decay_t<ValueT>>;

    MapItem(KeyT&& k, ValueT&& v) : _key(std::forward<KeyT>(k)), _value(std::forward<ValueT>(v)) {}
    MapItem& operator = (const MapItem&) = delete;

    inline const KeyT& key() const { return _key; }
    inline const ValueT& value() const { return _value; }

    template <typename ArchiverT>
    auto save(ArchiverT & ar) const { return ar(_key, _value); }
    
    template <typename ArchiverT>
    auto load(ArchiverT & ar) { return ar(_key, _value); }

  private:

    KeyType _key;
    ValueType _value;
};

// Function: make_kv_pair
template <typename KeyT, typename ValueT>
MapItem<KeyT, ValueT> make_kv_pair(KeyT&& k, ValueT&& v) {
  return { std::forward<KeyT>(k), std::forward<ValueT>(v) };
}

// ----------------------------------------------------------------------------
// Serializer Definition
// ----------------------------------------------------------------------------

template <typename T>
constexpr auto is_default_serializable_v = ( 
  std::is_arithmetic_v<T>    ||
  std::is_enum_v<T>          ||
  is_std_basic_string_v<T>   ||
  is_std_vector_v<T>         ||
  is_std_deque_v<T>          ||
  is_std_list_v<T>           ||
  is_std_forward_list_v<T>   ||
  is_std_map_v<T>            ||
  is_std_unordered_map_v<T>  ||
  is_std_set_v<T>            ||
  is_std_unordered_set_v<T>  ||
  is_std_duration_v<T>       ||
  is_std_time_point_v<T>     ||
  is_std_variant_v<T>        ||
  is_std_optional_v<T>       ||
  is_std_tuple_v<T>          ||
  is_std_array_v<T>
);


// Class: Serializer
template <typename Stream, typename SizeType = std::streamsize>
class Serializer {

  public:

    Serializer(Stream& stream);
    
    template <typename... T>
    SizeType operator()(T&&... items);
  
  private:

    Stream& _stream;
    
    template <typename T, 
      std::enable_if_t<!is_default_serializable_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _save(T&&);
    
    template <typename T, 
      std::enable_if_t<std::is_arithmetic_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _save(T&&);
    
    template <typename T, 
      std::enable_if_t<is_std_basic_string_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _save(T&&);
    
    template <typename T, 
      std::enable_if_t<is_std_vector_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _save(T&&);
    
    template <typename T, 
      std::enable_if_t<
        is_std_deque_v<std::decay_t<T>> ||
        is_std_list_v<std::decay_t<T>>, 
        void
      >* = nullptr
    >
    SizeType _save(T&&);
    
    template <typename T, 
      std::enable_if_t<
        is_std_forward_list_v<std::decay_t<T>>, 
        void
      >* = nullptr
    >
    SizeType _save(T&&);
    
    template <typename T, 
      std::enable_if_t<
        is_std_map_v<std::decay_t<T>> ||
        is_std_unordered_map_v<std::decay_t<T>>, 
        void
      >* = nullptr
    >
    SizeType _save(T&&);
    
    template <typename T, 
      std::enable_if_t<
        is_std_set_v<std::decay_t<T>> ||
        is_std_unordered_set_v<std::decay_t<T>>, 
        void
      >* = nullptr
    >
    SizeType _save(T&&);
    
    template <typename T, 
      std::enable_if_t<std::is_enum_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _save(T&&);

    template <typename T, 
      std::enable_if_t<is_std_duration_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _save(T&&);

    template <typename T, 
      std::enable_if_t<is_std_time_point_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _save(T&&);

    template <typename T, 
      std::enable_if_t<is_std_optional_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _save(T&&);
    
    template <typename T, 
      std::enable_if_t<is_std_variant_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _save(T&&);
    
    template <typename T, 
      std::enable_if_t<is_std_tuple_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _save(T&&);
    
    template <typename T, 
      std::enable_if_t<is_std_array_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _save(T&&);
    

};

// Constructor
template <typename Stream, typename SizeType>
Serializer<Stream, SizeType>::Serializer(Stream& stream) : _stream(stream) {
}

// Operator ()
template <typename Stream, typename SizeType>
template <typename... T>
SizeType Serializer<Stream, SizeType>::operator() (T&&... items) {
  return (_save(std::forward<T>(items)) + ...);
}

// arithmetic data type
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<std::is_arithmetic_v<std::decay_t<T>>, void>*
>
SizeType Serializer<Stream, SizeType>::_save(T&& t) {
  _stream.write(reinterpret_cast<const char*>(std::addressof(t)), sizeof(t));
  return sizeof(t);
}

// std::basic_string
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_basic_string_v<std::decay_t<T>>, void>*
>
SizeType Serializer<Stream, SizeType>::_save(T&& t) {
  using U = std::decay_t<T>;
  auto sz = _save(make_size_tag(t.size()));
  _stream.write(
    reinterpret_cast<const char*>(t.data()), 
    t.size()*sizeof(typename U::value_type)
  );
  return sz + t.size()*sizeof(typename U::value_type);
}

// std::vector
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_vector_v<std::decay_t<T>>, void>*
>
SizeType Serializer<Stream, SizeType>::_save(T&& t) {

  using U = std::decay_t<T>;
    
  auto sz = _save(make_size_tag(t.size()));

  if constexpr (std::is_arithmetic_v<typename U::value_type>) {
    _stream.write(
      reinterpret_cast<const char*>(t.data()), 
      t.size() * sizeof(typename U::value_type)
    );
    sz += t.size() * sizeof(typename U::value_type);
  } else {
    for(auto&& item : t) {
      sz += _save(item);
    }
  }

  return sz;
}

// std::list and std::deque
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_deque_v<std::decay_t<T>> ||
                   is_std_list_v<std::decay_t<T>>, void>*
>
SizeType Serializer<Stream, SizeType>::_save(T&& t) {
  auto sz = _save(make_size_tag(t.size()));
  for(auto&& item : t) {
    sz += _save(item);
  }
  return sz;
}

// std::forward_list
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_forward_list_v<std::decay_t<T>>, void>*
>
SizeType Serializer<Stream, SizeType>::_save(T&& t) {
  auto sz = _save(make_size_tag(std::distance(t.begin(), t.end())));
  for(auto&& item : t) {
    sz += _save(item);
  }
  return sz;
}

// std::map and std::unordered_map
template <typename Stream, typename SizeType>  
template <typename T, std::enable_if_t<
  is_std_map_v<std::decay_t<T>> ||
  is_std_unordered_map_v<std::decay_t<T>>, 
  void
>*>
SizeType Serializer<Stream, SizeType>::_save(T&& t) {
  auto sz = _save(make_size_tag(t.size()));
  for(auto&& [k, v] : t) {
    sz += _save(make_kv_pair(k, v));
  }
  return sz;
}

// std::set and std::unordered_set
template <typename Stream, typename SizeType>  
template <typename T, std::enable_if_t<
  is_std_set_v<std::decay_t<T>> ||
  is_std_unordered_set_v<std::decay_t<T>>, 
  void
>*>
SizeType Serializer<Stream, SizeType>::_save(T&& t) {
  auto sz = _save(make_size_tag(t.size()));
  for(auto&& item : t) {
    sz += _save(item);
  }
  return sz;
}

// enum data type
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<std::is_enum_v<std::decay_t<T>>, void>*
>
SizeType Serializer<Stream, SizeType>::_save(T&& t) {
  using U = std::decay_t<T>;
  return _save(static_cast<std::underlying_type_t<U>>(t));
}

// duration data type
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_duration_v<std::decay_t<T>>, void>*
>
SizeType Serializer<Stream, SizeType>::_save(T&& t) {
  return _save(t.count());
}

// time point data type
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_time_point_v<std::decay_t<T>>, void>*
>
SizeType Serializer<Stream, SizeType>::_save(T&& t) {
  return _save(t.time_since_epoch());
}

// optional data type
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_optional_v<std::decay_t<T>>, void>*
>
SizeType Serializer<Stream, SizeType>::_save(T&& t) {
  if(bool flag = t.has_value(); flag) {
    return _save(flag) + _save(*t);
  }
  else {
    return _save(flag);
  }
}

// variant type
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_variant_v<std::decay_t<T>>, void>*
>
SizeType Serializer<Stream, SizeType>::_save(T&& t) {
  return _save(t.index()) + 
         std::visit([&] (auto&& arg){ return _save(arg);}, t);
}

// tuple type
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_tuple_v<std::decay_t<T>>, void>*
>
SizeType Serializer<Stream, SizeType>::_save(T&& t) {
  return std::apply(
    [&] (auto&&... args) {
      return (_save(std::forward<decltype(args)>(args)) + ... + 0); 
    },
    std::forward<T>(t)
  );
}

// array
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_array_v<std::decay_t<T>>, void>*
>
SizeType Serializer<Stream, SizeType>::_save(T&& t) {

  using U = std::decay_t<T>;

  static_assert(std::tuple_size<U>::value > 0, "Array size can't be zero");

  SizeType sz;

  if constexpr(std::is_arithmetic_v<typename U::value_type>) {
    _stream.write(reinterpret_cast<const char*>(t.data()), sizeof(t));
    sz = sizeof(t);
  } 
  else {
    sz = 0;
    for(auto&& item : t) {
      sz += _save(item);
    }
  }

  return sz;
}

// custom save method    
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<!is_default_serializable_v<std::decay_t<T>>, void>*
>
SizeType Serializer<Stream, SizeType>::_save(T&& t) {
  return t.save(*this);
}

// ----------------------------------------------------------------------------
// DeSerializer Definition
// ----------------------------------------------------------------------------

template <typename T>
constexpr auto is_default_deserializable_v = 
  std::is_arithmetic_v<T>    ||
  std::is_enum_v<T>          ||
  is_std_basic_string_v<T>   ||
  is_std_vector_v<T>         ||
  is_std_deque_v<T>          ||
  is_std_list_v<T>           ||
  is_std_forward_list_v<T>   ||
  is_std_map_v<T>            ||
  is_std_unordered_map_v<T>  ||
  is_std_set_v<T>            ||
  is_std_unordered_set_v<T>  ||
  is_std_duration_v<T>       ||
  is_std_time_point_v<T>     ||
  is_std_variant_v<T>        ||
  is_std_optional_v<T>       ||
  is_std_tuple_v<T>          ||
  is_std_array_v<T>;

// Class: Deserializer
template <typename Stream, typename SizeType = std::streamsize>
class Deserializer {

  public:
    
    Deserializer(Stream& stream);
    
    template <typename... T>
    SizeType operator()(T&&... items);
  
  private:

    Stream& _stream;
    
    // Function: _variant_helper
    template <
      size_t I = 0, typename... ArgsT, 
      std::enable_if_t<I==sizeof...(ArgsT)>* = nullptr
    >
    SizeType _variant_helper(size_t, std::variant<ArgsT...>&);
    
    // Function: _variant_helper
    template <
      size_t I = 0, typename... ArgsT, 
      std::enable_if_t<I<sizeof...(ArgsT)>* = nullptr
    >
    SizeType _variant_helper(size_t, std::variant<ArgsT...>&);
    
    template <typename T, 
      std::enable_if_t<std::is_arithmetic_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _load(T&&);
    
    template <typename T, 
      std::enable_if_t<is_std_basic_string_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _load(T&&);
    
    template <typename T, 
      std::enable_if_t<is_std_vector_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _load(T&&);
    
    template <typename T, 
      std::enable_if_t<
        is_std_deque_v<std::decay_t<T>> ||
        is_std_list_v<std::decay_t<T>>  ||
        is_std_forward_list_v<std::decay_t<T>>, 
        void
      >* = nullptr
    >
    SizeType _load(T&&);
    
    template <typename T, 
      std::enable_if_t<is_std_map_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _load(T&&);
    
    template <typename T, 
      std::enable_if_t<is_std_unordered_map_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _load(T&&);
    
    template <typename T, 
      std::enable_if_t<is_std_set_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _load(T&&);
    
    template <typename T, 
      std::enable_if_t<is_std_unordered_set_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _load(T&&);
    
    template <typename T, 
      std::enable_if_t<std::is_enum_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _load(T&&);

    template <typename T, 
      std::enable_if_t<is_std_duration_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _load(T&&);

    template <typename T, 
      std::enable_if_t<is_std_time_point_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _load(T&&);

    template <typename T, 
      std::enable_if_t<is_std_optional_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _load(T&&);
    
    template <typename T, 
      std::enable_if_t<is_std_variant_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _load(T&&);
    
    template <typename T, 
      std::enable_if_t<is_std_tuple_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _load(T&&);
    
    template <typename T, 
      std::enable_if_t<is_std_array_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _load(T&&);
    
    template <typename T, 
      std::enable_if_t<!is_default_deserializable_v<std::decay_t<T>>, void>* = nullptr
    >
    SizeType _load(T&&);
};

// Constructor
template <typename Stream, typename SizeType>
Deserializer<Stream, SizeType>::Deserializer(Stream& stream) : _stream(stream) {
}

// Operator ()
template <typename Stream, typename SizeType>
template <typename... T>
SizeType Deserializer<Stream, SizeType>::operator() (T&&... items) {
  return (_load(std::forward<T>(items)) + ...);
}

// Function: _variant_helper
template <typename Stream, typename SizeType>
template <size_t I, typename... ArgsT, std::enable_if_t<I==sizeof...(ArgsT)>*>
SizeType Deserializer<Stream, SizeType>::_variant_helper(size_t, std::variant<ArgsT...>&) {
  return 0;
}

// Function: _variant_helper
template <typename Stream, typename SizeType>
template <size_t I, typename... ArgsT, std::enable_if_t<I<sizeof...(ArgsT)>*>
SizeType Deserializer<Stream, SizeType>::_variant_helper(size_t i, std::variant<ArgsT...>& v) {
  if(i == 0) {
    using type = ExtractType_t<I, std::variant<ArgsT...>>;
    if(v.index() != I) {
      static_assert(
        std::is_default_constructible<type>::value, 
        "Failed to archive variant (type should be default constructible T())"
      );
      v = type();
    }
    return _load(std::get<type>(v));
  }
  return _variant_helper<I+1, ArgsT...>(i-1, v);
}

// arithmetic data type
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<std::is_arithmetic_v<std::decay_t<T>>, void>*
>
SizeType Deserializer<Stream, SizeType>::_load(T&& t) {
  _stream.read(reinterpret_cast<char*>(std::addressof(t)), sizeof(t));
  return sizeof(t);
}

// std::basic_string
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_basic_string_v<std::decay_t<T>>, void>*
>
SizeType Deserializer<Stream, SizeType>::_load(T&& t) {
  using U = std::decay_t<T>;
  typename U::size_type num_chars;
  auto sz = _load(make_size_tag(num_chars));
  t.resize(num_chars);
  _stream.read(reinterpret_cast<char*>(t.data()), num_chars*sizeof(typename U::value_type));
  return sz + num_chars*sizeof(typename U::value_type);
}

// std::vector
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_vector_v<std::decay_t<T>>, void>*
>
SizeType Deserializer<Stream, SizeType>::_load(T&& t) {

  using U = std::decay_t<T>;
  
  typename U::size_type num_data;
    
  auto sz = _load(make_size_tag(num_data));

  if constexpr(std::is_arithmetic_v<typename U::value_type>) {
    t.resize(num_data);
    _stream.read(reinterpret_cast<char*>(t.data()), num_data * sizeof(typename U::value_type));
    sz += num_data * sizeof(typename U::value_type);
  } 
  else {
    t.resize(num_data);
    for(auto && v : t) {
      sz += _load(v);
    }
  }
  return sz;
}

// std::list and std::deque
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_deque_v<std::decay_t<T>> ||
                   is_std_list_v<std::decay_t<T>>  ||
                   is_std_forward_list_v<std::decay_t<T>>, void>*
>
SizeType Deserializer<Stream, SizeType>::_load(T&& t) {
  using U = std::decay_t<T>;
    
  typename U::size_type num_data;
  auto sz = _load(make_size_tag(num_data));

  t.resize(num_data);
  for(auto && v : t) {
    sz += _load(v);
  }
  return sz;
}

// std::map 
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_map_v<std::decay_t<T>>, void>*
>
SizeType Deserializer<Stream, SizeType>::_load(T&& t) {
  
  using U = std::decay_t<T>;

  typename U::size_type num_data;
  auto sz = _load(make_size_tag(num_data));
  
  t.clear();
  auto hint = t.begin();
    
  typename U::key_type k;
  typename U::mapped_type v;

  for(size_t i=0; i<num_data; ++i) {
    sz += _load(make_kv_pair(k, v));
    hint = t.emplace_hint(hint, std::move(k), std::move(v));
  }
  return sz;
}

// std::unordered_map
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_unordered_map_v<std::decay_t<T>>, void>*
>
SizeType Deserializer<Stream, SizeType>::_load(T&& t) {
  using U = std::decay_t<T>;
  typename U::size_type num_data;
  auto sz = _load(make_size_tag(num_data));

  t.clear();
  t.reserve(num_data);

  typename U::key_type k;
  typename U::mapped_type v;

  for(size_t i=0; i<num_data; ++i) {
    sz += _load(make_kv_pair(k, v));
    t.emplace(std::move(k), std::move(v));
  }
  
  return sz;
}

// std::set 
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_set_v<std::decay_t<T>>, void>*
>
SizeType Deserializer<Stream, SizeType>::_load(T&& t) {
  
  using U = std::decay_t<T>;

  typename U::size_type num_data;
  auto sz = _load(make_size_tag(num_data));

  t.clear();
  auto hint = t.begin();
    
  typename U::key_type k;

  for(size_t i=0; i<num_data; ++i) {   
    sz += _load(k);
    hint = t.emplace_hint(hint, std::move(k));
  }   
  return sz;
}

// std::unordered_set
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_unordered_set_v<std::decay_t<T>>, void>*
>
SizeType Deserializer<Stream, SizeType>::_load(T&& t) {
   
  using U = std::decay_t<T>;
   
  typename U::size_type num_data;
  auto sz = _load(make_size_tag(num_data));

  t.clear();
  t.reserve(num_data);
    
  typename U::key_type k;

  for(size_t i=0; i<num_data; ++i) {   
    sz += _load(k);
    t.emplace(std::move(k));
  }   
  return sz;
}

// enum data type
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<std::is_enum_v<std::decay_t<T>>, void>*
>
SizeType Deserializer<Stream, SizeType>::_load(T&& t) {
  using U = std::decay_t<T>;
  std::underlying_type_t<U> k;
  auto sz = _load(k);
  t = static_cast<U>(k);
  return sz;
}

// duration data type
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_duration_v<std::decay_t<T>>, void>*
>
SizeType Deserializer<Stream, SizeType>::_load(T&& t) {
  using U = std::decay_t<T>;
  typename U::rep count;
  auto s = _load(count);
  t = U{count};
  return s;
}

// time point data type
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_time_point_v<std::decay_t<T>>, void>*
>
SizeType Deserializer<Stream, SizeType>::_load(T&& t) {
  using U = std::decay_t<T>;
  typename U::duration elapsed;
  auto s = _load(elapsed);
  t = U{elapsed};
  return s;
}

// optional data type
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_optional_v<std::decay_t<T>>, void>*
>
SizeType Deserializer<Stream, SizeType>::_load(T&& t) {
  
  using U = std::decay_t<T>;

  bool has_value;
  auto s = _load(has_value);
  if(has_value) {
    if(!t) {
      t = typename U::value_type();
    }
    s += _load(*t);
  }
  else {
    t.reset(); 
  }
  return s;
}

// variant type
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_variant_v<std::decay_t<T>>, void>*
>
SizeType Deserializer<Stream, SizeType>::_load(T&& t) {
  std::decay_t<decltype(t.index())> idx;
  auto s = _load(idx);
  return s + _variant_helper(idx, t);
}

// tuple type
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_tuple_v<std::decay_t<T>>, void>*
>
SizeType Deserializer<Stream, SizeType>::_load(T&& t) {
  return std::apply(
    [&] (auto&&... args) {
      return (_load(std::forward<decltype(args)>(args)) + ... + 0); 
    },
    std::forward<T>(t)
  );
}

// array
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<is_std_array_v<std::decay_t<T>>, void>*
>
SizeType Deserializer<Stream, SizeType>::_load(T&& t) {

  using U = std::decay_t<T>;

  static_assert(std::tuple_size<U>::value > 0, "Array size can't be zero");

  SizeType sz;
    
  if constexpr(std::is_arithmetic_v<typename U::value_type>) {
    _stream.read(reinterpret_cast<char*>(t.data()), sizeof(t));
    sz = sizeof(t);
  } 
  else {
    sz = 0;
    for(auto && v : t) {
      sz += _load(v);
    }
  }

  return sz;
}

// custom save method    
template <typename Stream, typename SizeType>  
template <typename T, 
  std::enable_if_t<!is_default_deserializable_v<std::decay_t<T>>, void>*
>
SizeType Deserializer<Stream, SizeType>::_load(T&& t) {
  return t.load(*this);
}

}  // ned of namespace tf -----------------------------------------------------






