#pragma once
#include "data_types.hpp"
#include "sdbusplus/bus/match.hpp"

#include <string>

#include <gmock/gmock.h>
#include <gtest/gtest.h>

namespace phosphor
{
namespace dbus
{
namespace monitoring
{

/** @class CallMethodAndRead
 *  @brief GMock template member forwarding helper.
 *
 *  The code under test calls callMethodAndRead, which is a templated,
 *  free function.  Enable this under GMock by forwarding calls to it
 *  to functions that can be mocked.
 *
 *  @tparam DBusInterfaceType - The mock object type.
 *  @tparam Ret - The return type of the method being called.
 *  @tparam Args - The argument types of the method being called.
 *
 *  Specialize to implement new forwards.
 */
template <typename DBusInterfaceType, typename Ret, typename... Args>
struct CallMethodAndRead
{
    static Ret op(DBusInterfaceType& dbus, const std::string& busName,
                  const std::string& path, const std::string& interface,
                  const std::string& method, Args&&... args)
    {
        static_assert(true, "Missing CallMethodAndRead definition.");
        return Ret();
    }
};

/** @brief CallMethodAndRead specialization for
 *     xyz.openbmc_project.ObjectMapper.GetObject. */
template <typename DBusInterfaceType>
struct CallMethodAndRead<DBusInterfaceType, GetObject, const MapperPath&,
                         const std::vector<std::string>&>
{
    static GetObject op(DBusInterfaceType& dbus, const std::string& busName,
                        const std::string& path, const std::string& interface,
                        const std::string& method, const MapperPath& objectPath,
                        const std::vector<std::string>& interfaces)
    {
        return dbus.mapperGetObject(busName, path, interface, method,
                                    objectPath, interfaces);
    }
};

/** @brief CallMethodAndRead specialization for
 *     org.freedesktop.DBus.Properties.GetAll(uint64_t). */
template <typename DBusInterfaceType>
struct CallMethodAndRead<DBusInterfaceType, PropertiesChanged<uint64_t>,
                         const std::string&>
{
    static PropertiesChanged<uint64_t>
        op(DBusInterfaceType& dbus, const std::string& busName,
           const std::string& path, const std::string& interface,
           const std::string& method, const std::string& propertiesInterface)
    {
        return dbus.getPropertiesU64(busName, path, interface, method,
                                     propertiesInterface);
    }
};

/** @brief CallMethodAndRead specialization for
 *     org.freedesktop.DBus.Properties.GetAll(uint32_t). */
template <typename DBusInterfaceType>
struct CallMethodAndRead<DBusInterfaceType, PropertiesChanged<uint32_t>,
                         const std::string&>
{
    static PropertiesChanged<uint32_t>
        op(DBusInterfaceType& dbus, const std::string& busName,
           const std::string& path, const std::string& interface,
           const std::string& method, const std::string& propertiesInterface)
    {
        return dbus.getPropertiesU32(busName, path, interface, method,
                                     propertiesInterface);
    }
};

/** @brief CallMethodAndRead specialization for
 *     org.freedesktop.DBus.Properties.GetAll(uint16_t). */
template <typename DBusInterfaceType>
struct CallMethodAndRead<DBusInterfaceType, PropertiesChanged<uint16_t>,
                         const std::string&>
{
    static PropertiesChanged<uint16_t>
        op(DBusInterfaceType& dbus, const std::string& busName,
           const std::string& path, const std::string& interface,
           const std::string& method, const std::string& propertiesInterface)
    {
        return dbus.getPropertiesU16(busName, path, interface, method,
                                     propertiesInterface);
    }
};

/** @brief CallMethodAndRead specialization for
 *     org.freedesktop.DBus.Properties.GetAll(uint8_t). */
template <typename DBusInterfaceType>
struct CallMethodAndRead<DBusInterfaceType, PropertiesChanged<uint8_t>,
                         const std::string&>
{
    static PropertiesChanged<uint8_t>
        op(DBusInterfaceType& dbus, const std::string& busName,
           const std::string& path, const std::string& interface,
           const std::string& method, const std::string& propertiesInterface)
    {
        return dbus.getPropertiesU8(busName, path, interface, method,
                                    propertiesInterface);
    }
};

/** @brief CallMethodAndRead specialization for
 *     org.freedesktop.DBus.Properties.GetAll(int64_t). */
template <typename DBusInterfaceType>
struct CallMethodAndRead<DBusInterfaceType, PropertiesChanged<int64_t>,
                         const std::string&>
{
    static PropertiesChanged<int64_t>
        op(DBusInterfaceType& dbus, const std::string& busName,
           const std::string& path, const std::string& interface,
           const std::string& method, const std::string& propertiesInterface)
    {
        return dbus.getPropertiesU64(busName, path, interface, method,
                                     propertiesInterface);
    }
};

/** @brief CallMethodAndRead specialization for
 *     org.freedesktop.DBus.Properties.GetAll(int32_t). */
template <typename DBusInterfaceType>
struct CallMethodAndRead<DBusInterfaceType, PropertiesChanged<int32_t>,
                         const std::string&>
{
    static PropertiesChanged<int32_t>
        op(DBusInterfaceType& dbus, const std::string& busName,
           const std::string& path, const std::string& interface,
           const std::string& method, const std::string& propertiesInterface)
    {
        return dbus.getPropertiesU32(busName, path, interface, method,
                                     propertiesInterface);
    }
};

/** @brief CallMethodAndRead specialization for
 *     org.freedesktop.DBus.Properties.GetAll(int16_t). */
template <typename DBusInterfaceType>
struct CallMethodAndRead<DBusInterfaceType, PropertiesChanged<int16_t>,
                         const std::string&>
{
    static PropertiesChanged<int16_t>
        op(DBusInterfaceType& dbus, const std::string& busName,
           const std::string& path, const std::string& interface,
           const std::string& method, const std::string& propertiesInterface)
    {
        return dbus.getPropertiesU16(busName, path, interface, method,
                                     propertiesInterface);
    }
};

/** @brief CallMethodAndRead specialization for
 *     org.freedesktop.DBus.Properties.GetAll(int8_t). */
template <typename DBusInterfaceType>
struct CallMethodAndRead<DBusInterfaceType, PropertiesChanged<int8_t>,
                         const std::string&>
{
    static PropertiesChanged<int8_t>
        op(DBusInterfaceType& dbus, const std::string& busName,
           const std::string& path, const std::string& interface,
           const std::string& method, const std::string& propertiesInterface)
    {
        return dbus.getPropertiesU8(busName, path, interface, method,
                                    propertiesInterface);
    }
};

/** @brief CallMethodAndRead specialization for
 *     org.freedesktop.DBus.Properties.GetAll(std::string). */
template <typename DBusInterfaceType>
struct CallMethodAndRead<DBusInterfaceType, PropertiesChanged<std::string>,
                         const std::string&>
{
    static PropertiesChanged<std::string>
        op(DBusInterfaceType& dbus, const std::string& busName,
           const std::string& path, const std::string& interface,
           const std::string& method, const std::string& propertiesInterface)
    {
        return dbus.getPropertiesString(busName, path, interface, method,
                                        propertiesInterface);
    }
};

/** @class MockDBusInterface
 *  @brief DBus access delegate implementation for the property watch test
 *  suite.
 */
struct MockDBusInterface
{
    MOCK_METHOD6(mapperGetObject,
                 GetObject(const std::string&, const std::string&,
                           const std::string&, const std::string&,
                           const MapperPath&, const std::vector<std::string>&));

