#include <VWeb.h>

namespace VWeb {
#pragma region VWebSocketUtils
bool SocketUtils::MakeAsync(int socketId) {
  int flags = fcntl(socketId, F_GETFL, 0);
  if (flags == -1)
    return false;
  if (fcntl(socketId, F_SETFL, flags | O_NONBLOCK) == -1)
    return false;
  return true;
}
bool SocketUtils::Add(int socketId) { return MakeAsync(socketId); }
bool SocketUtils::Close(int socketId) { return close(socketId) >= 0; }
bool SocketUtils::WriteDone(int socketId) {
  return shutdown(socketId, SHUT_WR) != -1;
}
WriteState SocketUtils::Write(VWeb::SendData &sendData) {
  const char *buf = sendData.Content.c_str();
  ssize_t n;
  while (sendData.Size > 0) {
    n = send(sendData.SocketID, buf + sendData.Offset, sendData.Size, 0);
    if (n < 0) {
      if (errno == EAGAIN || errno == EWOULDBLOCK)
        return WriteState::EPOLL;
      fprintf(stderr, "[SocketUtils::Write] >> Im Errored\n");
      return WriteState::ERRORED;
    }
    sendData.Offset += n;
    sendData.Size -= n;
  }
  return WriteState::OK;
}
#pragma endregion VWebSocketUtils

#pragma region VWebSocketManager
SocketManager::SocketManager(const Ref<VWeb::ServerConfig> &serverConfig)
    : m_ServerConfig(serverConfig) {}
SocketManager::~SocketManager() { close(m_SocketID); }
Accept SocketManager::Handle() {
  Accept requestReturn{};
  int client =
      accept(m_SocketID, (sockaddr *)&m_Address, (socklen_t *)&m_AddressLength);
  if (client < 0) {
    if (errno == EAGAIN || errno == EWOULDBLOCK)
      requestReturn.ReturnValue = EPollReturns::BREAK;
    else
      requestReturn.ReturnValue = EPollReturns::FAILURE;
  } else {
    requestReturn.SockId = client;
    requestReturn.ReturnValue = EPollReturns::FAILURE;
    if (SocketUtils::Add(client) &&
        m_ServerConfig->EPoll->Dispatch(client, EPOLLIN | EPOLLET)) {
      requestReturn.ReturnValue = EPollReturns::OK;
    } else {
      SocketUtils::Close(client);
    }
  }
  return requestReturn;
}

bool SocketManager::SetSendListen(int socketID) {
  return m_ServerConfig->EPoll->UpdateEvents(socketID, EPOLLOUT | EPOLLET);
}
bool SocketManager::SetReadListen(int socketID) {
  return m_ServerConfig->EPoll->UpdateEvents(socketID, EPOLLIN | EPOLLET);
}
void SocketManager::Init() {
  if ((m_SocketID = socket(AF_INET, SOCK_STREAM, 0)) == 0)
    return Errored("Cannot Create Socket");

  auto trues = 1;
  struct timeval tv {
    1, 0
  };
  setsockopt(m_SocketID, SOL_SOCKET, SO_REUSEADDR, &trues, sizeof(int));
  setsockopt(m_SocketID, SOL_SOCKET, SO_REUSEPORT, &trues, sizeof(int));
  setsockopt(m_SocketID, SOL_SOCKET, SOCK_CLOEXEC, &trues, sizeof(int));
  setsockopt(m_SocketID, SOL_SOCKET, SOCK_NONBLOCK, &trues, sizeof(int));
  setsockopt(m_SocketID, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv);

  m_Address.sin_family = AF_INET;
  m_Address.sin_addr.s_addr = htonl(INADDR_ANY);
  m_Address.sin_port = htons(m_ServerConfig->Port);

  if (bind(m_SocketID, (struct sockaddr *)&m_Address, m_AddressLength) < 0)
    return Errored("Cannot Bind Socket");
  SocketUtils::MakeAsync(m_SocketID);
  if (listen(m_SocketID, SOMAXCONN) < 0)
    return Errored("Socket Failed to Listen");

  if (!m_ServerConfig->EPoll->Dispatch(m_SocketID,EPOLLIN | EPOLLET | EPOLLOUT))
    return Errored("Cannot Add Event");
}
void SocketManager::Errored(const std::string &data) {
  if (m_SocketID > 0)
    close(m_SocketID);
  fprintf(stderr, "%s\n", data.c_str());
  m_IsErrored = true;
}
#pragma endregion VWebSocketManager
} // namespace VWeb