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

using namespace std ;



/* *** Constructors *** */


/**
 * Clears #requests, but does not deallocate the values pointed by
 * the elements into it.
 */
ReferencePoint::~ReferencePoint()
{
  requests.clear() ;
}



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


double ReferencePoint::
average_measurements(const std::string &mac_transmitter) const
{
  unordered_map<string, Measurement> measurements(
    get_all_measurements(mac_transmitter)) ;
  double avg = 0 ;
  int n_ss = 0 ;

  for (auto i = measurements.begin() ; i != measurements.end() ; ++i)
    {
      avg += i->second.get_average_dbm() ;
      ++n_ss ;
    }

  return (avg / n_ss) ;
}


unordered_map<string, Measurement> ReferencePoint::
get_all_measurements() const
{
  unordered_map<string, Measurement> all ;

  for (auto i = requests.begin() ; i != requests.end() ; ++i)
    {
      unordered_map<string, Measurement> measurements =
        (*i)->get_measurements() ;
      for (auto j = measurements.begin() ; j != measurements.end() ; ++j)
        if (! all.insert(*j).second)
          all[j->first].merge(j->second) ;
    }

  return all ;
}


unordered_map<string, Measurement> ReferencePoint::
get_all_measurements(const string &mac_transmitter) const
{
  unordered_map<string, Measurement> all ;
  vector<CalibrationRequest*> requests_trx(
    get_requests(mac_transmitter)) ;

  for (auto i = requests_trx.begin() ; i != requests_trx.end() ; ++i)
    {
      unordered_map<string, Measurement> measurements =
        (*i)->get_measurements() ;
      for (auto j = measurements.begin() ; j != measurements.end() ; ++j)
        if (! all.insert(*j).second)
          all[j->first].merge(j->second) ;
    }

  return all ;
}


/**
 * @param mac_transmitter The MAC address of the transmitting mobile.
 *
 * @returns A vector containing all the requests sent by the mobile.
 * The returned vector is empty if no request was sent by the mobile.
 */
const vector<CalibrationRequest*> ReferencePoint::
get_requests(const string &mac_transmitter) const
{
  vector<CalibrationRequest*> res ;

  for (auto i = requests.begin() ; i != requests.end() ; ++i)
    if ((*i)->get_mobile()->get_mac_addr() == mac_transmitter)
      res.push_back(*i) ;

  return res ;
}



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


void ReferencePoint::delete_request(const CalibrationRequest *const r)
{
  for (auto i = requests.begin() ; i != requests.end() ; ++i)
    if (*i == r)
      {
        requests.erase(i) ;
        return ;
      }
}


/**
 * Note that the requests pointed by the elements of #requests are
 * actually deleted from the Stock.
 */
void ReferencePoint::delete_requests()
{
#ifndef NDEBUG
  int stock_nb_requests = Stock::nb_calibration_requests() ;
#endif // NDEBUG

  for (auto r = requests.begin() ; r != requests.end() ; ++r)
    Stock::delete_calibration_request(**r) ;

  assert(Stock::nb_calibration_requests() ==
         stock_nb_requests - requests.size()) ;

  requests.clear() ;
}


/**
 * Note that the requests pointed by the elements of #requests are
 * actually deleted from the Stock.
 *
 * @returns `true` if at least one request was deleted.
 * @returns `false` if the ReferencePoint was left untouched.
 */
bool ReferencePoint::delete_generated_requests(void)
{
  unsigned int nb_requests = requests.size() ;
  auto r = requests.begin() ;
  while (r != requests.end())
    {
      assert(*r) ;
      unordered_map<std::string, CapturePoint>::const_iterator cp ;

      if (! (*r)->get_mobile())
        goto delete_request ;

      // Check if the request was sent by a CP
      for (cp = Stock::get_cps().begin() ; cp != Stock::get_cps().end() ;
           ++cp)
        if ((*r)->get_mobile()->get_mac_addr() ==
            cp->second.get_mac_addr())
          break ;

      if (cp != Stock::get_cps().end()) // r was sent by a CP
        {
          ++r ;
          continue ; // Do not delete r
        }

      // r is not associated with a CP, delete it
delete_request:
      Stock::delete_calibration_request(**r) ;
      r = requests.erase(r) ;
    }

  return nb_requests != requests.size() ;
}



/* *** Operations *** */


/**
 * Before to compute the similarity, all the measurements containted in
 * #requests are put together, as if it was one big request.
 *
 * Note: to compute the similarity between two requests, one should use
 * Request::similarity().
 */
