control: Move functions to be templated

While testing the sensor type change from int64 to double, it was found
there were some fan control actions that were not templated to support
different data types that could be configured to be used in those
actions. Moving these to be templated allows any supported property type
to be used within these actions.

Tested:
    Using different property types result in actions functioning
correctly
    No impact to still using an int64 as the data type to these actions

Change-Id: I95274e4a1a4cf2ccacb089416eb01cd42e114caf
Signed-off-by: Matthew Barth <msbarth@us.ibm.com>
diff --git a/control/actions.hpp b/control/actions.hpp
index 87e9af2..d33c366 100644
--- a/control/actions.hpp
+++ b/control/actions.hpp
@@ -4,6 +4,7 @@
 #include <numeric>
 #include "types.hpp"
 #include "zone.hpp"
+#include "utility.hpp"
 
 namespace phosphor
 {
@@ -128,8 +129,58 @@
  *     An Action function to set the zone's floor speed when the average of
  *     property values within the group is below the lowest sensor value given
  */
+template <typename T>
 Action set_floor_from_average_sensor_value(
-        std::map<int64_t, uint64_t>&& val_to_speed);
+        std::map<T, uint64_t>&& val_to_speed)
+{
+    return [val_to_speed = std::move(val_to_speed)](control::Zone& zone,
+                                                    const Group& group)
+    {
+        auto speed = zone.getDefFloor();
+        if (group.size() != 0)
+        {
+            auto count = 0;
+            auto sumValue = std::accumulate(
+                    group.begin(),
+                    group.end(),
+                    0,
+                    [&zone, &count](T sum, auto const& entry)
+                    {
+                        try
+                        {
+                            return sum +
+                                zone.template getPropertyValue<T>(
+                                    std::get<pathPos>(entry),
+                                    std::get<intfPos>(entry),
+                                    std::get<propPos>(entry));
+                        }
+                        catch (const std::out_of_range& oore)
+                        {
+                            count++;
+                            return sum;
+                        }
+                    });
+            if ((group.size() - count) > 0)
+            {
+                auto groupSize = static_cast<int64_t>(group.size());
+                auto avgValue = sumValue / (groupSize - count);
+                auto it = std::find_if(
+                    val_to_speed.begin(),
+                    val_to_speed.end(),
+                    [&avgValue](auto const& entry)
+                    {
+                        return avgValue < entry.first;
+                    }
+                );
+                if (it != std::end(val_to_speed))
+                {
+                    speed = (*it).second;
+                }
+            }
+        }
+        zone.setFloor(speed);
+    };
+}
 
 /**
  * @brief An action to set the ceiling speed on a zone
@@ -145,8 +196,118 @@
  *     property values within the group is above(increasing) or
  *     below(decreasing) the key transition point
  */
