#ifndef _octetStream
#define _octetStream


/* This class creates a stream of data and adds stuff onto it.
 * This is used to pack and unpack stuff which is sent over the
 * network
 *
 * Unlike SPDZ-1.0 this class ONLY deals with native types
 * For our types we assume pack/unpack operations defined within
 * that class. This is to make sure this class is relatively independent
 * of the rest of the application; and so can be re-used.
 */

// 32-bit length fields for compatibility
#ifndef LENGTH_SIZE
#define LENGTH_SIZE 4
#endif

#include "Networking/data.h"
#include "Networking/sockets.h"
#include "Tools/avx_memcpy.h"

#include <string.h>
#include <vector>
#include <stdio.h>
#include <iostream>
#include <assert.h>

#include <sodium.h>
 
using namespace std;

class bigint;
class FlexBuffer;

class octetStream
{
  friend class FlexBuffer;
  template<class T> friend class Exchanger;

  size_t len,mxlen,ptr;  // len is the "write head", ptr is the "read head"
  octet *data;

  void reset();

  public:

  void resize(size_t l);
  void resize_precise(size_t l);
  void resize_min(size_t l);
  void reserve(size_t l);
  void clear();

  void assign(const octetStream& os);

  octetStream() : len(0), mxlen(0), ptr(0), data(0) {}
  octetStream(size_t maxlen);
  octetStream(size_t len, const octet* source);
  octetStream(FlexBuffer& buffer);
  octetStream(const octetStream& os);
  octetStream& operator=(const octetStream& os)
    { if (this!=&os) { assign(os); }
      return *this;
    }
  ~octetStream() { if(data) delete[] data; }
  
  size_t get_ptr() const     { return ptr; }
  size_t get_length() const  { return len; }
  size_t get_max_length() const { return mxlen; }
  octet* get_data() const { return data; }
  octet* get_data_ptr() const { return data + ptr; }

  bool done() const 	  { return ptr == len; }
  bool empty() const 	  { return len == 0; }
  size_t left() const 	  { return len - ptr; }

  octetStream hash()   const;
  // output must have length at least HASH_SIZE
  void hash(octetStream& output)   const;
  // The following produces a check sum for debugging purposes
  bigint check_sum(int req_bytes=crypto_hash_BYTES)       const;

  void concat(const octetStream& os);

  void reset_read_head()  { ptr=0; }
  /* If we reset write head then we should reset the read head as well */
  void reset_write_head() { len=0; ptr=0; }

  // Move len back num
  void rewind_write_head(size_t num) { len-=num; }

  bool equals(const octetStream& a) const;
  bool operator==(const octetStream& a) const { return equals(a); }
  bool operator!=(const octetStream& a) const { return not equals(a); }

  /* Append NUM random bytes from dev/random */
  void append_random(size_t num);

  // Append with no padding for decoding
  void append(const octet* x,const size_t l);
  octet* append(const size_t l);
  void append_no_resize(const octet* x,const size_t l);
  // Read l octets, with no padding for decoding
  void consume(octet* x,const size_t l);
  // Return pointer to next l octets and advance pointer
  octet* consume(size_t l);

  /* Now store and restore different types of data (with padding for decoding) */

  void store_bytes(octet* x, const size_t l); //not really "bytes"...
  void get_bytes(octet* ans, size_t& l);      //Assumes enough space in ans

  void store(unsigned int a) { store_int(a, 4); }
  void store(int a);
  void get(unsigned int& a) { a = get_int(4); }
  void get(int& a);

  void store(size_t a) { store_int(a, 8); }
  void get(size_t& a) { a = get_int(8); }

  void store_int(size_t a, int n_bytes);
  size_t get_int(int n_bytes);

  template<int N_BYTES>
  void store_int(size_t a);
  template<int N_BYTES>
  size_t get_int();

  void store(const bigint& x);
  void get(bigint& ans);

  template<class T>
  void store(const T& x);

  template<class T>
  T get();
  template<class T>
  void get(T& ans);

  // works for all statically allocated types
  template <class T>
  void serialize(const T& x) { append((octet*)&x, sizeof(x)); }
  template <class T>
  void unserialize(T& x) { consume((octet*)&x, sizeof(x)); }

  template <class T>
  void store(const vector<T>& v);
  template <class T>
  void get(vector<T>& v, const T& init = {});
  template <class T>
  void get_no_resize(vector<T>& v);

