#pragma once

#ifndef RESPONSE_H_MVRZEKPX
#define RESPONSE_H_MVRZEKPX

#include "rpc/detail/log.h"
#include "rpc/detail/make_unique.h"
#include "rpc/msgpack.hpp"

namespace rpc {
namespace detail {

//! \brief Represents a response and creates a msgpack to be sent back
//! as per the msgpack-rpc spec.
class response {
public:
    //! \brief Creates a response that represents a normal return value.
    //! \param id The sequence id (as per protocol).
    //! \param result The return value to store in the response.
    //! \tparam T Any msgpack-able type.
    //! \note If there is both an error and result in the response,
    //! the result will be discarded while packing the data.
    template <typename T> static response make_result(uint32_t id, T &&result);

    //! \brief Creates a response that represents an error.
    //! \param id The sequence id (as per protocol).
    //! \param error The error value to store in the response.
    //! \tparam T Any msgpack-able type.
    template <typename T> static response make_error(uint32_t id, T &&error);


    //! \brief Constructs a response from RPCLIB_MSGPACK::object (useful when
    //! reading a response from a stream).
    response(RPCLIB_MSGPACK::object_handle o);

    //! \brief Gets the response data as a RPCLIB_MSGPACK::sbuffer.
    RPCLIB_MSGPACK::sbuffer get_data() const;

    //! \brief Moves the specified object_handle into the response
    //! as a result.
    //! \param r The result to capture.
    void capture_result(RPCLIB_MSGPACK::object_handle &r);

    //! \brief Moves the specified object_handle into the response as an error.
    //! \param e The error to capture.
    void capture_error(RPCLIB_MSGPACK::object_handle &e);

    //! \brief Returns the call id/index used to identify which call
    //! this response corresponds to.
    uint32_t get_id() const;

    //! \brief Returns the error object stored in the response. Can
    //! be empty.
    std::shared_ptr<RPCLIB_MSGPACK::object_handle> get_error() const;

    //! \brief Returns the result stored in the response. Can be empty.
    std::shared_ptr<RPCLIB_MSGPACK::object_handle> get_result() const;

    //! \brief Gets an empty response which means "no response" (not to be
    //! confused with void return, i.e. this means literally
    //! "don't write the response to the socket")
    static response empty();

    //! \brief If true, this response is empty (\see empty())
    bool is_empty() const;

    //! \brief The type of a response, according to the msgpack-rpc spec
    using response_type =
        std::tuple<uint32_t, uint32_t, RPCLIB_MSGPACK::object, RPCLIB_MSGPACK::object>;

private:
    //! \brief Default constructor for responses.
    response();

    uint32_t id_;
    // I really wish to avoid shared_ptr here but at this point asio does not
    // work with move-only handlers in post() and I need to capture responses
    // in lambdas.
    std::shared_ptr<RPCLIB_MSGPACK::object_handle> error_;
    std::shared_ptr<RPCLIB_MSGPACK::object_handle> result_;
    bool empty_;
    RPCLIB_CREATE_LOG_CHANNEL(response)
};

template <typename T>
inline response response::make_result(uint32_t id, T &&result) {
    auto z = rpc::detail::make_unique<RPCLIB_MSGPACK::zone>();
    RPCLIB_MSGPACK::object o(std::forward<T>(result), *z);
    response inst;
    inst.id_ = id;
    inst.result_ = std::make_shared<RPCLIB_MSGPACK::object_handle>(o, std::move(z));
    return inst;
}

template <>
inline response
response::make_result(uint32_t id, std::unique_ptr<RPCLIB_MSGPACK::object_handle> &&r) {
    response inst;
    inst.id_ = id;
    inst.result_ = std::move(r);
    return inst;
}

template <typename T>
inline response response::make_error(uint32_t id, T &&error) {
    auto z = rpc::detail::make_unique<RPCLIB_MSGPACK::zone>();
    RPCLIB_MSGPACK::object o(std::forward<T>(error), *z);
    response inst;
    inst.id_ = id;
    inst.error_ = std::make_shared<RPCLIB_MSGPACK::object_handle>(o, std::move(z));
    return inst;
}

} /* detail */

} /* rpc  */

#endif /* end of include guard: RESPONSE_H_MVRZEKPX */
