#pragma once
#include "tach_sensor.hpp"

#include <memory>

namespace phosphor
{
namespace fan
{
namespace trust
{

constexpr auto sensorName = 0;
constexpr auto inTrust = 1;
using GroupDefinition = std::tuple<std::string, bool>;

struct GroupSensor
{
    std::shared_ptr<monitor::TachSensor> sensor;
    bool inTrust;
};

/**
 * @class Group
 *
 * An abstract sensor trust group base class.
 *
 * Supports the ability to know if a fan speed sensor value can
 * be trusted or not, where if it isn't trusted then it shouldn't
 * be used to determine if the fan is faulted or not.
 *
 * It's a group in that there can be multiple sensors in the group
 * and the trust of all sensors depends on something about those sensors.
 * For example, if all sensors in the group report a speed of zero,
 * then no sensor in the group is trusted.  All sensors in the group
 * have the same trust value.
 *
 * Trust is calculated when checkTrust() is called after a group
 * sensor's tach value changes.
 *
 * A derived class must override checkGroupTrust().
 */
class Group
{
  public:
    Group() = delete;
    virtual ~Group() = default;
    Group(const Group&) = delete;
    Group& operator=(const Group&) = delete;
    Group(Group&&) = default;
    Group& operator=(Group&&) = default;

    /**
     * Constructor
     *
     * @param[in] names - the names and inclusion of sensors in the group
     */
    explicit Group(const std::vector<GroupDefinition>& names) : _names(names) {}

    /**
     * Used to register a TachSensor object with the group.
     * It's only added to the group if the sensor's name is
     * in the group's list of names.
     *
     * @param[in] sensor - the TachSensor to register
     */
    void registerSensor(std::shared_ptr<monitor::TachSensor>& sensor)
    {
        auto found = std::find_if(
            _names.begin(), _names.end(), [&sensor](const auto& name) {
                return monitor::FAN_SENSOR_PATH + std::get<sensorName>(name) ==
                       sensor->name();
            });

        if (found != _names.end())
        {
            _sensors.push_back({sensor, std::get<inTrust>(*found)});
        }
    }

    /**
     * Says if a sensor belongs to the group.
     *
     * After all sensors have registered, this can be
     * used to say if a TachSensor is in the group.
     *
     * @param[in] sensor - the TachSensor object
     */
    bool inGroup(const monitor::TachSensor& sensor)
    {
        return (std::find_if(_sensors.begin(), _sensors.end(),
                             [&sensor](const auto& s) {
                                 return sensor.name() == s.sensor->name();
                             }) != _sensors.end());
    }

    /**
     * Cancels monitoring on all sensors in the group.
     *
     * Called when the group just changed to not trusted,
     * so that its sensors' monitoring method does not
     * cause them to be considered faulted.
     */
    void cancelMonitoring()
    {
        std::for_each(_sensors.begin(), _sensors.end(),
                      [](const auto& s) { s.sensor->resetMethod(); });
    }

    /**
     * Starts monitoring on all sensors in the group by processing their current
     * state
     *
     * Called when the group just changed to trusted.
     */
    void startMonitoring()
    {
        std::for_each(_sensors.begin(), _sensors.end(),
                      [](const auto& s) { s.sensor->processState(); });
    }

    /**
     * Determines the trust for this group based on this
     * sensor's latest status.
     *
     * Calls the derived class's checkGroupTrust function
     * and updates the class with the results.
     *
     * If this is called with a sensor not in the group,
     * it will be considered trusted.
     *
     * @param[in] sensor - TachSensor object
     *
     * @return tuple<bool, bool> -
     *   field 0 - the trust value
     *   field 1 - if that trust value changed since last call
     *             to checkTrust
     */
    auto checkTrust(const monitor::TachSensor& sensor)
    {
        if (inGroup(sensor))
        {
            auto trust = checkGroupTrust();

            setTrust(trust);

            return std::tuple<bool, bool>(_trusted, _stateChange);
        }
        return std::tuple<bool, bool>(true, false);
    }

    /**
     * Says if all sensors in the group are currently trusted,
     * as determined by the last call to checkTrust().
     *
     * @return bool - if the group's sensors are trusted or not
     */
    inline auto getTrust() const
    {
        return _trusted;
    }

    /**
     * Says if the trust value changed in the last call to
     * checkTrust()
     *
     * @return bool - if the trust changed or not
     */
    inline auto trustChanged() const
    {
        return _stateChange;
    }

  protected:
    /**
     * The sensor objects and their trust inclusion in the group.
     *
     * Added by registerSensor().
     */
    std::vector<GroupSensor> _sensors;

  private:
    /**
     * Checks if the group's sensors are trusted.
     *
     * The derived class must override this function
     * to provide custom functionality.
     *
     * @return bool - if group is trusted or not
     */
    virtual bool checkGroupTrust() = 0;

    /**
     * Sets the trust value on the object.
     *
     * @param[in] trust - the new trust value
     */
    inline void setTrust(bool trust)
    {
        _stateChange = (trust != _trusted);
        _trusted = trust;
    }

    /**
     * The current trust state of the group
     */
    bool _trusted = true;

    /**
     * If the trust value changed in the last call to checkTrust
     */
    bool _stateChange = false;

    /**
     * The names of the sensors and whether it is included in
     * determining trust for this group
     */
    const std::vector<GroupDefinition> _names;
}; // namespace trust

} // namespace trust
} // namespace fan
} // namespace phosphor