control: Construct base zone group from JSON

Pulls together the profiles, zones, and fans JSON configurations to
construct a single zone group to be used as fan control's configuration.
This base zone group includes all that is necessary to get a basic fan
control configuration that will hold fans at the zone's given full
speed. A single zone group is created since the active state of any
configured profiles is checked while creating the zone group.

*Note that "profiles" in JSON configs replace what was called
"Conditions" in YAML based configs.

Tested:
    Loaded profile based configurations
    Loaded non-profile based configurations
    Loaded combination of profile and non-profile based configurations

Change-Id: Id010c899a7633824b80c5cef21c848eadfb66243
Signed-off-by: Matthew Barth <msbarth@us.ibm.com>
diff --git a/control/json_parser.cpp b/control/json_parser.cpp
index 66c59e3..c074736 100644
--- a/control/json_parser.cpp
+++ b/control/json_parser.cpp
@@ -25,9 +25,39 @@
 
 #include <sdbusplus/bus.hpp>
 
+#include <algorithm>
+#include <cstdlib>
+#include <tuple>
+#include <vector>
+
 namespace phosphor::fan::control
 {
 
+bool checkEntry(const std::vector<std::string>& activeProfiles,
+                const std::vector<std::string>& entryProfiles)
+{
+    // Include entry if its list of profiles to be included in is empty
+    if (entryProfiles.empty())
+    {
+        // Entry always to be included
+        return true;
+    }
+    else
+    {
+        for (const auto& profile : activeProfiles)
+        {
+            auto iter =
+                std::find(entryProfiles.begin(), entryProfiles.end(), profile);
+            if (iter != entryProfiles.end())
+            {
+                // Entry configured to be included in active profile
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
 const std::vector<ZoneGroup> getZoneGroups(sdbusplus::bus::bus& bus)
 {
     std::vector<ZoneGroup> zoneGrps;
@@ -46,7 +76,68 @@
         auto groups = getConfig<json::Group>(bus);
     }
 
-    // TODO Create zone groups after loading all JSON config files
+    // Ensure all configurations use the same set of active profiles
+    // (In case a profile's active state changes during configuration)
+    std::vector<std::string> activeProfiles;
+    for (const auto& profile : profiles)
+    {
+        if (profile.second->isActive())
+        {
+            activeProfiles.emplace_back(profile.first.first);
+        }
+    }
+
+    // Conditions list empty for JSON based configurations
+    // TODO Remove conditions after YAML based configuration removed
+    std::vector<Condition> conditions;
+    std::vector<ZoneDefinition> zoneDefs;
+    for (const auto& zone : zones)
+    {
+        // Check zone profiles against active profiles
+        if (checkEntry(activeProfiles, zone.second->getProfiles()))
+        {
+            auto zoneName = zone.second->getName();
+            // Create FanDefinition list for zone
+            std::vector<FanDefinition> fanDefs;
+            for (const auto& fan : fans)
+            {
+                // Check fan profiles against active profiles and
+                // if the fan is included in the zone
+                if (checkEntry(activeProfiles, fan.second->getProfiles()) &&
+                    fan.second->getZone() == zoneName)
+                {
+                    fanDefs.emplace_back(std::make_tuple(
+                        fan.second->getName(), fan.second->getSensors(),
+                        fan.second->getInterface()));
+                }
+            }
+
+            // Create SetSpeedEvents list for zone
+            std::vector<SetSpeedEvent> speedEvents;
+            // TODO Populate SetSpeedEvents list with configured events
+
+            // YAML zone name was an integer, JSON is a string
+            // Design direction is for it to be a string and this can be
+            // removed after YAML based configurations are removed.
+            char* zoneName_end;
+            auto zoneNum = std::strtol(zoneName.c_str(), &zoneName_end, 10);
+            if (*zoneName_end)
+            {
+                throw std::runtime_error(
+                    "Zone names must be a string representation of a number");
+            }
+
+            zoneDefs.emplace_back(std::make_tuple(
+                zoneNum, zone.second->getFullSpeed(),
+                zone.second->getDefaultFloor(), zone.second->getIncDelay(),
+                zone.second->getDecInterval(), zone.second->getZoneHandlers(),
+                std::move(fanDefs), std::move(speedEvents)));
+        }
+    }
+    // TODO Should only result in a single entry but YAML based configurations
+    // produce a list of zone groups. Change to single zone group after YAML
+    // based configuartions are removed.
+    zoneGrps.emplace_back(std::make_tuple(conditions, zoneDefs));
 
     return zoneGrps;
 }
diff --git a/control/json_parser.hpp b/control/json_parser.hpp
index df9c680..7c08875 100644
--- a/control/json_parser.hpp
+++ b/control/json_parser.hpp
@@ -72,6 +72,28 @@
 }
 
 /**
+ * @brief Helper function to determine when a configuration entry is included
+ * based on the list of active profiles and its list of profiles
+ *
+ * A configuration entry may include a list of profiles that when any one of
+ * the profiles are active, the entry should be included. When the list of
+ * profiles for a configuration entry is empty, that results in always
+ * including the entry. An empty list of active profiles results in including
+ * only those entries configured without a list of profiles.
+ *
+ * i.e.) No profiles configured results in always being included, whereas
+ * providing a list of profiles on an entry results only in that entry being
+ * included when a profile in the list is active.
+ *
+ * @param[in] activeProfiles - List of active system profiles
+ * @param[in] entryProfiles - List of the configuration entry's profiles
+ *
+ * @return Whether the configuration entry should be included or not
+ */
+bool checkEntry(const std::vector<std::string>& activeProfiles,
+                const std::vector<std::string>& entryProfiles);
+
+/**
  * @brief Get the configuration definitions for zone groups
  *
  * @param[in] bus - The dbus bus object
diff --git a/control/manager.cpp b/control/manager.cpp
index ff8afef..bdea542 100644
--- a/control/manager.cpp
+++ b/control/manager.cpp
@@ -92,13 +92,21 @@
     // Create the appropriate Zone objects based on the
     // actual system configuration.
 #ifdef CONTROL_USE_JSON
-    auto zoneLayouts = getZoneGroups(bus);
+    for (auto& group : getZoneGroups(bus))
+    {
+        // Create a Zone object for each zone in the group
+        for (auto& z : std::get<zoneListPos>(group))
+        {
+            fs::path path{CONTROL_OBJPATH};
+            path /= std::to_string(std::get<zoneNumPos>(z));
+            _zones.emplace(
+                std::get<zoneNumPos>(z),
+                std::make_unique<Zone>(mode, _bus, path.string(), event, z));
+        }
+    }
 #else
-    auto zoneLayouts = _zoneLayouts;
-#endif
-
     // Find the 1 ZoneGroup that meets all of its conditions
-    for (auto& group : zoneLayouts)
+    for (auto& group : _zoneLayouts)
     {
         auto& conditions = std::get<conditionListPos>(group);
 
@@ -122,6 +130,7 @@
             break;
         }
     }
+#endif
 
     if (mode == Mode::control)
     {