/*
 * 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 "request.hh"
#include "calibrationrequest.hh"
#include "referencepoint.hh"

#include <sstream>

using namespace std ;



/* *** Constructors *** */


Request::Request(
  const Mobile *_mobile,
  const Timestamp &_time_sent,
  const unordered_map<string, Measurement> &_measurements
):
  type(OWL_REQUEST_UNDEFINED), nb_packets(1),
  mobile(_mobile), time_sent(_time_sent),
  measurements(_measurements), real_position(nullptr)
{
  received_now() ;
}


Request::Request(const unordered_map<string, Measurement>
                 &_measurements):
  type(OWL_REQUEST_UNDEFINED), nb_packets(1),
  mobile(nullptr), measurements(_measurements), real_position(nullptr)
{
  received_now() ;
}


Request::Request(
  const Timestamp &_time_sent,
  const unordered_map<string, Measurement> &_measurements
):
  type(OWL_REQUEST_UNDEFINED), nb_packets(1),
  mobile(nullptr), time_sent(_time_sent),
  measurements(_measurements), real_position(nullptr)
{
  received_now() ;
}


Request::Request(const Request &source):
  type(source.type), nb_packets(source.nb_packets),
  mobile(source.mobile), time_sent(source.time_sent),
  time_received(source.time_received),
  measurements(source.measurements), real_position(nullptr)
{
  if (source.real_position)
    real_position = new Point3D(*source.real_position) ;
}


/**
 * Note that the value pointed by #mobile is not deleted.
 */
Request::~Request()
{
  measurements.clear() ;
  delete real_position ;
}



/* *** Read accessors *** */


/**
 * @param mac_receiver The MAC address of the receiver CP.
 * @returns A pointer on the found measurement, or nullptr if no
 * measurement was made by this CP.
 */
const Measurement*
Request::get_measurement(const string &mac_receiver) const
{
  auto m = measurements.find(mac_receiver) ;
  if (m != measurements.end())
    return &m->second ;
  return nullptr ;
}



/* *** Write accessors *** */


inline void Request::clear_real_position()
{
  if (real_position)
    {
      delete real_position ;
      real_position = nullptr ;
    }
}


void Request::set_real_position(const Point3D &_real_position)
{
  if (real_position)
    *real_position = _real_position ;
  else
    real_position = new Point3D(_real_position) ;
}


/**
 * - #nb_packets is set to 1 (this is the default value when
 *   constructing a Request).
 * - #mobile is set to nullptr, but the value it pointed to is not
 *   deleted.
 * - The fields of #time_sent and #time_received are initialised to 0.
 * - #measurements is cleared.
 */
void Request::clear()
{
  type = OWL_REQUEST_UNDEFINED ;
  nb_packets = 1 ;
  mobile = nullptr ;
  time_sent.clear() ;
  time_received.clear() ;
  measurements.clear() ;
  clear_real_position() ;
}



/* *** Operations *** */


float Request::similarity(const Request &source) const
{
  unordered_map<string, Measurement>
  source_measurements(source.measurements) ;
  unordered_map<string, Measurement>
  my_measurements(measurements) ;

  PosUtil::complete_with_dummy_measurements(
    my_measurements, source_measurements) ;

  return PosUtil::similarity(
           my_measurements, source_measurements) ;
}



/* *** Operators *** */


Request& Request::operator=(const Request &source)
{
  if (this == &source)
    return *this ;

  type = source.type ;
  nb_packets = source.nb_packets ;
  mobile = source.mobile ;
  time_sent = source.time_sent ;
  time_received = source.time_received ;
  measurements = source.measurements ;

  clear_real_position() ;
  if (source.real_position)
    real_position = new Point3D(*source.real_position) ;

  return *this ;
}


bool Request::operator==(const Request &source) const
{
  if (this == &source)
    return true ;

  bool real_position_equal ;
  if (real_position == source.real_position)
    real_position_equal = true ; // equal pointers
  else if (real_position && source.real_position)
    real_position_equal = *real_position == *source.real_position ;
  else
    real_position_equal = false ; // one of the two is null

  return
    real_position_equal &&
    type == source.type &&
    nb_packets == source.nb_packets &&
    mobile == source.mobile &&
    time_sent == source.time_sent &&
    time_received == source.time_received &&
    measurements == source.measurements ;
}



const string Request::to_csv() const
{
  ostringstream csv_line ;

  // CSV format version
  // (this should match OWL_LATEST_AGGREGATION_CSV_FORMAT)
  constexpr unsigned int aggregation_csv_format = 1 ;

  csv_line << aggregation_csv_format << ';' ;

  if (mobile)
    csv_line << mobile->get_mac_addr() ;

  csv_line
      << ';' << static_cast<uint_fast16_t>(type)
      << ';' << nb_packets
      << ';' << time_sent
      << ';' ;

  const CalibrationRequest *calibration_request =
    dynamic_cast<const CalibrationRequest*>(this) ;
  if (!calibration_request)
    csv_line << "0;0;0;0" ;
  else
    {
      csv_line
          << calibration_request->get_reference_point()->get_x() << ';'
          << calibration_request->get_reference_point()->get_y() << ';'
          << calibration_request->get_reference_point()->get_z() << ';'
          << static_cast<int>(calibration_request->get_direction()) ;
    }

  for (auto i = measurements.begin() ; i != measurements.end() ; ++i)
    csv_line << ';' << i->second.to_csv() ;

  return csv_line.str() ;
}



ostream& operator<<(ostream &os, const Request &r)
{
  // Timestamp
  os << "At " << r.time_sent
     << " (received at " << r.time_received << "; " ;

  if (r.real_position)
    os << "Real coordinates: " << *r.real_position << "; " ;

  // MAC address
  os << "Type: " << static_cast<uint_fast16_t>(r.type)
     << ", Number of packets sent: " << r.nb_packets
     << ", Mobile: "
     << (r.mobile ? r.mobile->get_mac_addr() : "Unknown_Mobile")
     << ":" ;

  // List of Measurements
  if (r.measurements.empty())
    os << " No values" ;
  else
    for (auto i = r.measurements.begin() ; i != r.measurements.end() ;
         ++i)
      os << '\n' << i->first << ": " << i->second ;

  return os ;
}
