/*
 * 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.
 */


#include "outputtcpsocketevaal.hh"
#include "request.hh"
#include "resultlist.hh"
#include "area.hh"
#include "stock.hh"
#include "posexcept.hh"

#include <iostream>
#include <sstream>
#include <cstdio> // For perror()
#include <boost/lexical_cast.hpp>

using namespace std ;



/* *** Constructors *** */


OutputTCPSocketEvAAL::
OutputTCPSocketEvAAL(const string &_remote_host,
                     const uint_fast16_t _remote_port):
  OutputNetworkSocket(_remote_host, _remote_port)
{
  if (! init_socket())
    throw error_opening_output_file("TCP socket (EvAAL)") ;
}



/* *** Operations *** */


/**
 * @returns `true` if the socket were successfully opened.
 * @returns `false` in case of error.
 */
bool OutputTCPSocketEvAAL::init_socket()
{
  sockfd = socket(AF_INET, SOCK_STREAM, 0) ;
  if (sockfd < 0)
    {
      perror("TCP socket creation failed") ;
      return false ;
    }

  struct sockaddr_in server_description ;
  memset(&server_description, 0, sizeof(server_description)) ;
  server_description.sin_family = AF_INET ;
  // Server IP address:
  server_description.sin_addr.s_addr = inet_addr(remote_host.c_str()) ;
  // Listening port on the server:
  server_description.sin_port = htons(remote_port) ;

  int ret = connect(sockfd, (struct sockaddr *)&server_description,
                    sizeof(server_description)) ;
  if (ret < 0)
    {
      perror("Cannot bind the TCP socket") ;
      return false ;
    }

  return true ;
}


/**
 * @param results Must contain only one element, since the EvAAL format
 * accepts only one algorithm result.
 */
void OutputTCPSocketEvAAL::write(const ResultList &results)
{
  assert(results.get_results().size() <= 1) ;
  if (! results.get_results().empty())
    write(results.get_results()[0]) ;
}


void OutputTCPSocketEvAAL::write(const Result &result)
{
  const Point3D &position = result.get_position() ;
  Timestamp request_time = result.get_request()->get_time_sent() ;
  int area_of_interest = area_of_interest_number(position) ;

  ostringstream str ;
  str
      << "OwlPS "
      << position.get_x() << ' '
      << position.get_y() << ' '
      << static_cast<uint64_t>(request_time) << ' ' ;

  if (area_of_interest > 0)
    str << area_of_interest ;

  str << '\n' ;

  if (send_data(str.str()))
    acknowledge() ;
}


int OutputTCPSocketEvAAL::
area_of_interest_number(const Point3D &position) const
{
  const Area *const area = Stock::in_which_area_is(position) ;
  if (!area)
    return 0 ;

  int aoi_number = 0 ;
  try
    {
      aoi_number = boost::lexical_cast<int>(area->get_name()) ;
    }
  catch (boost::bad_lexical_cast &e)
    {
      cerr
          << "Cannot convert the area of interest's name (\""
          << area->get_name() << "\") into an integer value: "
          << e.what() << '\n' ;
    }

  return aoi_number ;
}


/**
 * Sends the text buffer 'data'.
 */
bool OutputTCPSocketEvAAL::send_data(const string &data) const
{
  // Do *not* add one extra character for the '\0', it must not be sent!
  unsigned int data_len = data.size() ;

  ssize_t nsent = send(sockfd, data.c_str(), data_len, 0) ;
  if (nsent != static_cast<ssize_t>(data_len))
    {
      perror("Error sending result data through the TCPEvAAL socket") ;
      return false ;
    }
  return true ;
}


bool OutputTCPSocketEvAAL::acknowledge() const
{
  char ack_buf[10] ;
  ssize_t nrecv = recv(sockfd, ack_buf, 10, 0) ;
  if (nrecv < 0)
    perror("Error receiving acknowledgement") ;

  ack_buf[9] = '\0' ;
  string ack(ack_buf) ;
  if (ack == "Published")
    return true ;

  if (ack == "NotParsed")
    {
      cerr << "TCP server error: string not parsed.\n" ;
      return false ;
    }

  cerr
      << "TCP server error: unknown acknoledgment string \""
      << ack << "\".\n" ;
  return false ;
}
