#include "shm.h"

#include <xcb/xcb.h>
#include <xcb/xcbext.h>

#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "ui/gfx/x/xproto_internal.h"

namespace x11 {

Shm::Shm(x11::Connection* connection,
    const x11::QueryExtensionReply& info)
    : connection_(connection), info_(info) {}

template <> COMPONENT_EXPORT(X11)
void ReadEvent<Shm::CompletionEvent>(
    Shm::CompletionEvent* event_, ReadBuffer* buffer) {
  auto& buf = *buffer;

  auto& sequence = (*event_).sequence;
  auto& drawable = (*event_).drawable;
  auto& minor_event = (*event_).minor_event;
  auto& major_event = (*event_).major_event;
  auto& shmseg = (*event_).shmseg;
  auto& offset = (*event_).offset;

  // response_type
  uint8_t response_type;
  Read(&response_type, &buf);

  // pad0
  Pad(&buf, 1);

  // sequence
  Read(&sequence, &buf);

  // drawable
  Read(&drawable, &buf);

  // minor_event
  Read(&minor_event, &buf);

  // major_event
  Read(&major_event, &buf);

  // pad1
  Pad(&buf, 1);

  // shmseg
  Read(&shmseg, &buf);

  // offset
  Read(&offset, &buf);

  DCHECK_LE(buf.offset, 32ul);
}

Future<Shm::QueryVersionReply>
Shm::QueryVersion(
    const Shm::QueryVersionRequest& request) {
  if (!connection_->Ready() || !present())
    return {};

  WriteBuffer buf;

  // major_opcode
  uint8_t major_opcode = info_.major_opcode;
  buf.Write(&major_opcode);

  // minor_opcode
  uint8_t minor_opcode = 0;
  buf.Write(&minor_opcode);

  // length
  // Caller fills in length for writes.
  Pad(&buf, sizeof(uint16_t));

  Align(&buf, 4);

  return x11::SendRequest<Shm::QueryVersionReply>(connection_, &buf, false);
}

namespace detail {
template<> COMPONENT_EXPORT(X11)
std::unique_ptr<Shm::QueryVersionReply>
ReadReply<Shm::QueryVersionReply>(ReadBuffer* buffer) {
  auto& buf = *buffer;
  auto reply = std::make_unique<Shm::QueryVersionReply>();

  auto& shared_pixmaps = (*reply).shared_pixmaps;
  auto& sequence = (*reply).sequence;
  auto& major_version = (*reply).major_version;
  auto& minor_version = (*reply).minor_version;
  auto& uid = (*reply).uid;
  auto& gid = (*reply).gid;
  auto& pixmap_format = (*reply).pixmap_format;

  // response_type
  uint8_t response_type;
  Read(&response_type, &buf);

  // shared_pixmaps
  Read(&shared_pixmaps, &buf);

  // sequence
  Read(&sequence, &buf);

  // length
  uint32_t length;
  Read(&length, &buf);

  // major_version
  Read(&major_version, &buf);

  // minor_version
  Read(&minor_version, &buf);

  // uid
  Read(&uid, &buf);

  // gid
  Read(&gid, &buf);

  // pixmap_format
  Read(&pixmap_format, &buf);

  // pad0
  Pad(&buf, 15);

  Align(&buf, 4);
  DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length);

  return reply;
}
}  // namespace detail

Future<void>
Shm::Attach(
    const Shm::AttachRequest& request) {
  if (!connection_->Ready() || !present())
    return {};

  WriteBuffer buf;

  auto& shmseg = request.shmseg;
  auto& shmid = request.shmid;
  auto& read_only = request.read_only;

  // major_opcode
  uint8_t major_opcode = info_.major_opcode;
  buf.Write(&major_opcode);

  // minor_opcode
  uint8_t minor_opcode = 1;
  buf.Write(&minor_opcode);

  // length
  // Caller fills in length for writes.
  Pad(&buf, sizeof(uint16_t));

  // shmseg
  buf.Write(&shmseg);

  // shmid
  buf.Write(&shmid);

  // read_only
  buf.Write(&read_only);

  // pad0
  Pad(&buf, 3);

  Align(&buf, 4);

  return x11::SendRequest<void>(connection_, &buf, false);
}

