#pragma once

#include <boost/coroutine2/coroutine.hpp>
#include <boost/endian/conversion.hpp>

#include <cstdint>
#include <ostream>
#include <iostream>
#include <istream>
#include <sstream>
#include <string>
#include <vector>

typedef boost::coroutines2::coroutine<void> coro_t;

// Serialization, similar to Boost Serialization
// but for portable binary archive
// using big endian coding (network byte order)
namespace Serialization {

class OArchive
{
public:
 OArchive(std::ostream& os): os(os) {}
 ~OArchive() {}

 template<class T>
 OArchive& operator &(T& v) {
  v.serialize(*this);

  return *this;
 };

 template <class T>
 OArchive& write_fundamental(T& v)
 {
  T value = boost::endian::native_to_big(v);
  os.write((char*)&value, sizeof(value));
  return *this;
 }

 OArchive& operator &(uint8_t& v)
 {
  return write_fundamental(v);
 };

 OArchive& operator &(uint16_t& v)
 {
  return write_fundamental(v);
 };

 OArchive& operator &(uint32_t& v)
 {
  return write_fundamental(v);
 };

 OArchive& operator &(uint64_t& v)
 {
  return write_fundamental(v);
 };

 OArchive& operator &(int64_t& v)
 {
  return write_fundamental(*reinterpret_cast<uint64_t*>(v));
 };

 OArchive& operator &(std::vector<uint8_t>& v)
 {
  uint32_t size = static_cast<uint32_t>(v.size());
  *this & size;
  os.write((char*)v.data(), size);
  return *this;
 };

 OArchive& operator &(std::string& v)
 {
  uint32_t size = static_cast<uint32_t>(v.size());
  *this & size;
  os.write((char*)v.data(), v.size());
  return *this;
 };

private:
 std::ostream &os;
};

class IArchive
{
public:
 IArchive(std::istream& is): is(is) {}
 IArchive(std::stringstream& is, coro_t::pull_type& coro) : is(is), mStringStream(&is), mCoro(&coro) {}
 ~IArchive() {}

 template<class T>
 IArchive& operator &(T& v)
 {
  v.serialize(*this);

  return *this;
 };

 template <class T>
 IArchive& read_fundamental(T& v)
 {
  // in coroutine case, wait for input, if necessary
  if (mCoro && mStringStream) {
   while (true) {
    auto pindex {mStringStream->tellp()};
    auto gindex {mStringStream->tellg()};
    if (pindex != std::stringstream::pos_type(-1) && gindex != std::stringstream::pos_type(-1) && pindex > gindex) {
     if (static_cast<size_t>(pindex - gindex) < sizeof(v))
      (*mCoro)();
     else
      break;
    } else {
     std::cerr << "Error: read_fundamental: Bad stringstream indices: " << pindex << ", " << gindex << std::endl;
     break;
    }
   }
  }

  // now, we have enough bytes available
  T value;
  is.read((char*)&value, sizeof(value));
  v = boost::endian::big_to_native(value);
  return *this;
 }

 IArchive& operator &(uint8_t& v)
 {
  return read_fundamental(v);
 };

 IArchive& operator &(uint16_t& v)
 {
  return read_fundamental(v);
 };

 IArchive& operator &(uint32_t& v)
 {
  return read_fundamental(v);
 };

 IArchive& operator &(uint64_t& v)
 {
  return read_fundamental(v);
 };

 IArchive& operator &(int64_t& v)
 {
  uint64_t uv;
  read_fundamental(uv);
  v = *reinterpret_cast<int64_t*>(uv);
  return *this;
 };

 template <class T>
 IArchive& read_bytes_vector(T& v)
 {
  uint32_t size;
  *this & size;

  v.resize(size);

  // in coroutine case, wait for input, if necessary
  if (mCoro && mStringStream) {
   while (true) {
    auto pindex {mStringStream->tellp()};
    auto gindex {mStringStream->tellg()};
    if (pindex != std::stringstream::pos_type(-1) && gindex != std::stringstream::pos_type(-1) && pindex > gindex) {
     if (static_cast<uint32_t>(pindex - gindex) < size)
      (*mCoro)();
     else
      break;
    } else {
     std::cerr << "Error: read_bytes_vector: Bad stringstream indices: " << pindex << ", " << gindex << std::endl;
     break;
    }
   }
  }

  // now, we have enough bytes available
  is.read((char*)v.data(), size);
  return *this;
 }

 IArchive& operator &(std::vector<uint8_t>& v)
 {
  return read_bytes_vector(v);
 };

 IArchive& operator &(std::string& v)
 {
  return read_bytes_vector(v);
 };

private:
 std::istream &is;
 std::stringstream* mStringStream{ }; // for i/o sizes access
 coro_t::pull_type* mCoro{ }; // optional for coroutine
};

// - Free functions ----------------------------------------------------------

template<class Archive, class T>
void serialize(Archive& ar, T& v)
{
 ar & v;
}

template<class T>
OArchive& operator <<(OArchive& ar, T& v)
{
 serialize(ar, v);

 return ar;
};

template<class T>
IArchive& operator >>(IArchive& ar, T& v)
{
 serialize(ar, v);

 return ar;
};

}