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


#ifndef _OWLPS_POSITIONING_MEASUREMENT_HH_
#define _OWLPS_POSITIONING_MEASUREMENT_HH_

#include "capturepoint.hh"

#include <map>
#include <ostream>
#include <cmath>

using pkt_id_t = uint_fast16_t ;
using ss_t = int_fast16_t ;

/// Represents a list of signal strengths captured by one CapturePoint
class Measurement
{
protected:
  /// The CapturePoint that performed the measurement
  const CapturePoint *cp ;
  /// List of signal strengths captured (in dBm)
  std::map<pkt_id_t, ss_t> ss_list ;
  /// Average of all the captured signal strengths (dBm)
  float average_dbm ;
  /// Average of all the captured signal strengths (mW)
  float average_mw ;
  /// Variance of all the captured signal strengths (mW)
  float variance_mw ;
  /// Variance of all the captured signal strengths (dBm)
  float variance_dbm ;

private:
  /// Intermediate variable used to compute the variance (mW)
  float variance_mw_m2 ;
  /// Intermediate variable used to compute the variance (dBm)
  float variance_dbm_m2 ;
  /// Number of elements taken into account in the variance computation
  unsigned int variance_size ;


protected:
  /** @name Operations */
  //@{
  /// Recalculates entirely the average and variance from #ss_list
  void recalculate_average(void) ;
  /// Update the average and variance with a new SS
  void update_average(const ss_t ss_dbm) ;
  /// Counts the number of SS of source that are within the interval
  /// [average_dbm-bound;average_dbm+bound]
  unsigned int nb_in_interval(
    const Measurement &source, const float bound) const ;
  //@}


public:
  explicit Measurement(const CapturePoint *const _cp = nullptr):
    cp(_cp), average_dbm(0),
    average_mw(0), variance_mw(0), variance_dbm(0),
    variance_mw_m2(0), variance_dbm_m2(0), variance_size(0) {}

  Measurement(const Measurement &source):
    cp(source.cp), ss_list(source.ss_list),
    average_dbm(source.average_dbm), average_mw(source.average_mw),
    variance_mw(source.variance_mw), variance_dbm(source.variance_dbm),
    variance_mw_m2(0), variance_dbm_m2(0), variance_size(0) {}

  ~Measurement(void) ;

  /** @name Read accessors */
  //@{
  /// Returns the CapturePoint associated with the Measurement
  const CapturePoint* get_cp(void) const ;
  /// Returns the packet number `pkt_id`
  ss_t get_ss(const pkt_id_t pkt_id) const ;
  /// Returns the mean of the SS list, in dBm
  float get_average_dbm(void) const ;
  /// Returns the variance of the SS list
  float get_variance_mw(void) const ;
  /// Returns the standard deviation of the SS list (mW)
  float get_std_deviation_mw(void) const ;
  /// Returns the standard deviation of the SS list (dBm)
  float get_std_deviation_dbm(void) const ;
  /// Returns the number of SS in the SS list
  int get_nb_ss(void) const ;
  //@}

  /** @name Write accessors */
  //@{
  /// Associate the Measurement with a CapturePoint
  void set_cp(const CapturePoint *const _cp) ;
  /// Adds a signal strength (in dBm) to #ss_list
  void add_ss(const pkt_id_t packet_id, const ss_t ss_dbm) ;
  /// Adds several signal strengths (in dBm) to #ss_list
  void add_ss_list(const std::map<pkt_id_t, ss_t> &_ss_list) ;
  /// Merges a given Measurement into the current Measurement
  void merge(const Measurement &source) ;
  /// Reset all the attributes
  void clear(void) ;
  //@}

  /** @name Operations */
  //@{
  /// Computes the similarity with another Measurement in the SS space
  float similarity(const Measurement &source) const ;
  /// Computes the distance to another Measurement's SS average
  float ss_square_distance(const Measurement &source) const ;
  /// Computes the distance to another SS value (in dBm)
  float ss_square_distance(const float ss_dbm) const ;
  /// Computes the distance to another Measurement's variance (mW)
  float variance_mw_square_distance(const Measurement &source) const ;
  /// Computes the distance to another variance value in mW
  float variance_mw_square_distance(const float var) const ;
  //@}

  /** @name Operators */
  //@{
  Measurement& operator=(const Measurement &m) ;
  bool operator==(const Measurement &m) const ;
  bool operator!=(const Measurement &m) const ;
  operator bool(void) const ;
  //@}

  /** @name Conversion accessors */
  //@{
  /// Converts to a CSV string
  const std::string to_csv(void) const ;
  //@}

  /// Displays a Measurement
  friend std::ostream &operator<<(std::ostream &os, const Measurement &m) ;
} ;



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


inline const CapturePoint* Measurement::get_cp() const
{
  return cp ;
}


inline float Measurement::get_average_dbm() const
{
  return average_dbm ;
}


inline float Measurement::get_variance_mw() const
{
  return variance_mw ;
}


inline float Measurement::get_std_deviation_mw() const
{
  return sqrtf(variance_mw) ;
}


inline float Measurement::get_std_deviation_dbm() const
{
  return sqrtf(variance_dbm) ;
}


inline int Measurement::get_nb_ss() const
{
  return ss_list.size() ;
}



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


inline void Measurement::set_cp(const CapturePoint *const _cp)
{
  cp = _cp ;
}



/* *** Operations *** */


inline float Measurement::
ss_square_distance(const Measurement &source) const
{
  return ss_square_distance(source.average_dbm) ;
}


inline float Measurement::ss_square_distance(const float ss_dbm) const
{
  return ((ss_dbm - average_dbm) * (ss_dbm - average_dbm)) ;
}


inline float Measurement::
variance_mw_square_distance(const Measurement &source) const
{
  return variance_mw_square_distance(source.variance_mw) ;
}


inline float Measurement::
variance_mw_square_distance(const float var) const
{
  return ((var - variance_mw) * (var - variance_mw)) ;
}



/* *** Operators *** */


inline bool Measurement::operator!=(const Measurement &m) const
{
  return !(*this == m) ;
}


/**
 * @returns `false` if the Measurement is empty.
 * @returns `true` if at least one attribute is initialised.
 */
inline Measurement::operator bool() const
{
  return
    cp ||
    ! ss_list.empty() ;
}



#endif // _OWLPS_POSITIONING_MEASUREMENT_HH_
