#include "networking.h"

char buffer[1000];

////////////
// Server //
////////////

server_connection::server_connection() {
  struct ifaddrs *ifAddrStruct = NULL;
  struct ifaddrs *ifa = NULL;
  void *tmpAddrPtr = NULL;
  char addressBuffer[INET_ADDRSTRLEN];
  
  // Creating socket file descriptor 
  if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { 
    perror("socket failed"); 
    exit(EXIT_FAILURE); 
  }
  
  // Get available interfaces
  getifaddrs(&ifAddrStruct);
  
  // Display them and have the user select which to use
  clear();
  int i = 0;
  
  mvprintw(0, 0, "Select interface to listen");
  
  for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
    if (!ifa->ifa_addr) {
      continue;
    }
    
    if (ifa->ifa_addr->sa_family == AF_INET) {
      tmpAddrPtr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
      inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
      i++;
      mvprintw(i, 0, "%d) %s", i, addressBuffer);
    }
  }
  
  int key = getch();
  while ((key - '1') < 0 || (key - '1') > i - 1) key = getch();
  
  i = 0;
  key = key - '1';
  
  // Use the selected IP to bind to the port
  address.sin_family = AF_INET;
  address.sin_port = htons(BATTLESHIP_PORT);
  
  for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
    if (!ifa->ifa_addr) {
      continue;
    }
    
    if (ifa->ifa_addr->sa_family == AF_INET) {
      tmpAddrPtr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
      inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
      if (i == key) {
        address.sin_addr.s_addr = inet_addr(addressBuffer);
        break;
      }
      i++;
    }
  }
  
  // Free the addresses, we don't need them anymore
  if (ifAddrStruct != NULL) freeifaddrs(ifAddrStruct);
  
  // Bind the socket
  if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
    perror("bind failed");
    exit(EXIT_FAILURE);
  }
  
  if (listen(server_fd, 0) < 0) {
    perror("listen failed");
    exit(EXIT_FAILURE);
  }
  
  clear();
  
  mvprintw(0, 0, "Waiting for connections");
  mvprintw(1, 0, "IP: %s", addressBuffer);
   
  refresh();
  
  if ((server_sock = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
    perror("accept failed");
    exit(EXIT_FAILURE);
  }
}

server_connection::~server_connection() {
  close(server_sock);
  close(server_fd);
}

std::string server_connection::exchange_message(const std::string &message) {
  strcpy(buffer, message.c_str());
  buffer[message.length()] = ';';
  int bytes_to_send = message.length() + 1;
  
  send(server_sock, buffer, bytes_to_send, 0);
  
  char tmp = 0;
  std::string received;
  
  while (true) {
    read(server_sock, &tmp, 1);
    if (tmp == ';') break;
    received.push_back(tmp);
  }
  
  return received;
}

////////////
// Client //
////////////

client_connection::client_connection() {
  // Create socket
  if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    perror("socket failed");
    exit(EXIT_FAILURE);
  }
  
  memset(&serv_addr, '0', sizeof(serv_addr));
  
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons(BATTLESHIP_PORT);
  
  clear();
  
  while (true) {
    mvprintw(0, 0, "Enter IP address:");
    move(1, 0);
    clrtoeol();
    refresh();
    std::string ip = getstring();
    
    if (inet_pton(AF_INET, ip.c_str(), &serv_addr.sin_addr) <= 0) {
      mvprintw(3, 0, "Invalid IP");
      refresh();
      continue;
    }
    
    if (connect(client_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
      move(3, 0);
      clrtoeol();
      mvprintw(3, 0, "Failed to connect");
      continue;
    }
    
    break;
  }
}

client_connection::~client_connection() {
  close(client_sock);
}

std::string client_connection::exchange_message(const std::string &message) {
  char tmp = 0;
  std::string received;
  
  while (true) {
    read(client_sock, &tmp, 1);
    if (tmp == ';') break;
    received.push_back(tmp);
  }
  
  strcpy(buffer, message.c_str());
  buffer[message.length()] = ';';
  int bytes_to_send = message.length() + 1;
  
  send(client_sock, buffer, bytes_to_send, 0);
  
  return received;
}