#include <sys/un.h>
#include <sys/socket.h>
#include <zconf.h>
#include <netinet/in.h>

#include <spdlog/spdlog.h>

#include "include/communication/TCPSocketServer.hpp"
#include "include/communication/MessageProcessor.hpp"

#define SERVER_PORT 9090
#define TCP_LISTEN_BACKLOG 5

TCPSocketServer::TCPSocketServer() {
  server_thread = std::thread(&TCPSocketServer::server_loop, this);
  server_thread.detach();
}

[[noreturn]] void TCPSocketServer::server_loop() {
  struct sockaddr_in address;
  int socket_file_descriptor = socket(AF_INET, SOCK_STREAM, 0);
  if (socket_file_descriptor == 0) {
    spdlog::error("TCP socket initialization failed!");
    exit(EXIT_FAILURE);
  }

  int opt = 1;
  if (int err = setsockopt(socket_file_descriptor, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
    spdlog::error("TCP setsockopt failed! Err: {}", err);
    exit(EXIT_FAILURE);
  }
  address.sin_family = AF_INET;
  address.sin_addr.s_addr = INADDR_ANY;
  address.sin_port = htons(SERVER_PORT);

  if (bind(socket_file_descriptor, (struct sockaddr *) &address, sizeof(address)) < 0) {
    spdlog::error("TCP bind failed!");
    exit(EXIT_FAILURE);
  }

  if (listen(socket_file_descriptor, TCP_LISTEN_BACKLOG) < 0) {
    spdlog::error("TCP listen failed!");
    exit(EXIT_FAILURE);
  }

  char read_buffer[SOCKET_BUFFER_SIZE];
  char write_buffer[SOCKET_BUFFER_SIZE];
  for (;;) {
    int client_file_descriptor = accept(socket_file_descriptor, NULL, NULL);
    if (client_file_descriptor < 0) {
      spdlog::error("TCP accept failed!");
      continue;
    }

    read(client_file_descriptor, read_buffer, 1);
    uint8_t message_size = read_buffer[0];

    read(client_file_descriptor, read_buffer, read_buffer[0]);
    std::string string_message;
    for (int i{}; i < message_size; i++) {
      string_message += read_buffer[i];
    }

    auto response = MessageProcessor::process_message(string_message);
    uint8_t response_size = response->ByteSizeLong();
    write_buffer[0] = response_size;
    write(client_file_descriptor, write_buffer, 1);

    response->SerializeToArray(write_buffer, SOCKET_BUFFER_SIZE);
    write(client_file_descriptor, write_buffer, response_size);

    close(client_file_descriptor);
  }
}