    MOCK_METHOD5(getPropertiesU64,
                 PropertiesChanged<uint64_t>(
                     const std::string&, const std::string&, const std::string&,
                     const std::string&, const std::string&));

    MOCK_METHOD5(getPropertiesU32,
                 PropertiesChanged<uint32_t>(
                     const std::string&, const std::string&, const std::string&,
                     const std::string&, const std::string&));

    MOCK_METHOD5(getPropertiesU16,
                 PropertiesChanged<uint16_t>(
                     const std::string&, const std::string&, const std::string&,
                     const std::string&, const std::string&));

    MOCK_METHOD5(getPropertiesU8,
                 PropertiesChanged<uint8_t>(
                     const std::string&, const std::string&, const std::string&,
                     const std::string&, const std::string&));

    MOCK_METHOD5(getPropertiesS64,
                 PropertiesChanged<int64_t>(
                     const std::string&, const std::string&, const std::string&,
                     const std::string&, const std::string&));

    MOCK_METHOD5(getPropertiesS32,
                 PropertiesChanged<int32_t>(
                     const std::string&, const std::string&, const std::string&,
                     const std::string&, const std::string&));

    MOCK_METHOD5(getPropertiesS16,
                 PropertiesChanged<int16_t>(
                     const std::string&, const std::string&, const std::string&,
                     const std::string&, const std::string&));

    MOCK_METHOD5(getPropertiesS8,
                 PropertiesChanged<int8_t>(
                     const std::string&, const std::string&, const std::string&,
                     const std::string&, const std::string&));

    MOCK_METHOD5(getPropertiesString,
                 PropertiesChanged<std::string>(
                     const std::string&, const std::string&, const std::string&,
                     const std::string&, const std::string&));

    MOCK_METHOD2(fwdAddMatch,
                 void(const std::string&,
                      const sdbusplus::bus::match::match::callback_t&));

