/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file LICENSE.rst or https://cmake.org/licensing for details.  */
#include "cmDebuggerPosixPipeConnection.h"

#include <cerrno>
#include <cstring>
#include <stdexcept>
#include <utility>

#include <unistd.h>

#include <sys/socket.h>
#include <sys/types.h>

namespace cmDebugger {

#ifndef _WIN32

cmDebuggerPipeConnection_POSIX::cmDebuggerPipeConnection_POSIX(
  std::string name)
  : PipeName(std::move(name))
{
  addr.sun_path[0] = '\0';
}

cmDebuggerPipeConnection_POSIX::~cmDebuggerPipeConnection_POSIX()
{
  if (isOpen()) {
    close();
  }
}

bool cmDebuggerPipeConnection_POSIX::StartListening(std::string& errorMessage)
{
  listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);
  if (listen_fd < 0) {
    errorMessage = "Failed to create socket: ";
    errorMessage += strerror(errno);
    return false;
  }

  addr.sun_family = AF_UNIX;
  strncpy(addr.sun_path, PipeName.c_str(), sizeof(addr.sun_path));
  addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
  if (bind(listen_fd, (sockaddr*)&addr, sizeof(addr)) == -1) {
    errorMessage = "Failed to bind name '";
    errorMessage += addr.sun_path;
    errorMessage += "' to socket: ";
    errorMessage += strerror(errno);
    close_listen();
    return false;
  }

  if (listen(listen_fd, 1) == -1) {
    errorMessage = "Failed to listen on socket: ";
    errorMessage += strerror(errno);
    close_listen();
    return false;
  }

  StartedListening.set_value();
  return true;
}

std::shared_ptr<dap::Reader> cmDebuggerPipeConnection_POSIX::GetReader()
{
  return std::static_pointer_cast<dap::Reader>(shared_from_this());
}

std::shared_ptr<dap::Writer> cmDebuggerPipeConnection_POSIX::GetWriter()
{
  return std::static_pointer_cast<dap::Writer>(shared_from_this());
}

bool cmDebuggerPipeConnection_POSIX::isOpen()
{
  return rw_pipe >= 0;
}

void cmDebuggerPipeConnection_POSIX::close()
{
  close_listen();
  ::close(rw_pipe);
  rw_pipe = -1;
}

void cmDebuggerPipeConnection_POSIX::close_listen()
{
  if (strlen(addr.sun_path) > 0) {
    unlink(addr.sun_path);
    addr.sun_path[0] = '\0';
  }
  ::close(listen_fd);
  listen_fd = -1;
}

void cmDebuggerPipeConnection_POSIX::WaitForConnection()
{
  sockaddr_un laddr;
  socklen_t len = sizeof(laddr);
  rw_pipe = accept(listen_fd, (sockaddr*)&laddr, &len);
  if (rw_pipe < 0) {
    close();
    return;
  }

  close_listen(); // no longer need the listen resources
}

size_t cmDebuggerPipeConnection_POSIX::read(void* buffer, size_t n)
{
  size_t result = 0;
  if (rw_pipe >= 0) {
    ssize_t count = ::read(rw_pipe, buffer, n);
    if (count <= 0) {
      // EOF or error (including EBADF from a concurrent close).
      close();
      return 0;
    }
    result = static_cast<size_t>(count);
  }

  return result;
}

bool cmDebuggerPipeConnection_POSIX::write(void const* buffer, size_t n)
{
  bool result = false;
  if (rw_pipe >= 0) {
    result = ::write(rw_pipe, buffer, n) >= 0;
    if (!result) {
      close();
    }
  }

  return result;
}

cmDebuggerPipeClient_POSIX::cmDebuggerPipeClient_POSIX(std::string name)
  : PipeName(std::move(name))
{
}

cmDebuggerPipeClient_POSIX::~cmDebuggerPipeClient_POSIX()
{
  close();
}

void cmDebuggerPipeClient_POSIX::WaitForConnection()
{
  rw_pipe = socket(AF_UNIX, SOCK_STREAM, 0);
  if (rw_pipe < 0) {
    throw std::runtime_error(std::string("Failed to create socket: ") +
                             strerror(errno));
  }

  sockaddr_un addr;
  addr.sun_family = AF_UNIX;
  strncpy(addr.sun_path, PipeName.c_str(), sizeof(addr.sun_path));
  addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
  if (connect(rw_pipe, (sockaddr*)&addr, sizeof(addr)) == -1) {
    close();
    throw std::runtime_error(
      std::string("Failed to connect path to socket: ") + strerror(errno));
  }
}

bool cmDebuggerPipeClient_POSIX::isOpen()
{
  return rw_pipe >= 0;
}

void cmDebuggerPipeClient_POSIX::close()
{
  if (isOpen()) {
    ::close(rw_pipe);
    rw_pipe = -1;
  }
}

void cmDebuggerPipeClient_POSIX::ShutdownForTesting()
{
  if (isOpen()) {
    ::shutdown(rw_pipe, SHUT_RDWR);
  }
}

size_t cmDebuggerPipeClient_POSIX::read(void* buffer, size_t n)
{
  ssize_t count = 0;
  if (isOpen()) {
    count = ::read(rw_pipe, buffer, n);
    if (count <= 0) {
      // EOF or error (including EBADF from a concurrent close).
      close();
      return 0;
    }
  }

  return static_cast<size_t>(count);
}

bool cmDebuggerPipeClient_POSIX::write(void const* buffer, size_t n)
{
  int count = 0;
  if (isOpen()) {
    count = static_cast<int>(::write(rw_pipe, buffer, n));
    if (count < 0) {
      close();
    }
  }

  return count > 0;
}

#endif // !_WIN32

} // namespace cmDebugger