  void consume(octetStream& s,size_t l)
    { s.resize(l);
      consume(s.data,l);
      s.len=l;
    }

  template<class T>
  void Send(T socket_num) const;
  template<class T>
  void Receive(T socket_num);

  void input(istream& s);
  void output(ostream& s);

  template<class T>
  void exchange(T send_socket, T receive_socket) { exchange(send_socket, receive_socket, *this); }
  template<class T>
  void exchange(T send_socket, T receive_socket, octetStream& receive_stream) const;

  friend ostream& operator<<(ostream& s,const octetStream& o);
  friend class PRNG;
};

class Player;

class octetStreams : public vector<octetStream>
{
public:
  octetStreams() {}
  octetStreams(const Player& P);

  void reset(const Player& P);
};


inline void octetStream::resize(size_t l)
{
  if (l<mxlen) { return; }
  l=2*l;      // Overcompensate in the resize to avoid calling this a lot
  resize_precise(l);
}


inline void octetStream::resize_precise(size_t l)
{
  if (l == mxlen)
    return;

  octet* nd=new octet[l];
  if (data)
    {
      memcpy(nd, data, min(len, l) * sizeof(octet));
      delete[] data;
    }
  data=nd;
  mxlen=l;
}

inline void octetStream::resize_min(size_t l)
{
  if (l > mxlen)
    resize_precise(l);
}

inline void octetStream::reserve(size_t l)
{
  if (len + l > mxlen)
    resize_precise(len + l);
}

inline octet* octetStream::append(const size_t l)
{
  if (len+l>mxlen)
    resize(len+l);
  octet* res = data + len;
  len+=l;
  return res;
}

inline void octetStream::append(const octet* x, const size_t l)
{
  avx_memcpy(append(l), x, l*sizeof(octet));
}

inline void octetStream::append_no_resize(const octet* x, const size_t l)
{
  avx_memcpy(data+len,x,l*sizeof(octet));
  len+=l;
}

inline octet* octetStream::consume(size_t l)
{
  if(ptr + l > len)
    throw runtime_error("insufficient data");
  octet* res = data + ptr;
  ptr += l;
  return res;
}

inline void octetStream::consume(octet* x,const size_t l)
{
  avx_memcpy(x, consume(l), l * sizeof(octet));
}

inline void octetStream::store_int(size_t l, int n_bytes)
{
  resize(len+n_bytes);
  encode_length(data+len,l,n_bytes);
  len+=n_bytes;
}

inline size_t octetStream::get_int(int n_bytes)
{
  return decode_length(consume(n_bytes), n_bytes);
}

template<int N_BYTES>
inline void octetStream::store_int(size_t l)
{
  assert(N_BYTES <= 8);
  resize(len+N_BYTES);
  uint64_t tmp = htole64(l);
  memcpy(data + len, &tmp, N_BYTES);
  len+=N_BYTES;
}

template<int N_BYTES>
inline size_t octetStream::get_int()
{
  assert(N_BYTES <= 8);
  size_t tmp = 0;
  memcpy(&tmp, consume(N_BYTES), N_BYTES);
  return le64toh(tmp);
}


template<class T>
inline void octetStream::Send(T socket_num) const
{
  send(socket_num,len,LENGTH_SIZE);
  send(socket_num,data,len);
}


template<class T>
inline void octetStream::Receive(T socket_num)
{
  size_t nlen=0;
  receive(socket_num,nlen,LENGTH_SIZE);
  len=0;
  resize_min(nlen);
  len=nlen;

  receive(socket_num,data,len);
  reset_read_head();
}

template<class T>
void octetStream::store(const T& x)
{
    x.pack(*this);
}

template<class T>
T octetStream::get()
{
    T res;
    res.unpack(*this);
    return res;
}

template<class T>
void octetStream::get(T& res)
{
    res.unpack(*this);
}

template<>
inline int octetStream::get()
{
    return get_int(sizeof(int));
}

template<class T>
void octetStream::store(const vector<T>& v)
{
  store(v.size());
  for (auto& x : v)
    store(x);
}

template<class T>
void octetStream::get(vector<T>& v, const T& init)
{
  size_t size;
  get(size);
  v.resize(size, init);
  for (auto& x : v)
    get(x);
}

template<class T>
void octetStream::get_no_resize(vector<T>& v)
{
  size_t size;
  get(size);
  if (size != v.size())
    throw runtime_error("wrong vector length");
  for (auto& x : v)
    get(x);
}

#endif