    static MockDBusInterface* ptr;
    static MockDBusInterface& instance()
    {
        return *ptr;
    }
    static void instance(MockDBusInterface& p)
    {
        ptr = &p;
    }

    /** @brief GMock member template/free function forward. */
    template <typename Ret, typename... Args>
    static auto callMethodAndRead(const std::string& busName,
                                  const std::string& path,
                                  const std::string& interface,
                                  const std::string& method, Args&&... args)
    {
        return CallMethodAndRead<MockDBusInterface, Ret, Args...>::op(
            instance(), busName, path, interface, method,
            std::forward<Args>(args)...);
    }

    /** @brief GMock free function forward. */
    static auto
        addMatch(const std::string& match,
                 const sdbusplus::bus::match::match::callback_t& callback)
    {
        instance().fwdAddMatch(match, callback);
    }
};

/** @class Expect
 *  @brief Enable use of EXPECT_CALL from a C++ template.
 */
template <typename T>
struct Expect
{
};

template <>
struct Expect<uint64_t>
{
    template <typename MockObjType>
    static auto& getProperties(MockObjType&& mockObj, const std::string& path,
                               const std::string& interface)
    {
        return EXPECT_CALL(std::forward<MockObjType>(mockObj),
                           getPropertiesU64(::testing::_, path,
                                            "org.freedesktop.DBus.Properties",
                                            "GetAll", interface));
    }
};

template <>
struct Expect<uint32_t>
{
    template <typename MockObjType>
    static auto& getProperties(MockObjType&& mockObj, const std::string& path,
                               const std::string& interface)
    {
        return EXPECT_CALL(std::forward<MockObjType>(mockObj),
                           getPropertiesU32(::testing::_, path,
                                            "org.freedesktop.DBus.Properties",
                                            "GetAll", interface));
    }
};

template <>
struct Expect<uint16_t>
{
    template <typename MockObjType>
    static auto& getProperties(MockObjType&& mockObj, const std::string& path,
                               const std::string& interface)
    {
        return EXPECT_CALL(std::forward<MockObjType>(mockObj),
                           getPropertiesU16(::testing::_, path,
                                            "org.freedesktop.DBus.Properties",
                                            "GetAll", interface));
    }
};

template <>
struct Expect<uint8_t>
{
    template <typename MockObjType>
    static auto& getProperties(MockObjType&& mockObj, const std::string& path,
                               const std::string& interface)
    {
        return EXPECT_CALL(std::forward<MockObjType>(mockObj),
                           getPropertiesU8(::testing::_, path,
                                           "org.freedesktop.DBus.Properties",
                                           "GetAll", interface));
    }
};

template <>
struct Expect<int64_t>
{
    template <typename MockObjType>
    static auto& getProperties(MockObjType&& mockObj, const std::string& path,
                               const std::string& interface)
    {
        return EXPECT_CALL(std::forward<MockObjType>(mockObj),
                           getPropertiesS64(::testing::_, path,
                                            "org.freedesktop.DBus.Properties",
                                            "GetAll", interface));
    }
};

template <>
struct Expect<int32_t>
{
    template <typename MockObjType>
    static auto& getProperties(MockObjType&& mockObj, const std::string& path,
                               const std::string& interface)
    {
        return EXPECT_CALL(std::forward<MockObjType>(mockObj),
                           getPropertiesS32(::testing::_, path,
                                            "org.freedesktop.DBus.Properties",
                                            "GetAll", interface));
    }
};

template <>
struct Expect<int16_t>
{
    template <typename MockObjType>
    static auto& getProperties(MockObjType&& mockObj, const std::string& path,
                               const std::string& interface)
    {
        return EXPECT_CALL(std::forward<MockObjType>(mockObj),
                           getPropertiesS16(::testing::_, path,
                                            "org.freedesktop.DBus.Properties",
                                            "GetAll", interface));
    }
};

template <>
struct Expect<int8_t>
{
    template <typename MockObjType>
    static auto& getProperties(MockObjType&& mockObj, const std::string& path,
                               const std::string& interface)
    {
        return EXPECT_CALL(std::forward<MockObjType>(mockObj),
                           getPropertiesS8(::testing::_, path,
                                           "org.freedesktop.DBus.Properties",
                                           "GetAll", interface));
    }
};

template <>
struct Expect<std::string>
{
    template <typename MockObjType>
    static auto& getProperties(MockObjType&& mockObj, const std::string& path,
                               const std::string& interface)
    {
        return EXPECT_CALL(
            std::forward<MockObjType>(mockObj),
            getPropertiesString(::testing::_, path,
                                "org.freedesktop.DBus.Properties", "GetAll",
                                interface));
    }
};

} // namespace monitoring
} // namespace dbus
} // namespace phosphor