#include "seobeo/seobeo.h"

int Seobeo_CreateSocket(int32 stream, const char *host,  const char* port, int32 backlog)
{
  struct addrinfo hints = {0}, *server_infos, *free_server_info;
  int32 sock_fd, yes = 1;  // Need this for setsockopt 

  hints.ai_family   = AF_INET;
  hints.ai_socktype = stream ? SOCK_STREAM   : SOCK_DGRAM;
  hints.ai_protocol = stream ? IPPROTO_TCP   : IPPROTO_UDP;
  hints.ai_flags    = stream ? AI_PASSIVE    : 0;

  if (getaddrinfo(host, port, &hints, &server_infos) != 0)
  {
    perror("getaddrinfo");
    return -1;
  }

  for
  (
    free_server_info = server_infos;
    free_server_info != NULL;
    free_server_info = free_server_info->ai_next
  )
  {
    if
    (
      (sock_fd = socket(
        free_server_info->ai_family, free_server_info->ai_socktype,
        free_server_info->ai_protocol
      )) == -1
    )
    {
      perror("socket");
      continue;
    }
    
     if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1)
     {
       perror("setsockopt");
       continue;
     }

     if (bind(sock_fd, free_server_info->ai_addr, free_server_info->ai_addrlen) == -1)
     {
       close(sock_fd);
       perror("setsockopt");
       continue;
     }

     // UDP should be non blocking
     if(!stream)
     {
       if (fcntl(sock_fd, F_SETFL, O_NONBLOCK) != 0)
       {
         close(sock_fd);
         perror("v_network: Couldn't make socket non-blocking\n");
         return -1;
       }
     }

     // binded to a open server infos;
     break;
  }

  // No longer need these values
  freeaddrinfo(server_infos);

  if (free_server_info == NULL)
  {
    perror("No free server");
    return -1;
  }

  // TCP listen
  if(stream)
  {
    if (listen(sock_fd, backlog) != 0)
    {
      perror("listen");
      close(sock_fd);
      return -1;
    }
  }

  return sock_fd;
}

Seobeo_PHandle Seobeo_Stream_Handle_Create(const char *host,  const char* port)
{
  Seobeo_PHandle p_handle;
  p_handle = malloc(sizeof(*p_handle));

  p_handle->socket = Seobeo_CreateSocket(1, host,  port, 10); // socke fd
  p_handle->host = host != NULL ? strdup(host) : "localhost";
  p_handle->port = strdup(port);

  p_handle->read_buffer = malloc(sizeof(*p_handle->read_buffer) * INITIAL_BUFFER_CAPACITY);
  p_handle->read_buffer_capacity = INITIAL_BUFFER_CAPACITY;
  p_handle->read_buffer_len = 0;

  p_handle->write_buffer = malloc(sizeof(*p_handle->read_buffer) * INITIAL_BUFFER_CAPACITY);
  p_handle->write_buffer_capacity = INITIAL_BUFFER_CAPACITY;
  p_handle->write_buffer_len = 0;

  p_handle->file = NULL;
  p_handle->text_copy = NULL;
  p_handle->file_name = NULL;

  return p_handle;
}

Seobeo_PHandle Seobeo_Stream_Handle_Accept(Seobeo_PHandle p_server_handle)
{
  struct sockaddr_storage addr;
  socklen_t addrlen = sizeof addr;
  char client_inet_addr[INET6_ADDRSTRLEN];
  int client_fd = accept(p_server_handle->socket,
                         (struct sockaddr*)&addr,
                         &addrlen);
  inet_ntop(
      addr.ss_family,
      Seobeo_GetIP4OrIP6((struct sockaddr *)&addr),
      client_inet_addr, sizeof client_inet_addr);

  if (client_fd == -1)
  {
    return NULL;
  }

  Seobeo_PHandle p_client_handle = malloc(sizeof *p_client_handle);

  p_client_handle->socket               = client_fd;
  p_client_handle->host                 = strdup(client_inet_addr);
  p_client_handle->port                 = NULL;

  p_client_handle->read_buffer_capacity = p_server_handle->read_buffer_capacity;
  p_client_handle->read_buffer_len      = 0;
  p_client_handle->read_buffer          = malloc(p_client_handle->read_buffer_capacity);

  p_client_handle->write_buffer_capacity = p_server_handle->write_buffer_capacity;
  p_client_handle->write_buffer_len      = 0;
  p_client_handle->write_buffer          = malloc(p_client_handle->write_buffer_capacity);

  return p_client_handle;
}

