// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef MEDIA_CAPTURE_CONTENT_FEEDBACK_SIGNAL_ACCUMULATOR_H_
#define MEDIA_CAPTURE_CONTENT_FEEDBACK_SIGNAL_ACCUMULATOR_H_

#include "base/time/time.h"
#include "media/capture/capture_export.h"

namespace media {

// Utility class for maintaining an exponentially-decaying average of feedback
// signal values whose updates occur at undetermined, possibly irregular time
// intervals.
//
// Feedback signals can be made by multiple sources.  Meaning, there can be
// several values provided for the same timestamp.  In this case, the greatest
// value is retained and used to re-compute the average.  Therefore, the values
// provided to this class' methods should be appropriately translated with this
// in mind.  For example, an "fraction available" metric should be translated
// into a "fraction utilized" one.
//
// Usage note: Reset() must be called at least once before the first call to
// Update().
//
// This template class supports data points that are timestamped using either
// |base::TimeDelta| or |base::TimeTicks|.
template <typename TimeType>
class FeedbackSignalAccumulator {
 public:
  // |half_life| is the amount of time that must pass between two data points to
  // move the accumulated average value halfway in-between.  Example: If
  // |half_life| is one second, then calling Reset(0.0, t=0s) and then
  // Update(1.0, t=1s) will result in an accumulated average value of 0.5.
  explicit FeedbackSignalAccumulator(base::TimeDelta half_life)
      : half_life_(half_life), average_(NAN) {
    DCHECK(half_life_ > base::TimeDelta());
  }

  // Erase all memory of historical values, re-starting with the given
  // |starting_value|.
  void Reset(double starting_value, TimeType timestamp) {
    average_ = update_value_ = prior_average_ = starting_value;
    reset_time_ = update_time_ = prior_update_time_ = timestamp;
  }

  TimeType reset_time() const { return reset_time_; }

  // Apply the given |value|, which was observed at the given |timestamp|, to
  // the accumulated average.  If the timestamp is in chronological order, the
  // update succeeds and this method returns true.  Otherwise the update has no
  // effect and false is returned.  If there are two or more updates at the same
  // |timestamp|, only the one with the greatest value will be accounted for
  // (see class comments for elaboration).
  bool Update(double value, TimeType timestamp) {
    DCHECK(!std::isnan(average_)) << "Reset() must be called once.";

    if (timestamp < update_time_) {
      return false;  // Not in chronological order.
    } else if (timestamp == update_time_) {
      if (timestamp == reset_time_) {
        // Edge case: Multiple updates at reset timestamp.
        average_ = update_value_ = prior_average_ =
            std::max(value, update_value_);
        return true;
      }
      if (value <= update_value_)
        return true;
      update_value_ = value;
    } else {
      prior_average_ = average_;
      prior_update_time_ = update_time_;
      update_value_ = value;
      update_time_ = timestamp;
    }

    const double elapsed_us = static_cast<double>(
        (update_time_ - prior_update_time_).InMicroseconds());
    const double weight =
        elapsed_us / (elapsed_us + half_life_.InMicroseconds());
    average_ = weight * update_value_ + (1.0 - weight) * prior_average_;
    DCHECK(std::isfinite(average_));

    return true;
  }

  TimeType update_time() const { return update_time_; }

  // Returns the current accumulated average value.
  double current() const { return average_; }

 private:
  // In conjunction with the |update_time_| and |prior_update_time_|, this is
  // used to compute the weight of the current update value versus the prior
  // accumulated average.
  const base::TimeDelta half_life_;

  TimeType reset_time_;   // |timestamp| passed in last call to Reset().
  double average_;        // Current accumulated average.
  double update_value_;   // Latest |value| accepted by Update().
  TimeType update_time_;  // Latest |timestamp| accepted by Update().
  double prior_average_;  // Accumulated average before last call to Update().
  TimeType prior_update_time_;  // |timestamp| in prior call to Update().
};

}  // namespace media

#endif  // MEDIA_CAPTURE_CONTENT_FEEDBACK_SIGNAL_ACCUMULATOR_H_
