#pragma once
#include "./std_headers.h"

#include "./packetbuf.h"

namespace packetio {

template <typename T>
string packet_get_typetag(vector<T> const &x)
{
  return "vector:1< "s + packet_get_typetag(T()) + " >"s;
}

template <typename T>
void packet_wr_value(::packet &p, vector<T> const &x)
{
  if (!(x.size() < 0x3fffffff)) throw runtime_error("Unreasonable size "s + to_string(x.size()));
  packetio::packet_wr_value(p, (uint32_t)x.size());
  for (size_t i = 0; i < x.size(); i++) {
    packetio::packet_wr_value(p, x[i]);
  }
}

template <>
void packet_wr_value(::packet &p, vector<U8> const &x);


template <typename T>
void packet_rd_value(::packet &p, vector<T> &x)
{
  uint32_t size;
  packetio::packet_rd_value(p, size);
  if (!(size < 0x3fffffff)) throw runtime_error("Unreasonable size "s + to_string(size));
  x.resize(size);
  for (size_t i = 0; i < x.size(); i++) {
    packetio::packet_rd_value(p, x[i]);
  }
}


template <>
void packet_rd_value(::packet &p, vector<U8> &x);

template <typename T>
std::function<void(::packet &, vector<T> &)> packet_rd_value_compat(vector<T> const &x, string const &typetag)
{
  if (typetag == packet_get_typetag(x)) {
    return static_cast<void (*)(::packet & p, vector<T> & x1)>(packetio::packet_rd_value);
  }
  else {
    return nullptr;
  }
}

// ----------------------------------------------------------------------

template <typename T1, typename T2>
string packet_get_typetag(pair<T1, T2> const &x)
{
  return "pair:1< "s + packet_get_typetag(x.first) + ", "s + packet_get_typetag(x.second) + " >"s;
}

template <typename T1, typename T2>
void packet_wr_value(::packet &p, pair<T1, T2> const &x)
{
  packet_wr_value(p, x.first);
  packet_wr_value(p, x.second);
}

template <typename T1, typename T2>
void packet_rd_value(::packet &p, pair<T1, T2> &x)
{
  packet_rd_value(p, x.first);
  packet_rd_value(p, x.second);
}

template <typename T1, typename T2>
std::function<void(::packet &, pair<T1, T2> &)>
packet_rd_value_compat(pair<T1, T2> const &x, string const &typetag)
{
  if (typetag == packet_get_typetag(x)) {
    return static_cast<void (*)(::packet & p, pair<T1, T2> & x1)>(packetio::packet_rd_value);
  }
  else {
    return nullptr;
  }
}

// ----------------------------------------------------------------------

template <typename T1, typename T2>
string packet_get_typetag(map<T1, T2> const &x)
{
  return "map:1< "s + packet_get_typetag(T1()) + ", "s + packet_get_typetag(T2()) + " >"s;
}

template <typename T1, typename T2>
void packet_wr_value(::packet &p, map<T1, T2> const &x)
{
  packet_wr_value(p, (uint32_t)x.size());
  for (auto it = x.begin(); it != x.end(); it++) {
    packet_wr_value(p, it->first);
    packet_wr_value(p, it->second);
  }
}

template <typename T1, typename T2>
void packet_rd_value(::packet &p, map<T1, T2> &x)
{
  uint32_t x_size = 0;
  packet_rd_value(p, x_size);
  for (size_t xi = 0; xi < x_size; xi++) {
    T1 first;
    T2 second;
    packet_rd_value(p, first);
    packet_rd_value(p, second);
    x[first] = second;
  }
}

template <typename T1, typename T2>
std::function<void(::packet &, map<T1, T2> &)>
packet_rd_value_compat(map<T1, T2> const &x, string const &typetag)
{
  if (typetag == packet_get_typetag(x)) {
    return static_cast<void (*)(::packet &p, map<T1, T2> &x1)>(packetio::packet_rd_value);
  }
  else {
    return nullptr;
  }
}

} // namespace packetio

template <typename T>
void packet::add_checked(const T &x)
{
  add_len8_string(packetio::packet_get_typetag(x));
  packetio::packet_wr_value(*this, x);
}

template <typename T>
void packet::add(const T &x)
{
  packetio::packet_wr_value(*this, x);
}

template <typename T>
void packet::add_typetag(const T &x)
{
  add_len8_string(packetio::packet_get_typetag(x));
}

template <typename T>
void packet::get(T &x)
{
  packetio::packet_rd_value(*this, x);
}

template <typename T>
void packet::get_checked(T &x)
{
  string typetag = get_len8_string();
  if (typetag == packetio::packet_get_typetag(x)) {
    packetio::packet_rd_value(*this, x);
  }
  else {
    throw packet_rd_type_err(packetio::packet_get_typetag(x), typetag);
  }
}

template <typename T>
void packet::get_compat(T &x)
{
  string typetag = get_len8_string();
  auto f = packetio::packet_rd_value_compat(x, typetag);
  if (!f) {
    throw packet_rd_type_err(packetio::packet_get_typetag(x), typetag);
  }
  f(*this, x);
}

template <typename T>
std::function<void(packet &, T &)> packet::get_compat_func(T const &x, string const &typetag)
{
  return packetio::packet_rd_value_compat(x, typetag);
}

template <typename T>
T packet::fget()
{
  T ret;
  packetio::packet_rd_value(*this, ret);
  return ret;
}

template <typename T>
T packet::fget_checked()
{
  T ret;
  get_checked(ret);
  return ret;
}

template <typename T>
T packet::fget_compat()
{
  T ret;
  get_compat(ret);
  return ret;
}


#define INSTANTIATE_PACKETIO(T) \
  template void packet::add_checked(T const &x); \
  template void packet::add(T const &x); \
  template void packet::add_typetag(T const &x); \
  template void packet::get(T &x); \
  template void packet::get_checked(T &x); \
  template void packet::get_compat(T &x); \
  template std::function<void(packet &, T &)> packet::get_compat_func(T const &x, string const &typetag); \
  template T packet::fget(); \
  template T packet::fget_checked(); \
  template T packet::fget_compat();