Future<void>
Shm::Detach(
    const Shm::DetachRequest& request) {
  if (!connection_->Ready() || !present())
    return {};

  WriteBuffer buf;

  auto& shmseg = request.shmseg;

  // major_opcode
  uint8_t major_opcode = info_.major_opcode;
  buf.Write(&major_opcode);

  // minor_opcode
  uint8_t minor_opcode = 2;
  buf.Write(&minor_opcode);

  // length
  // Caller fills in length for writes.
  Pad(&buf, sizeof(uint16_t));

  // shmseg
  buf.Write(&shmseg);

  Align(&buf, 4);

  return x11::SendRequest<void>(connection_, &buf, false);
}

Future<void>
Shm::PutImage(
    const Shm::PutImageRequest& request) {
  if (!connection_->Ready() || !present())
    return {};

  WriteBuffer buf;

  auto& drawable = request.drawable;
  auto& gc = request.gc;
  auto& total_width = request.total_width;
  auto& total_height = request.total_height;
  auto& src_x = request.src_x;
  auto& src_y = request.src_y;
  auto& src_width = request.src_width;
  auto& src_height = request.src_height;
  auto& dst_x = request.dst_x;
  auto& dst_y = request.dst_y;
  auto& depth = request.depth;
  auto& format = request.format;
  auto& send_event = request.send_event;
  auto& shmseg = request.shmseg;
  auto& offset = request.offset;

  // major_opcode
  uint8_t major_opcode = info_.major_opcode;
  buf.Write(&major_opcode);

  // minor_opcode
  uint8_t minor_opcode = 3;
  buf.Write(&minor_opcode);

  // length
  // Caller fills in length for writes.
  Pad(&buf, sizeof(uint16_t));

  // drawable
  buf.Write(&drawable);

  // gc
  buf.Write(&gc);

  // total_width
  buf.Write(&total_width);

  // total_height
  buf.Write(&total_height);

  // src_x
  buf.Write(&src_x);

  // src_y
  buf.Write(&src_y);

  // src_width
  buf.Write(&src_width);

  // src_height
  buf.Write(&src_height);

  // dst_x
  buf.Write(&dst_x);

  // dst_y
  buf.Write(&dst_y);

  // depth
  buf.Write(&depth);

  // format
  uint8_t tmp0;
  tmp0 = static_cast<uint8_t>(format);
  buf.Write(&tmp0);

  // send_event
  buf.Write(&send_event);

  // pad0
  Pad(&buf, 1);

  // shmseg
  buf.Write(&shmseg);

  // offset
  buf.Write(&offset);

  Align(&buf, 4);

  return x11::SendRequest<void>(connection_, &buf, false);
}

Future<Shm::GetImageReply>
Shm::GetImage(
    const Shm::GetImageRequest& request) {
  if (!connection_->Ready() || !present())
    return {};

  WriteBuffer buf;

  auto& drawable = request.drawable;
  auto& x = request.x;
  auto& y = request.y;
  auto& width = request.width;
  auto& height = request.height;
  auto& plane_mask = request.plane_mask;
  auto& format = request.format;
  auto& shmseg = request.shmseg;
  auto& offset = request.offset;

  // major_opcode
  uint8_t major_opcode = info_.major_opcode;
  buf.Write(&major_opcode);

  // minor_opcode
  uint8_t minor_opcode = 4;
  buf.Write(&minor_opcode);

  // length
  // Caller fills in length for writes.
  Pad(&buf, sizeof(uint16_t));

  // drawable
  buf.Write(&drawable);

  // x
  buf.Write(&x);

  // y
  buf.Write(&y);

  // width
  buf.Write(&width);

  // height
  buf.Write(&height);

  // plane_mask
  buf.Write(&plane_mask);

  // format
  buf.Write(&format);

  // pad0
  Pad(&buf, 3);

  // shmseg
  buf.Write(&shmseg);

  // offset
  buf.Write(&offset);

  Align(&buf, 4);

  return x11::SendRequest<Shm::GetImageReply>(connection_, &buf, false);
}

namespace detail {
template<> COMPONENT_EXPORT(X11)
std::unique_ptr<Shm::GetImageReply>
ReadReply<Shm::GetImageReply>(ReadBuffer* buffer) {
  auto& buf = *buffer;
  auto reply = std::make_unique<Shm::GetImageReply>();

  auto& depth = (*reply).depth;
  auto& sequence = (*reply).sequence;
  auto& visual = (*reply).visual;
  auto& size = (*reply).size;

  // response_type
  uint8_t response_type;
  Read(&response_type, &buf);

  // depth
  Read(&depth, &buf);

  // sequence
  Read(&sequence, &buf);

  // length
  uint32_t length;
  Read(&length, &buf);

  // visual
  Read(&visual, &buf);

  // size
  Read(&size, &buf);

  Align(&buf, 4);
  DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length);

