#ifndef BIBUNM_PAYLOAD_H
#define BIBUNM_PAYLOAD_H

#include <string>
#include <memory>
#include <functional>

namespace bibunm
{

///
/// \brief The binary blob you want to send somewhere.
///
/// The Payload class is a very fancy buffer. If you understand unique_ptrs you understand the ownership semantics of
/// the payload. Usually when one obtains a message they intend to send on any IO, they create a buffer of specified size,
/// then they write to the buffer (encoding a message) and finally send it with Unix send(socket, ...) or whatever the
/// case may be. BiBun does not work exactly like that because it is asynchronous and the main network IO occurs in one
/// thread. This requires messages (and the payloads they hold) to be queued and therefore it is irresponsible to
/// expect the user to hold onto a message until it can be sent. <b>Therefore, you relinquish all ownership over the
/// buffer to the Payload.</b> You do not care how it is stored, cleaned up, etc. It will be deallocated when
/// appropriate. A simple example follows.
///
/// \code{.cpp}
///
/// // You allocate your own buffer.
/// char *buffer = new char[1000];
/// if(!buffer)
///   return -1; // or throw.
///
/// // Encode your message. This could be converting a JSON C++ object to a cstring.
/// int rc = encode_message(buffer, sizeof(buffer), msg);
/// // handle errors.
///
/// // Now you create a payload. You relinquish any rights to the buffer.
/// Payload payload(buffer,
///                 sizeof(buffer), // Or a smaller value, if encoding determined that the whole buffer was not used.
///                 "DESCRIPTION_OF_ENCODING" // So someone on the otherside knows how to decode your payload.
/// );
///
/// // Now you can pass your payload onto another function, bibunm::request_objects::Message, or be sent with a
/// // bibunm::Client.
/// \endcode
///
/// \remark It is important to note that you specify the size of the data contained in the buffer. The actual size of the buffer
/// is not important, as long as it is larger. The smaller the buffer the better, because this will be the number of
/// bytes (at least for the payload) sent over the network... even if you used 3 bytes of a 1KB buffer. <>
///
class Payload
{
public:
   ///
   /// \brief Constructs a payload, reserving size to write a buffer in.
   ///
   /// This constructor allocates space for a buffer. You can then use this allocated space in your routines with the
   /// buffer functions. See \ref buffer_fns. You do not need to worry about the buffer being deallocated. It will be
   /// automatically when the payload is destroyed. Very often, this will not be up to you.
   /// \param reserve_size The size in bytes of the buffer. Remember to leave +1 for null-termination if required.
   /// \param type User-defined string to describe the contents of the buffer. See type() for a more detailed
   /// description.
   ///
   Payload(size_t reserve_size, std::string type);

   // Usual constructor when you want to send a payload, but you are already managing your own resource. Pass the
   // buffer pointer and its size... if the buffer is null-terminated use strlen. type is a user-chosen description
   // of what is held in the buffer. The deleter is a C++ function, the default will deallocate a buffer allocated with
   // new char[size]. If it was allocated differently, deallocate it appropriately.

   ///
   /// \brief Constructs a payload, with an already existing (and allocated) buffer.
   ///
   /// You would usually use this constructor when you have already allocated space for your buffer and may have already
   /// written into it. Just remember, as always, the Payload owns your buffer now. <b>You have no right to go delete it
   /// or change its size.</b>
   /// \param data Pointer to the allocated buffer.
   /// \param length Length in bytes of the buffer.
   /// \param deleter How we deallocate buffer. The default is with delete[]. If you used malloc or some weird pool or
   ///   something this is your chance to specify how to deallocate it. You can use whatever callable object you want.
   ///   Lambda, functor, function...
   Payload(char *data, size_t length, std::string type,
           std::function<void(char*)> deleter = [](char *ptr) {
      if(ptr) delete[] ptr;
   });

   /// @name Access and description of the Payload buffer
   /// \anchor buffer_fns
   /// These methods give you access to the buffer... the actual message. Take note of what actually belongs to you.
   /// Don't trample all over the buffer with your damn memcpys, etc.
   /// @{

   ///
   /// \brief Get the buffer owned by the Payload.
   ///
   /// You have write access to this buffer, just remember that its size is
   ///   provided by size(). Don't delete this buffer. Remember that I own this buffer. It is part of the payload. The
   ///   payload in order to access it. So avoid storing this pointer somewhere.
   /// \return char buffer pointer.
   ///
   char *getBuffer();

   ///
   /// \brief Get the buffer owned by the Payload.
   ///
   /// Read-only access to the buffer.
   /// \return char buffer pointer.
   ///
   const char *getBuffer() const;

   ///
   /// \brief The size of the buffer.
   ///
   /// This value will include the space of null-termination if that was provided.
   /// \return The size of the buffer in bytes.
   ///
   size_t size() const;

   ///
   /// \brief Gets some project-defined description of what kind of object is contained in the buffer.
   ///
   /// This is essential for the client to decide how it should decode the buffer. Did you send JSON, is it encoded
   ///   in UTF16 for some obscure reason. This is up to the BiBun clients to decide and there should be some agreement
   ///   or contract to what each description means.
   /// \return User-defined string describing the object contained in the buffer.
   ///
   const std::string &type() const;

   /// @}

   Payload(const Payload &other);
   Payload &operator=(const Payload &other);

   Payload(Payload &&other) noexcept;
   Payload &operator=(Payload &&other) noexcept;

   /// @name Ownership
   /// These functions deal provide a means to release the buffer to ownership of the caller, the caller can then put
   /// it in their own memory management scheme. This is mostly used for language bindings which require the buffer
   /// and will deallocate it in their own event loop.
   /// @{

   ///
   /// \brief Do not deallocate the buffer when the Payload is deleted.
   ///
   /// This is used when the caller decides s/he would like to manage the buffer's lifetime from now on. Remember that
   /// you are tied to deleting the buffer to the way it was allocated. So you must use the deleter itself...
   /// \note { Call getFreeCallback() first to acquire the deallocation function. If you don't do that you will never
   /// know how to delete the buffer. }
   ///
   void release();

   ///
   /// \brief Provides the deallocation callback.
   /// \return The callback to delete the buffer.
   ///
   const std::function<void(char*)> getFreeCallback() const;

   /// @}

   virtual ~Payload();
private:
   char* _data;
   std::function<void(char*)> _deleter;
   size_t _size;
   std::string _type;
};

}

#endif // BIBUNM_PAYLOAD_H
