added support for onChange report

Report is now notified when metric changes and updates reading values.

Tested:
  - Added new unit tests
  - OnChange report updates Readings when metric values changes

Change-Id: I3be9ef7aa0486cb15bac627aa1de5cc632613b3b
Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com>
diff --git a/src/metric.cpp b/src/metric.cpp
index 3e450d6..a9cf45e 100644
--- a/src/metric.cpp
+++ b/src/metric.cpp
@@ -1,6 +1,6 @@
 #include "metric.hpp"
 
-#include "details/collection_function.hpp"
+#include "metrics/collection_data.hpp"
 #include "types/report_types.hpp"
 #include "types/sensor_types.hpp"
 #include "utils/labeled_tuple.hpp"
@@ -10,130 +10,6 @@
 
 #include <algorithm>
 
-class Metric::CollectionData
-{
-  public:
-    using ReadingItem = details::ReadingItem;
-
-    virtual ~CollectionData() = default;
-
-    virtual std::optional<double> update(Milliseconds timestamp) = 0;
-    virtual double update(Milliseconds timestamp, double value) = 0;
-};
-
-class Metric::DataPoint : public Metric::CollectionData
-{
-  public:
-    std::optional<double> update(Milliseconds) override
-    {
-        return lastReading;
-    }
-
-    double update(Milliseconds, double reading) override
-    {
-        lastReading = reading;
-        return reading;
-    }
-
-  private:
-    std::optional<double> lastReading;
-};
-
-class Metric::DataInterval : public Metric::CollectionData
-{
-  public:
-    DataInterval(std::shared_ptr<details::CollectionFunction> function,
-                 CollectionDuration duration) :
-        function(std::move(function)),
-        duration(duration)
-    {
-        if (duration.t.count() == 0)
-        {
-            throw sdbusplus::exception::SdBusError(
-                static_cast<int>(std::errc::invalid_argument),
-                "Invalid CollectionDuration");
-        }
-    }
-
-    std::optional<double> update(Milliseconds timestamp) override
-    {
-        if (readings.empty())
-        {
-            return std::nullopt;
-        }
-
-        cleanup(timestamp);
-
-        return function->calculate(readings, timestamp);
-    }
-
-    double update(Milliseconds timestamp, double reading) override
-    {
-        readings.emplace_back(timestamp, reading);
-
-        cleanup(timestamp);
-
-        return function->calculate(readings, timestamp);
-    }
-
-  private:
-    void cleanup(Milliseconds timestamp)
-    {
-        auto it = readings.begin();
-        for (auto kt = std::next(readings.rbegin()); kt != readings.rend();
-             ++kt)
-        {
-            const auto& [nextItemTimestamp, nextItemReading] = *std::prev(kt);
-            if (timestamp >= nextItemTimestamp &&
-                timestamp - nextItemTimestamp > duration.t)
-            {
-                it = kt.base();
-                break;
-            }
-        }
-        readings.erase(readings.begin(), it);
-
-        if (timestamp > duration.t)
-        {
-            readings.front().first =
-                std::max(readings.front().first, timestamp - duration.t);
-        }
-    }
-
-    std::shared_ptr<details::CollectionFunction> function;
-    std::vector<ReadingItem> readings;
-    CollectionDuration duration;
-};
-
-class Metric::DataStartup : public Metric::CollectionData
-{
-  public:
-    explicit DataStartup(
-        std::shared_ptr<details::CollectionFunction> function) :
-        function(std::move(function))
-    {}
-
-    std::optional<double> update(Milliseconds timestamp) override
-    {
-        if (readings.empty())
-        {
-            return std::nullopt;
-        }
-
-        return function->calculateForStartupInterval(readings, timestamp);
-    }
-
-    double update(Milliseconds timestamp, double reading) override
-    {
-        readings.emplace_back(timestamp, reading);
-        return function->calculateForStartupInterval(readings, timestamp);
-    }
-
-  private:
-    std::shared_ptr<details::CollectionFunction> function;
-    std::vector<ReadingItem> readings;
-};
-
 Metric::Metric(Sensors sensorsIn, OperationType operationTypeIn,
                std::string idIn, CollectionTimeScope timeScopeIn,
                CollectionDuration collectionDurationIn,
@@ -141,9 +17,9 @@
     id(std::move(idIn)),
     sensors(std::move(sensorsIn)), operationType(operationTypeIn),
     collectionTimeScope(timeScopeIn), collectionDuration(collectionDurationIn),
-    collectionAlgorithms(makeCollectionData(sensors.size(), operationType,
-                                            collectionTimeScope,
-                                            collectionDuration)),
+    collectionAlgorithms(
+        metrics::makeCollectionData(sensors.size(), operationType,
+                                    collectionTimeScope, collectionDuration)),
     clock(std::move(clockIn))
 {
     readings = utils::transform(sensors, [this](const auto& sensor) {
@@ -151,7 +27,20 @@
     });
 }
 
-Metric::~Metric() = default;
+void Metric::registerForUpdates(interfaces::MetricListener& listener)
+{
+    listeners.emplace_back(listener);
+}
+
+void Metric::unregisterFromUpdates(interfaces::MetricListener& listener)
+{
+    listeners.erase(
+        std::remove_if(listeners.begin(), listeners.end(),
+                       [&listener](const interfaces::MetricListener& item) {
+                           return &item == &listener;
+                       }),
+        listeners.end());
+}
 
 void Metric::initialize()
 {
@@ -190,18 +79,22 @@
     return resultReadings;
 }
 
-void Metric::sensorUpdated(interfaces::Sensor& notifier, Milliseconds timestamp)
-{
-    findAssociatedData(notifier).update(timestamp);
-}
-
 void Metric::sensorUpdated(interfaces::Sensor& notifier, Milliseconds timestamp,
                            double value)
 {
-    findAssociatedData(notifier).update(timestamp, value);
+    auto& data = findAssociatedData(notifier);
+    double newValue = data.update(timestamp, value);
+
+    if (data.updateLastValue(newValue))
+    {
+        for (interfaces::MetricListener& listener : listeners)
+        {
+            listener.metricUpdated();
+        }
+    }
 }
 
-Metric::CollectionData&
+metrics::CollectionData&
     Metric::findAssociatedData(const interfaces::Sensor& notifier)
 {
     auto it = std::find_if(
@@ -222,46 +115,42 @@
                                    collectionTimeScope, collectionDuration);
 }
 
-std::vector<std::unique_ptr<Metric::CollectionData>>
-    Metric::makeCollectionData(size_t size, OperationType op,
-                               CollectionTimeScope timeScope,
-                               CollectionDuration duration)
-{
-    using namespace std::string_literals;
-
-    std::vector<std::unique_ptr<Metric::CollectionData>> result;
-
-    result.reserve(size);
-
-    switch (timeScope)
-    {
-        case CollectionTimeScope::interval:
-            std::generate_n(
-                std::back_inserter(result), size,
-                [cf = details::makeCollectionFunction(op), duration] {
-                    return std::make_unique<DataInterval>(cf, duration);
-                });
-            break;
-        case CollectionTimeScope::point:
-            std::generate_n(std::back_inserter(result), size,
-                            [] { return std::make_unique<DataPoint>(); });
-            break;
-        case CollectionTimeScope::startup:
-            std::generate_n(std::back_inserter(result), size,
-                            [cf = details::makeCollectionFunction(op)] {
-                                return std::make_unique<DataStartup>(cf);
-                            });
-            break;
-        default:
-            throw std::runtime_error("timeScope: "s +
-                                     utils::enumToString(timeScope) +
-                                     " is not supported"s);
-    }
-
-    return result;
-}
-
 uint64_t Metric::sensorCount() const
 {
     return sensors.size();
 }
+
+void Metric::updateReadings(Milliseconds timestamp)
+{
+    for (auto& data : collectionAlgorithms)
+    {
+        if (std::optional<double> newValue = data->update(timestamp))
+        {
+            if (data->updateLastValue(*newValue))
+            {
+                for (interfaces::MetricListener& listener : listeners)
+                {
+                    listener.metricUpdated();
+                }
+                return;
+            }
+        }
+    }
+}
+
+bool Metric::isTimerRequired() const
+{
+    if (collectionTimeScope == CollectionTimeScope::point)
+    {
+        return false;
+    }
+
+    if (collectionTimeScope == CollectionTimeScope::startup &&
+        (operationType == OperationType::min ||
+         operationType == OperationType::max))
+    {
+        return false;
+    }
+
+    return true;
+}