/*
 * This file is part of the Owl Positioning System (OwlPS) project.
 * It is subject to the copyright notice and license terms in the
 * COPYRIGHT.t2t file found in the top-level directory of this
 * distribution and at
 *   https://code.lm7.fr/mcy/owlps/src/master/COPYRIGHT.t2t
 * No part of the OwlPS Project, including this file, may be copied,
 * modified, propagated, or distributed except according to the terms
 * contained in the COPYRIGHT.t2t file; the COPYRIGHT.t2t file must be
 * distributed along with this file, either separately or by replacing
 * this notice by the COPYRIGHT.t2t file's contents.
 *
 ***********************************************************************
 *
 * This is the main source file of libowlps-client.
 */


#include "owlps-client.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include <sys/param.h>



/**
 * Opens a transmission UDP socket to a server in a simplified way.
 * This function uses owl_create_udp_trx_socket() (from libowlps)
 * as a backend, and allows to select a particular network interface.
 *
 * @param[in] dest_host host name or IP address of the remote server.
 * @param[in] dest_port Port on which the remote server listens.
 * @param[out] server Structure in which the server information will
 * be saved.
 * @param[in] iface The name of the network interface to send through.
 * If this parameter is `NULL` or an empty string, the interface will
 * be selected automatically.
 */
int owl_create_trx_socket(const char *const dest_host,
                          const uint_fast16_t dest_port,
                          struct sockaddr *const server,
                          const char *const iface)
{
  int sockfd = owl_create_udp_trx_socket(dest_host, dest_port, server) ;
  if (sockfd < 0)
    {
      perror("Error! Cannot create UDP sending socket to the aggregation"
             " server") ;
      exit(OWL_ERR_SOCKET_CREATE) ;
    }

  // If we specified an interface name
  if (iface != NULL && iface[0] != '\0')
    owl_use_iface(sockfd, iface) ;

  return sockfd ;
}



/**
 * Selects the interface named `iface` as a transmission interface for
 * the socket `sockfd`.
 */
void owl_use_iface(const int sockfd, const char *const iface)
{
#if defined(linux) && defined(__GLIBC__)
  if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, iface,
                 strlen(iface) + 1) == -1)
    {
      fprintf(stderr, "Error! Cannot select interface %s to send the"
              " packet: ", iface) ;
      perror("") ;
      fprintf(stderr, "Sending through the default interface.\n") ;
    }
#else // linux && __GLIBC__
  fprintf(stderr, "Cannot select interface %s to send the packet:"
	  " this option is currently Linux and GNU libc-specific.\n"
	  "Sending through the default interface.\n", iface) ;
#endif // linux && __GLIBC__
}



/**
 * Transmits a request.
 * No packet will be transmitted if the global variable `owl_run` is
 * `false`, and the transmission will stop as soon as possible if it
 * becomes `false`.
 *
 * @param[in] sockfd The file descriptor of the socket to use for the
 * transmission.
 * @param[in] server The server's information (as generated by
 * owl_create_trx_socket(), for example).
 * @param[in] packet The buffer to send through the socket.
 * @param[in] packet_size The buffer's size.
 * @param[in] nb_pkt Number of packets to transmit.
 * @param[in] delay Delay between two transmissions, in milliseconds.
 * @param[in] verbose If `true`, a "progress bar" will be printed on the
 * standard output.
 */
void owl_send_request(const int sockfd,
                      const struct sockaddr *const server,
                      const void *const packet,
                      const uint_fast16_t packet_size,
                      const uint_fast16_t nb_pkt,
                      const uint_fast32_t delay,
                      const bool verbose)
{
  uint_fast16_t i ;

  if (nb_pkt == 0 || !owl_run)
    return ;

  if (verbose)
    printf("Sent packets: ") ;

  // Transmit first packet:
  owl_send_packet(sockfd, server, packet, packet_size, verbose) ;

  // Transmit remaining packets (if any):
  for (i = 1 ; i < nb_pkt ; ++i)
    {
      uint16_t current_pkt = htons(i + 1) ;
      owl_msleep(delay) ; // Wait during the wanted delay
      if (! owl_run)
        break ;
      // Update the packet number:
      memcpy(&((char*) packet)[1], &current_pkt, sizeof(uint16_t)) ;
      owl_send_packet(sockfd, server, packet, packet_size, verbose) ;
    }

  if (verbose)
    putchar('\n') ;
}



/**
 * Transmits a single packet of a request.
 * See the documentation of the owl_send_request() function for an
 * explanation on the parameters.
 * When `verbose` if `true`, a dot will be printed on the standard output
 * and the standard output will be flushed.
 */
void owl_send_packet(const int sockfd,
                     const struct sockaddr *const server,
                     const void *const packet,
                     const uint_fast16_t packet_size,
                     const bool verbose)
{
  ssize_t nsent = sendto(sockfd, packet, packet_size, 0,
                         server, (socklen_t) sizeof(*server)) ;
  if (nsent != (ssize_t) packet_size)
    {
      perror("Error sending data to the network") ;
      exit(OWL_ERR_SOCKET_SEND) ;
    }

  if (verbose)
    {
      putchar('.') ;
      fflush(stdout) ;
    }
}
