#pragma once
#include <memory>
#include "tach_sensor.hpp"

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());
         }

        /**
         * Stops the timers on all sensors in the group.
         *
         * Called when the group just changed to not trusted,
         * so that its sensors' timers can't fire a callback
         * that may cause them to be considered faulted.
         */
        void stopTimers()
        {
            std::for_each(
                    _sensors.begin(),
                    _sensors.end(),
                    [](const auto& s)
                    {
                        s.sensor->stopTimer();
                    });
        }

        /**
         * Starts the timers on all functional sensors in the group if
         * their target and input values do not match.
         *
         * Called when the group just changed to trusted.
         */
        void startTimers()
        {
            std::for_each(
                    _sensors.begin(),
                    _sensors.end(),
                    [](const auto& s)
                    {
                        //If a sensor isn't functional, then its timer
                        //already expired so don't bother starting it again
                        if (s.sensor->functional() &&
                            static_cast<uint64_t>(
                                s.sensor->getInput()) !=
                                    s.sensor->getTarget())
                        {
                            s.sensor->startTimer(
                                phosphor::fan::monitor::TimerMode::nonfunc);
                        }
                    });
        }

        /**
         * 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;
};

}
}
}