#include "dbusSensor.hpp"

#include "virtualSensor.hpp"

#include <sdbusplus/bus.hpp>
#include <sdbusplus/bus/match.hpp>

#include <cmath>
static constexpr auto sensorIntf =
    sdbusplus::common::xyz::openbmc_project::sensor::Value::interface;

/** When the Entity Manager removes the sensor, the interfaceRemoveSignal sent
 * uses the path /xyz/openbmc_project/sensors
 * */
static constexpr auto interfacesSensorPath = "/xyz/openbmc_project/sensors";

namespace phosphor::virtual_sensor
{

DbusSensor::DbusSensor(sdbusplus::bus_t& bus, const std::string& path,
                       VirtualSensor& virtualSensor) :
    bus(bus), path(path), virtualSensor(virtualSensor),
    signalPropChange(
        bus, sdbusplus::bus::match::rules::propertiesChanged(path, sensorIntf),
        [this](sdbusplus::message_t& message) {
            handleDbusSignalPropChange(message);
        }),
    signalRemove(
        bus,
        sdbusplus::bus::match::rules::interfacesRemoved(interfacesSensorPath),
        [this](sdbusplus::message_t& message) {
            handleDbusSignalRemove(message);
        })
{
    initSensorValue();
}

double DbusSensor::getSensorValue()
{
    return value;
}

void DbusSensor::initSensorValue()
{
    try
    {
        // If servName is not empty, reduce one DbusCall
        if (servName.empty())
        {
            value = std::numeric_limits<double>::quiet_NaN();
            servName = getService(bus, path, sensorIntf);
        }

        if (!servName.empty())
        {
            signalNameOwnerChanged.reset();
            signalNameOwnerChanged = std::make_unique<sdbusplus::bus::match_t>(
                bus,
                sdbusplus::bus::match::rules::nameOwnerChanged() +
                    sdbusplus::bus::match::rules::arg0namespace(servName),
                [this](sdbusplus::message_t& message) {
                    handleDbusSignalNameOwnerChanged(message);
                });

            value = getDbusProperty<double>(bus, servName, path, sensorIntf,
                                            "Value");
        }
    }
    catch (const std::exception& e)
    {
        value = std::numeric_limits<double>::quiet_NaN();
    }

    return;
}

void DbusSensor::handleDbusSignalNameOwnerChanged(sdbusplus::message_t& msg)
{
    try
    {
        auto [name, oldOwner,
              newOwner] = msg.unpack<std::string, std::string, std::string>();

        if (!oldOwner.empty() && !name.empty())
        {
            if (name == servName)
            {
                // Connection removed

                value = std::numeric_limits<double>::quiet_NaN();
                virtualSensor.updateVirtualSensor();
            }
        }
    }
    catch (const std::exception& e)
    {
        lg2::error("Error in dbusSensor NameOwnerChanged: {PATH}  {ERROR}",
                   "PATH", path, "ERROR", e);
    }
}

void DbusSensor::handleDbusSignalPropChange(sdbusplus::message_t& msg)
{
    try
    {
        using SensorValuePropertiesVariant = sdbusplus::server::xyz::
            openbmc_project::sensor::Value::PropertiesVariant;
        auto [msgIfce, msgData] =
            msg.unpack<std::string,
                       std::map<std::string, SensorValuePropertiesVariant>>();

        std::string path = msg.get_path();

        if (auto itr = msgData.find("Value"); itr != msgData.end())
        {
            value = std::get<double>(itr->second);
            if (!std::isfinite(value))
            {
                value = std::numeric_limits<double>::quiet_NaN();
            }

            virtualSensor.updateVirtualSensor();
        }
    }
    catch (const std::exception& e)
    {
        lg2::error("Error in dbusSensor PropertyChange: {PATH}  {ERROR}",
                   "PATH", path, "ERROR", e);
    }
}

void DbusSensor::handleDbusSignalRemove(sdbusplus::message_t& msg)
{
    try
    {
        auto objPath = msg.unpack<sdbusplus::message::object_path>();

        if (this->path == objPath)
        {
            value = std::numeric_limits<double>::quiet_NaN();
            virtualSensor.updateVirtualSensor();
        }
    }
    catch (const std::exception& e)
    {
        lg2::error("Error in dbusSensor interfaceRemove: {PATH}  {ERROR}",
                   "PATH", path, "ERROR", e);
    }
}

} // namespace phosphor::virtual_sensor