float ReferencePoint::similarity(const Request &source) const
{
  assert(! requests.empty()) ;

  unordered_map<string, Measurement>
  source_measurements(source.get_measurements()) ;
  unordered_map<string, Measurement>
  all_measurements(get_all_measurements()) ;

  PosUtil::complete_with_dummy_measurements(
    all_measurements, source_measurements) ;

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


/**
 * @param cp_mac The MAC address of the CapturePoint to work on.
 *
 * @returns The Friis index associated to the CapturePoint.
 * @returns 0 if the CP is unknown at this ReferencePoint.
 */
float ReferencePoint::
friis_index_for_cp(const string &cp_mac) const
{
  const CapturePoint &cp = Stock::get_cp(cp_mac) ;
  double const_term = cp.friis_constant_term() ;

  int nb_friis_idx = 0 ;
  double friis_idx_sum =
    friis_indexes_for_cp(cp, const_term, nb_friis_idx) ;

  if (nb_friis_idx == 0)
    return 0 ;
  return friis_idx_sum / nb_friis_idx ;
}


/**
 * Computes a Friis index sum for the distance CP-ReferencePoint,
 * based on the measurements of this CP that are present in the
 * ReferencePoint. To obtain the real (averaged) Friis index, one
 * has to divide the returned sum by the number of indexes.
 *
 * @param[in] cp The CapturePoint to work on.
 * @param[in] const_term The "constant" part of the computation.
 * @param[out] nb_indexes The number of indexes computed.
 *
 * @returns The sum of all Friis indexes for the CapturePoint.
 * @returns 0 if the CP is unknown at this ReferencePoint.
 */
float ReferencePoint::
friis_indexes_for_cp(const CapturePoint &cp,
                     const double const_term,
                     int &nb_indexes) const
{
  nb_indexes = 0 ;
  double friis_idx_sum = 0 ;

  const string &cp_mac = cp.get_mac_addr() ;
  float distance = this->distance(cp.get_coordinates()) ;

  /*
   * Compute an index for the CP's Measurement in each Request in the
   * ReferencePoint. The Friis index for the CP is the average of all
   * these indexes (we do not compute the average in this function).
   */
  for (auto request = requests.begin() ; request != requests.end() ;
       ++request)
    {
      const unordered_map<string, Measurement> &measurements =
        (*request)->get_measurements() ;
      auto measurement = measurements.find(cp_mac) ;
      if (measurement != measurements.end())
        {
          float ss = measurement->second.get_average_dbm() ;
          assert((*request)->get_mobile()) ;
          float mobile_gain =
            (*request)->get_mobile()->get_antenna_gain() ;
          float mobile_pow =
            (*request)->get_mobile()->get_trx_power() ;
          friis_idx_sum +=
            (const_term + mobile_gain + mobile_pow - ss)
            / (10 * log10(distance)) ;
          ++nb_indexes ;
        }
    }

  return friis_idx_sum ;
}


/**
 * Computes a Friis index for the distance CP-ReferencePoint, based on a
 * given packet (`pkt_id),` of a measurement of this CP present in the
 * ReferencePoint. This measurement is the first found in the
 * ReferencePoint. This works well when we keep only one calibration
 * request per reference point.
 *
 * @param cp_mac The MAC address of the CapturePoint to work on.
 * @param pkt_id The packet ID to look for.
 *
 * @returns The Friis index for this CP and packet.
 * @returns 0 if the CP is unknown at this ReferencePoint, or if there
 * is no packet with the wanted ID.
 */
float ReferencePoint::
friis_index_for_cp(const string &cp_mac, const pkt_id_t pkt_id) const
{
  const CapturePoint &cp = Stock::get_cp(cp_mac) ;
  double const_term = cp.friis_constant_term() ;
  float distance = this->distance(cp.get_coordinates()) ;
  float friis_idx = 0 ;

  for (auto request = requests.begin() ; request != requests.end() ;
       ++request)
    {
      const unordered_map<string, Measurement> &measurements =
        (*request)->get_measurements() ;
      auto measurement = measurements.find(cp_mac) ;
      if (measurement == measurements.end())
        continue ;
      ss_t ss = measurement->second.get_ss(pkt_id) ;
      if (ss == 0)
        continue ;

      assert((*request)->get_mobile()) ;
      float mobile_gain = (*request)->get_mobile()->get_antenna_gain() ;
      float mobile_pow = (*request)->get_mobile()->get_trx_power() ;
      friis_idx =
        (const_term + mobile_gain + mobile_pow - ss)
        / (10 * log10(distance)) ;
      break ;
    }

  return friis_idx ;
}



/* *** Operators *** */


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

  this->Point3D::operator=(source) ;
  requests = source.requests ;

  return *this ;
}


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

  return
    this->Point3D::operator==(source) &&
    requests == source.requests ;
}



ostream &operator<<(ostream &os, const ReferencePoint &rp)
{
  // Coordinates
  os << static_cast<Point3D>(rp) ;

  // List of requests
  if (rp.requests.empty())
    os << "\nNo request." << '\n' ;
  else
    for (auto i = rp.requests.begin() ; i != rp.requests.end() ; ++i)
      os << '\n' << **i ;

  return os ;
}
