control: Add 'less_than' modifier

This modifier allows one to return a value from a table based on if the
value passed to it is less than the table key value.

For example:

"modifier": {
  "operator": "less_than",
  "value": [
    {
       "arg_value": 30,
       "parameter_value": 300
    },
    {
       "arg_value": 40,
       "parameter_value": 400
    },
    {
       "arg_value": 50,
       "parameter_value": 500
    }
  ]
}

With the above config, it would do the following:
* value less than 30:  return 300
* else if value less than 40: return 400
* else if value less than 50: return 500
* else: return a default value based on data type of parameter_value

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I8b47341f41723c0cdf1ee2786526550fc24551f9
diff --git a/control/json/config_base.hpp b/control/json/config_base.hpp
index 4f7a425..5bfb9f2 100644
--- a/control/json/config_base.hpp
+++ b/control/json/config_base.hpp
@@ -102,7 +102,6 @@
         return _profiles;
     }
 
-  protected:
     /**
      * @brief Determines the data type of a JSON configured parameter that is
      * used as a variant within the fan control application and returns the
@@ -143,6 +142,7 @@
             "Unsupported data type for JSON object's value");
     }
 
+  protected:
     /* Name of the configuration object */
     std::string _name;
 
diff --git a/control/json/utils/modifier.cpp b/control/json/utils/modifier.cpp
index 6d00bc1..6a9d278 100644
--- a/control/json/utils/modifier.cpp
+++ b/control/json/utils/modifier.cpp
@@ -16,6 +16,7 @@
 
 #include "modifier.hpp"
 
+#include "json/config_base.hpp"
 #include "json/manager.hpp"
 
 #include <fmt/format.h>
@@ -46,6 +47,37 @@
 };
 
 /**
+ * @brief Return a default value to use when the argument passed
+ *        to LessThanOperator is out of range.
+ */
+PropertyVariantType getDefaultValue(const PropertyVariantType& val)
+{
+    if (std::holds_alternative<bool>(val))
+    {
+        return false;
+    }
+    else if (std::holds_alternative<std::string>(val))
+    {
+        return std::string{};
+    }
+    else if (std::holds_alternative<double>(val))
+    {
+        return std::numeric_limits<double>::quiet_NaN();
+    }
+    else if (std::holds_alternative<int32_t>(val))
+    {
+        return std::numeric_limits<int32_t>::quiet_NaN();
+    }
+    else if (std::holds_alternative<int64_t>(val))
+    {
+        return std::numeric_limits<int64_t>::quiet_NaN();
+    }
+
+    throw std::runtime_error(
+        "Invalid variant type when determining default value");
+}
+
+/**
  * @brief Implements the minus operator to subtract two values.
  *
  * With strings values, A - B removes all occurrences of B in A.
@@ -53,6 +85,10 @@
  */
 struct MinusOperator : public Modifier::BaseOperator
 {
+    MinusOperator(const json& valueObj) :
+        arg(ConfigBase::getJsonValue(valueObj))
+    {}
+
     PropertyVariantType operator()(double val) override
     {
         return val - std::visit(ToTypeVisitor<double>(), arg);
@@ -88,64 +124,161 @@
             "Bool not allowed as a 'minus' modifier value"};
     }
 
-    MinusOperator(PropertyVariantType& arg) : arg(arg)
-    {}
-
     PropertyVariantType arg;
 };
 