+template <typename T>
 Action set_ceiling_from_average_sensor_value(
-        std::map<int64_t, uint64_t>&& val_to_speed);
+        std::map<T, uint64_t>&& val_to_speed)
+{
+    return [val_to_speed = std::move(val_to_speed)](Zone& zone,
+                                                    const Group& group)
+    {
+        auto speed = zone.getCeiling();
+        if (group.size() != 0)
+        {
+            auto count = 0;
+            auto sumValue = std::accumulate(
+                    group.begin(),
+                    group.end(),
+                    0,
+                    [&zone, &count](T sum, auto const& entry)
+                    {
+                        try
+                        {
+                            return sum +
+                                zone.template getPropertyValue<T>(
+                                    std::get<pathPos>(entry),
+                                    std::get<intfPos>(entry),
+                                    std::get<propPos>(entry));
+                        }
+                        catch (const std::out_of_range& oore)
+                        {
+                            count++;
+                            return sum;
+                        }
+                    });
+            if ((group.size() - count) > 0)
+            {
+                auto groupSize = static_cast<int64_t>(group.size());
+                auto avgValue = sumValue / (groupSize - count);
+                auto prevValue = zone.swapCeilingKeyValue(avgValue);
+                if (avgValue != prevValue)
+                {// Only check if previous and new values differ
+                    if (avgValue < prevValue)
+                    {// Value is decreasing from previous
+                        for (auto it = val_to_speed.rbegin();
+                             it != val_to_speed.rend();
+                             ++it)
+                        {
+                            if (it == val_to_speed.rbegin() &&
+                                avgValue >= it->first)
+                            {
+                                // Value is at/above last map key, set
+                                // ceiling speed to the last map key's value
+                                speed = it->second;
+                                break;
+                            }
+                            else if (std::next(it, 1) == val_to_speed.rend() &&
+                                     avgValue <= it->first)
+                            {
+                                // Value is at/below first map key, set
+                                // ceiling speed to the first map key's value
+                                speed = it->second;
+                                break;
+                            }
+                            if (avgValue < it->first &&
+                                it->first <= prevValue)
+                            {
+                                // Value decreased & transitioned across
+                                // a map key, update ceiling speed to this
+                                // map key's value when new value is below
+                                // map's key and the key is at/below the
+                                // previous value
+                                speed = it->second;
+                            }
+                        }
+                    }
+                    else
+                    {// Value is increasing from previous
+                        for (auto it = val_to_speed.begin();
+                             it != val_to_speed.end();
+                             ++it)
+                        {
+                            if (it == val_to_speed.begin() &&
+                                avgValue <= it->first)
+                            {
+                                // Value is at/below first map key, set
+                                // ceiling speed to the first map key's value
+                                speed = it->second;
+                                break;
+                            }
+                            else if (std::next(it, 1) == val_to_speed.end() &&
+                                     avgValue >= it->first)
+                            {
+                                // Value is at/above last map key, set
+                                // ceiling speed to the last map key's value
+                                speed = it->second;
+                                break;
+                            }
+                            if (avgValue > it->first &&
+                                it->first >= prevValue)
+                            {
+                                // Value increased & transitioned across
+                                // a map key, update ceiling speed to this
+                                // map key's value when new value is above
+                                // map's key and the key is at/above the
+                                // previous value
+                                speed = it->second;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        zone.setCeiling(speed);
+    };
+}
 
 /**
  * @brief An action to set the speed increase delta and request speed change
@@ -195,7 +356,7 @@
                         // difference times the given speed delta
                         netDelta = std::max(
                             netDelta,
-                            (delta/factor) * speedDelta);
+                            static_cast<uint64_t>((delta/factor) * speedDelta));
                     }
                 }
                 catch (const std::out_of_range& oore)
@@ -253,7 +414,8 @@
                         // difference times the given speed delta
                         netDelta = std::min(
                             netDelta,
-                            ((state - value)/factor) * speedDelta);
+                            static_cast<uint64_t>(
+                                ((state - value)/factor) * speedDelta));
                     }
                 }
                 else
@@ -364,10 +526,75 @@
  *     An Action function to set the zone's floor speed from a resulting group
  * of valid sensor values based on their highest value or median.
  */
+template <typename T>
 Action set_floor_from_median_sensor_value(
-        int64_t lowerBound,
-        int64_t upperBound,
-        std::map<int64_t, uint64_t>&& valueToSpeed);
+        T&& lowerBound,
+        T&& upperBound,
+        std::map<T, uint64_t>&& valueToSpeed)
+{
+    return [lowerBound = std::forward<T>(lowerBound),
+            upperBound = std::forward<T>(upperBound),
+            valueToSpeed = std::move(valueToSpeed)](control::Zone& zone,
+                                                    const Group& group)
+    {
+        auto speed = zone.getDefFloor();
+        if (group.size() != 0)
+        {
+            std::vector<T> validValues;
+            for (auto const& member : group)
+            {
+                try
+                {
+                    auto value = zone.template getPropertyValue<T>(
+                            std::get<pathPos>(member),
+                            std::get<intfPos>(member),
+                            std::get<propPos>(member));
+                    if (value == std::clamp(value, lowerBound, upperBound))
+                    {
+                        // Sensor value is valid
+                        validValues.emplace_back(value);
+                    }
+                }
+                catch (const std::out_of_range& oore)
+                {
+                    continue;
+                }
+            }
+
+            if (!validValues.empty())
+            {
+                auto median = validValues.front();
+                // Get the determined median value
+                if (validValues.size() == 2)
+                {
+                    // For 2 values, use the highest instead of the average
+                    // for a thermally safe floor
+                    median = *std::max_element(validValues.begin(),
+                                               validValues.end());
+                }
+                else if (validValues.size() > 2)
+                {
+                    median = utility::getMedian(validValues);
+                }
+
+                // Use determined median sensor value to find floor speed
+                auto it = std::find_if(
+                    valueToSpeed.begin(),
+                    valueToSpeed.end(),
+                    [&median](auto const& entry)
+                    {
+                        return median < entry.first;
+                    }
+                );
+                if (it != std::end(valueToSpeed))
+                {
+                    speed = (*it).second;
+                }
+            }
+        }
+        zone.setFloor(speed);
+    };
+}
 
 /**
  * @brief An action to update the default floor speed