void Seobeo_Handle_Destroy(Seobeo_PHandle p_handle)
{
  close(p_handle->socket);
  free(p_handle->host);
  free(p_handle->port);
  free(p_handle->read_buffer);
  free(p_handle->write_buffer);
  free(p_handle->file);
  free(p_handle->text_copy);
  free(p_handle->file_name);
  free(p_handle);
}

int Seobeo_Handle_Flush(Seobeo_PHandle p_handle)
{
  uint32 total = p_handle->write_buffer_len;
  uint32 sent  = 0;

  while (sent < total)
  {
    ssize_t n = write(
      p_handle->socket,
      p_handle->write_buffer + sent,
      total - sent
    );
    if (n < 0) {
      if (errno == EINTR)  continue;
      if (errno == EAGAIN) return 1;
      return -1;
    }
    sent += (uint32)n;
  }

  p_handle->write_buffer_len = 0;
  return 0;
}

int Seobeo_Handle_Queue(Seobeo_PHandle p_handle, const uint8_t *data, uint32_t data_size)
{
  if (p_handle->write_buffer_len + data_size > p_handle->write_buffer_capacity)
  {
    int rc = Seobeo_Handle_Flush(p_handle);
    if (rc < 0) return -1;
    if (rc > 0) return 1;
  }

  if (data_size > p_handle->write_buffer_capacity)
  {
    uint32_t offset = 0;
    while (offset < data_size)
    {
      ssize_t n = write(p_handle->socket,
                data + offset,
                data_size - offset);
      if (n < 0)
      {
        if (errno == EINTR)  continue;
        if (errno == EAGAIN) return 1;
        return -1;
      }
      offset += (uint32_t)n;
    }
    return 0;
  }

  memcpy(p_handle->write_buffer + p_handle->write_buffer_len,
         data,
         data_size);
  p_handle->write_buffer_len += data_size;
  return 0;
}

int Seobeo_Handle_Read(Seobeo_PHandle p_handle)
{
  // How many bytes we can still read into the buffer
  uint32 free_space = p_handle->read_buffer_capacity - p_handle->read_buffer_len;
  if (free_space == 0)
    return -1;

  ssize_t n = read(
    p_handle->socket,
    p_handle->read_buffer + p_handle->read_buffer_len,
    free_space
  );
  if (n < 0) {
    if (errno == EINTR)    return Seobeo_Handle_Read(p_handle);
    if (errno == EAGAIN)   return 0;   // no data available right now
    return -1;                        // fatal error
  }
  if (n == 0) {
    return -2;   // peer closed the connection
  }

  p_handle->read_buffer_len += (uint32)n;
  return (int)n;  // number of bytes read
}

void Seobeo_Handle_Consume(Seobeo_PHandle p_handle, uint32 consumed)
{
  if (consumed >= p_handle->read_buffer_len)
  {
    p_handle->read_buffer_len = 0;
    return;
  }

  // Slide remaining bytes to the front
  memmove(
    p_handle->read_buffer,
    p_handle->read_buffer + consumed,
    p_handle->read_buffer_len - consumed
  );
  p_handle->read_buffer_len -= consumed;
}

void *Seobeo_GetIP4OrIP6(struct sockaddr *sa)
{
  if (sa->sa_family == AF_INET) 
  {
    return &(((struct sockaddr_in*)sa)->sin_addr);
  }

  return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