+/**
+ * @brief Implements an operator to return a value specified in
+ *        the JSON that is chosen based on if the value passed
+ *        into the operator is less than the lowest arg_value it
+ *        is true for.
+ *
+ * "modifier": {
+ *  "operator": "less_than",
+ *  "value": [
+ *    {
+ *      "arg_value": 30, // if value is less than 30
+ *      "parameter_value": 300  // then return 300
+ *    },
+ *    {
+ *      "arg_value": 40, // else if value is less than 40
+ *      "parameter_value": 400 // then return 400
+ *    },
+ *   ]
+ *  }
+ *
+ * If the value passed in is higher than the highest arg_value,
+ * it returns a default value this is chosen based on the
+ * data type of parameter_value.
+ */
+struct LessThanOperator : public Modifier::BaseOperator
+{
+    LessThanOperator(const json& valueArray)
+    {
+        if (!valueArray.is_array())
+        {
+            log<level::ERR>(
+                fmt::format("Invalid JSON data for less_than config: {}",
+                            valueArray.dump())
+                    .c_str());
+            throw std::invalid_argument("Invalid modifier JSON");
+        }
+
+        for (const auto& valueEntry : valueArray)
+        {
+            if (!valueEntry.contains("arg_value") ||
+                !valueEntry.contains("parameter_value"))
+            {
+                log<level::ERR>(
+                    fmt::format("Missing arg_value or parameter_value keys "
+                                "in less_than config: {}",
+                                valueArray.dump())
+                        .c_str());
+                throw std::invalid_argument("Invalid modifier JSON");
+            }
+
+            auto argVal = ConfigBase::getJsonValue(valueEntry.at("arg_value"));
+
+            if (std::holds_alternative<bool>(argVal))
+            {
+                log<level::ERR>(
+                    fmt::format(
+                        "Invalid data type in arg_value key in modifier JSON "
+                        "config: {}",
+                        valueArray.dump())
+                        .c_str());
+                throw std::invalid_argument("Invalid modifier JSON");
+            }
+
+            auto paramVal =
+                ConfigBase::getJsonValue(valueEntry.at("parameter_value"));
+
+            rangeValues.emplace_back(argVal, paramVal);
+        }
+
+        if (rangeValues.empty())
+        {
+            log<level::ERR>(fmt::format("No valid range values found in "
+                                        "modifier json: {}",
+                                        valueArray.dump())
+                                .c_str());
+            throw std::invalid_argument("Invalid modifier JSON");
+        }
+    }
+
+    PropertyVariantType operator()(double val) override
+    {
+        for (const auto& rangeValue : rangeValues)
+        {
+            if (val < std::visit(ToTypeVisitor<double>(), rangeValue.first))
+            {
+                return rangeValue.second;
+            }
+        }
+        // Return a default value based on last entry type
+        return getDefaultValue(rangeValues.back().second);
+    }
+
+    PropertyVariantType operator()(int32_t val) override
+    {
+        for (const auto& rangeValue : rangeValues)
+        {
+            if (val < std::visit(ToTypeVisitor<int32_t>(), rangeValue.first))
+            {
+                return rangeValue.second;
+            }
+        }
+        return getDefaultValue(rangeValues.back().second);
+    }
+
+    PropertyVariantType operator()(int64_t val) override
+    {
+        for (const auto& rangeValue : rangeValues)
+        {
+            if (val < std::visit(ToTypeVisitor<int64_t>(), rangeValue.first))
+            {
+                return rangeValue.second;
+            }
+        }
+        return getDefaultValue(rangeValues.back().second);
+    }
+
+    PropertyVariantType operator()(const std::string& val) override
+    {
+        for (const auto& rangeValue : rangeValues)
+        {
+            if (val <
+                std::visit(ToTypeVisitor<std::string>(), rangeValue.first))
+            {
+                return rangeValue.second;
+            }
+        }
+        return getDefaultValue(rangeValues.back().second);
+    }
+
+    PropertyVariantType operator()(bool val) override
+    {
+        throw std::runtime_error{
+            "Bool not allowed as a 'less_than' modifier value"};
+    }
+
+    std::vector<std::pair<PropertyVariantType, PropertyVariantType>>
+        rangeValues;
+};
+
 Modifier::Modifier(const json& jsonObj)
 {
-    setValue(jsonObj);
     setOperator(jsonObj);
 }
 
-void Modifier::setValue(const json& jsonObj)
-{
-    if (!jsonObj.contains("value"))
-    {
-        log<level::ERR>(
-            fmt::format("Modifier entry in JSON missing 'value': {}",
-                        jsonObj.dump())
-                .c_str());
-        throw std::invalid_argument("Invalid modifier JSON");
-    }
-
-    const auto& object = jsonObj.at("value");
-    if (auto boolPtr = object.get_ptr<const bool*>())
-    {
-        _value = *boolPtr;
-    }
-    else if (auto intPtr = object.get_ptr<const int64_t*>())
-    {
-        _value = *intPtr;
-    }
-    else if (auto doublePtr = object.get_ptr<const double*>())
-    {
-        _value = *doublePtr;
-    }
-    else if (auto stringPtr = object.get_ptr<const std::string*>())
-    {
-        _value = *stringPtr;
-    }
-    else
-    {
-        log<level::ERR>(
-            fmt::format(
-                "Invalid JSON type for value property in modifer json: {}",
-                jsonObj.dump())
-                .c_str());
-        throw std::invalid_argument("Invalid modifier JSON");
-    }
-}
-
 void Modifier::setOperator(const json& jsonObj)
 {
-    if (!jsonObj.contains("operator"))
+    if (!jsonObj.contains("operator") || !jsonObj.contains("value"))
     {
         log<level::ERR>(
-            fmt::format("Modifier entry in JSON missing 'operator': {}",
-                        jsonObj.dump())
+            fmt::format(
+                "Modifier entry in JSON missing 'operator' or 'value': {}",
+                jsonObj.dump())
                 .c_str());
         throw std::invalid_argument("Invalid modifier JSON");
     }
@@ -154,7 +287,11 @@
 
     if (op == "minus")
     {
-        _operator = std::make_unique<MinusOperator>(_value);
+        _operator = std::make_unique<MinusOperator>(jsonObj["value"]);
+    }
+    else if (op == "less_than")
+    {
+        _operator = std::make_unique<LessThanOperator>(jsonObj["value"]);
     }
     else
     {
diff --git a/control/json/utils/modifier.hpp b/control/json/utils/modifier.hpp
index 3f59246..6153279 100644
--- a/control/json/utils/modifier.hpp
+++ b/control/json/utils/modifier.hpp
@@ -42,6 +42,7 @@
  *
  * The valid operators are:
  *  - "minus"
+ *  - "less_than"
  *
  * To add a new operator, derive a new class from BaseOperator and
  * then create it accordingly in setOperator.
@@ -103,13 +104,6 @@
      */
     void setOperator(const json& jsonObj);
 
-    /**
-     * @brief Subtracts _value from value
-     *
-     * @param[in] value - The value to subtract from
-     */
-    PropertyVariantType minus(const PropertyVariantType& value);
-
     /** @brief The value used by the operator */
     PropertyVariantType _value;