  return reply;
}
}  // namespace detail

Future<void>
Shm::CreatePixmap(
    const Shm::CreatePixmapRequest& request) {
  if (!connection_->Ready() || !present())
    return {};

  WriteBuffer buf;

  auto& pid = request.pid;
  auto& drawable = request.drawable;
  auto& width = request.width;
  auto& height = request.height;
  auto& depth = request.depth;
  auto& shmseg = request.shmseg;
  auto& offset = request.offset;

  // major_opcode
  uint8_t major_opcode = info_.major_opcode;
  buf.Write(&major_opcode);

  // minor_opcode
  uint8_t minor_opcode = 5;
  buf.Write(&minor_opcode);

  // length
  // Caller fills in length for writes.
  Pad(&buf, sizeof(uint16_t));

  // pid
  buf.Write(&pid);

  // drawable
  buf.Write(&drawable);

  // width
  buf.Write(&width);

  // height
  buf.Write(&height);

  // depth
  buf.Write(&depth);

  // pad0
  Pad(&buf, 3);

  // shmseg
  buf.Write(&shmseg);

  // offset
  buf.Write(&offset);

  Align(&buf, 4);

  return x11::SendRequest<void>(connection_, &buf, false);
}

Future<void>
Shm::AttachFd(
    const Shm::AttachFdRequest& request) {
  if (!connection_->Ready() || !present())
    return {};

  WriteBuffer buf;

  auto& shmseg = request.shmseg;
  auto& shm_fd = request.shm_fd;
  auto& read_only = request.read_only;

  // major_opcode
  uint8_t major_opcode = info_.major_opcode;
  buf.Write(&major_opcode);

  // minor_opcode
  uint8_t minor_opcode = 6;
  buf.Write(&minor_opcode);

  // length
  // Caller fills in length for writes.
  Pad(&buf, sizeof(uint16_t));

  // shmseg
  buf.Write(&shmseg);

  // shm_fd
  buf.fds().push_back(HANDLE_EINTR(dup(shm_fd.get())));

  // read_only
  buf.Write(&read_only);

  // pad0
  Pad(&buf, 3);

  Align(&buf, 4);

  return x11::SendRequest<void>(connection_, &buf, false);
}

Future<Shm::CreateSegmentReply>
Shm::CreateSegment(
    const Shm::CreateSegmentRequest& request) {
  if (!connection_->Ready() || !present())
    return {};

  WriteBuffer buf;

  auto& shmseg = request.shmseg;
  auto& size = request.size;
  auto& read_only = request.read_only;

  // major_opcode
  uint8_t major_opcode = info_.major_opcode;
  buf.Write(&major_opcode);

  // minor_opcode
  uint8_t minor_opcode = 7;
  buf.Write(&minor_opcode);

  // length
  // Caller fills in length for writes.
  Pad(&buf, sizeof(uint16_t));

  // shmseg
  buf.Write(&shmseg);

  // size
  buf.Write(&size);

  // read_only
  buf.Write(&read_only);

  // pad0
  Pad(&buf, 3);

  Align(&buf, 4);

  return x11::SendRequest<Shm::CreateSegmentReply>(connection_, &buf, true);
}

namespace detail {
template<> COMPONENT_EXPORT(X11)
std::unique_ptr<Shm::CreateSegmentReply>
ReadReply<Shm::CreateSegmentReply>(ReadBuffer* buffer) {
  auto& buf = *buffer;
  auto reply = std::make_unique<Shm::CreateSegmentReply>();

  auto& nfd = (*reply).nfd;
  auto& sequence = (*reply).sequence;
  auto& shm_fd = (*reply).shm_fd;

  // response_type
  uint8_t response_type;
  Read(&response_type, &buf);

  // nfd
  Read(&nfd, &buf);

  // sequence
  Read(&sequence, &buf);

  // length
  uint32_t length;
  Read(&length, &buf);

  // shm_fd
  shm_fd = base::ScopedFD(buf.TakeFd());

  // pad0
  Pad(&buf, 24);

  Align(&buf, 4);
  DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length);

  return reply;
}
}  // namespace detail

}  // namespace x11
