#ifndef ANDROID_PDX_UDS_IPC_HELPER_H_
#define ANDROID_PDX_UDS_IPC_HELPER_H_

#include <sys/socket.h>
#include <utility>
#include <vector>

#include <pdx/rpc/serializable.h>
#include <pdx/rpc/serialization.h>
#include <pdx/status.h>
#include <pdx/utility.h>

namespace android {
namespace pdx {
namespace uds {

// Test interfaces used for unit-testing payload sending/receiving over sockets.
class SendInterface {
 public:
  virtual ssize_t Send(int socket_fd, const void* data, size_t size,
                       int flags) = 0;
  virtual ssize_t SendMessage(int socket_fd, const msghdr* msg, int flags) = 0;

 protected:
  virtual ~SendInterface() = default;
};

class RecvInterface {
 public:
  virtual ssize_t Receive(int socket_fd, void* data, size_t size,
                          int flags) = 0;
  virtual ssize_t ReceiveMessage(int socket_fd, msghdr* msg, int flags) = 0;

 protected:
  virtual ~RecvInterface() = default;
};

// Helper methods that allow to send/receive data through abstract interfaces.
// Useful for mocking out the underlying socket I/O.
Status<void> SendAll(SendInterface* sender, const BorrowedHandle& socket_fd,
                     const void* data, size_t size);
Status<void> SendMsgAll(SendInterface* sender, const BorrowedHandle& socket_fd,
                        const msghdr* msg);
Status<void> RecvAll(RecvInterface* receiver, const BorrowedHandle& socket_fd,
                     void* data, size_t size);
Status<void> RecvMsgAll(RecvInterface* receiver,
                        const BorrowedHandle& socket_fd, msghdr* msg);

#define RETRY_EINTR(fnc_call)                 \
  ([&]() -> decltype(fnc_call) {              \
    decltype(fnc_call) result;                \
    do {                                      \
      result = (fnc_call);                    \
    } while (result == -1 && errno == EINTR); \
    return result;                            \
  })()

class SendPayload : public MessageWriter, public OutputResourceMapper {
 public:
  SendPayload(SendInterface* sender = nullptr) : sender_{sender} {}
  Status<void> Send(const BorrowedHandle& socket_fd);
  Status<void> Send(const BorrowedHandle& socket_fd, const ucred* cred);

  // MessageWriter
  void* GetNextWriteBufferSection(size_t size) override;
  OutputResourceMapper* GetOutputResourceMapper() override;

  // OutputResourceMapper
  Status<FileReference> PushFileHandle(const LocalHandle& handle) override;
  Status<FileReference> PushFileHandle(const BorrowedHandle& handle) override;
  Status<FileReference> PushFileHandle(const RemoteHandle& handle) override;
  Status<ChannelReference> PushChannelHandle(
      const LocalChannelHandle& handle) override;
  Status<ChannelReference> PushChannelHandle(
      const BorrowedChannelHandle& handle) override;
  Status<ChannelReference> PushChannelHandle(
      const RemoteChannelHandle& handle) override;

 private:
  SendInterface* sender_;
  ByteBuffer buffer_;
  std::vector<int> file_handles_;
};

class ReceivePayload : public MessageReader, public InputResourceMapper {
 public:
  ReceivePayload(RecvInterface* receiver = nullptr) : receiver_{receiver} {}
  Status<void> Receive(const BorrowedHandle& socket_fd);
  Status<void> Receive(const BorrowedHandle& socket_fd, ucred* cred);

  // MessageReader
  BufferSection GetNextReadBufferSection() override;
  void ConsumeReadBufferSectionData(const void* new_start) override;
  InputResourceMapper* GetInputResourceMapper() override;

  // InputResourceMapper
  bool GetFileHandle(FileReference ref, LocalHandle* handle) override;
  bool GetChannelHandle(ChannelReference ref,
                        LocalChannelHandle* handle) override;

 private:
  RecvInterface* receiver_;
  ByteBuffer buffer_;
  std::vector<LocalHandle> file_handles_;
  size_t read_pos_{0};
};

template <typename FileHandleType>
class ChannelInfo {
 public:
  FileHandleType data_fd;
  FileHandleType event_fd;

 private:
  PDX_SERIALIZABLE_MEMBERS(ChannelInfo, data_fd, event_fd);
};

template <typename FileHandleType>
class ChannelConnectionInfo {
 public:
  FileHandleType channel_fd;

 private:
  PDX_SERIALIZABLE_MEMBERS(ChannelConnectionInfo, channel_fd);
};

template <typename FileHandleType>
class RequestHeader {
 public:
  int32_t op{0};
  ucred cred;
  uint32_t send_len{0};
  uint32_t max_recv_len{0};
  std::vector<FileHandleType> file_descriptors;
  std::vector<ChannelInfo<FileHandleType>> channels;
  std::array<uint8_t, 32> impulse_payload;
  bool is_impulse{false};

 private:
  PDX_SERIALIZABLE_MEMBERS(RequestHeader, op, send_len, max_recv_len,
                           file_descriptors, channels, impulse_payload,
                           is_impulse);
};

template <typename FileHandleType>
class ResponseHeader {
 public:
  int32_t ret_code{0};
  uint32_t recv_len{0};
  std::vector<FileHandleType> file_descriptors;
  std::vector<ChannelInfo<FileHandleType>> channels;

 private:
  PDX_SERIALIZABLE_MEMBERS(ResponseHeader, ret_code, recv_len, file_descriptors,
                           channels);
};

template <typename T>
inline Status<void> SendData(const BorrowedHandle& socket_fd, const T& data) {
  SendPayload payload;
  rpc::Serialize(data, &payload);
  return payload.Send(socket_fd);
}

template <typename FileHandleType>
inline Status<void> SendData(const BorrowedHandle& socket_fd,
                             const RequestHeader<FileHandleType>& request) {
  SendPayload payload;
  rpc::Serialize(request, &payload);
  return payload.Send(socket_fd, &request.cred);
}

Status<void> SendData(const BorrowedHandle& socket_fd, const void* data,
                      size_t size);
Status<void> SendDataVector(const BorrowedHandle& socket_fd, const iovec* data,
                            size_t count);

template <typename T>
inline Status<void> ReceiveData(const BorrowedHandle& socket_fd, T* data) {
  ReceivePayload payload;
  Status<void> status = payload.Receive(socket_fd);
  if (status && rpc::Deserialize(data, &payload) != rpc::ErrorCode::NO_ERROR)
    status.SetError(EIO);
  return status;
}

template <typename FileHandleType>
inline Status<void> ReceiveData(const BorrowedHandle& socket_fd,
                                RequestHeader<FileHandleType>* request) {
  ReceivePayload payload;
  Status<void> status = payload.Receive(socket_fd, &request->cred);
  if (status && rpc::Deserialize(request, &payload) != rpc::ErrorCode::NO_ERROR)
    status.SetError(EIO);
  return status;
}

Status<void> ReceiveData(const BorrowedHandle& socket_fd, void* data,
                         size_t size);
Status<void> ReceiveDataVector(const BorrowedHandle& socket_fd,
                               const iovec* data, size_t count);

size_t CountVectorSize(const iovec* data, size_t count);
void InitRequest(android::pdx::uds::RequestHeader<BorrowedHandle>* request,
                 int opcode, uint32_t send_len, uint32_t max_recv_len,
                 bool is_impulse);

Status<void> WaitForEndpoint(const std::string& endpoint_path,
                             int64_t timeout_ms);

}  // namespace uds
}  // namespace pdx
}  // namespace android

#endif  // ANDROID_PDX_UDS_IPC_HELPER_H_
