#include "../interface_ops.hpp"

#include <sdbusplus/test/sdbus_mock.hpp>

#include <gtest/gtest.h>

using namespace phosphor::inventory::manager;
using namespace testing;
using namespace std::string_literals;

struct MockInterface;
struct DummyInterfaceWithProperties;

MockInterface* g_currentMock = nullptr;

using FakeVariantType = int;
using InterfaceVariant = std::map<std::string, FakeVariantType>;

struct MockInterface
{
    MockInterface()
    {
        g_currentMock = this;
    }
    ~MockInterface()
    {
        g_currentMock = nullptr;
    }
    MockInterface(const MockInterface&) = delete;
    MockInterface& operator=(const MockInterface&) = delete;
    // Not supporting move semantics simply because they aren't needed.
    MockInterface(MockInterface&&) = delete;
    MockInterface& operator=(MockInterface&&) = delete;

    // We'll be getting calls proxyed through other objects.
    MOCK_METHOD2(constructWithProperties,
                 void(const char*, const InterfaceVariant& i));
    MOCK_METHOD1(constructWithoutProperties, void(const char*));
    MOCK_METHOD2(setPropertyByName, void(std::string, FakeVariantType));

    MOCK_METHOD2(serializeTwoArgs,
                 void(const std::string&, const std::string&));
    MOCK_METHOD3(serializeThreeArgs,
                 void(const std::string&, const std::string&,
                      const DummyInterfaceWithProperties&));

    MOCK_METHOD0(deserializeNoop, void());
    MOCK_METHOD3(deserializeThreeArgs,
                 void(const std::string&, const std::string&,
                      DummyInterfaceWithProperties&));
};

struct DummyInterfaceWithoutProperties
{
    DummyInterfaceWithoutProperties(sdbusplus::bus::bus&, const char* name)
    {
        g_currentMock->constructWithoutProperties(name);
    }
};

struct DummyInterfaceWithProperties
{
    using PropertiesVariant = FakeVariantType;

    DummyInterfaceWithProperties(sdbusplus::bus::bus&, const char* name,
                                 const InterfaceVariant& i)
    {
        g_currentMock->constructWithProperties(name, i);
    }

    void setPropertyByName(std::string name, PropertiesVariant val)
    {
        g_currentMock->setPropertyByName(name, val);
    }
};

struct SerialForwarder
{
    static void serialize(const std::string& path, const std::string& iface)
    {
        g_currentMock->serializeTwoArgs(path, iface);
    }

    static void serialize(const std::string& path, const std::string& iface,
                          const DummyInterfaceWithProperties& obj)
    {
        g_currentMock->serializeThreeArgs(path, iface, obj);
    }

    static void deserialize(const std::string& path, const std::string& iface)
    {
        g_currentMock->deserializeNoop();
    }

    static void deserialize(const std::string& path, const std::string& iface,
                            DummyInterfaceWithProperties& obj)
    {
        g_currentMock->deserializeThreeArgs(path, iface, obj);
    }
};

TEST(InterfaceOpsTest, TestHasPropertiesNoProperties)
{
    EXPECT_FALSE(HasProperties<DummyInterfaceWithoutProperties>::value);
}

TEST(InterfaceOpsTest, TestHasPropertiesHasProperties)
{
    EXPECT_TRUE(HasProperties<DummyInterfaceWithProperties>::value);
}

TEST(InterfaceOpsTest, TestMakePropertylessInterfaceWithoutArguments)
{
    MockInterface mock;
    Interface i;
    sdbusplus::SdBusMock interface;

    EXPECT_CALL(mock, constructWithoutProperties("foo")).Times(1);
    EXPECT_CALL(mock, constructWithProperties(_, _)).Times(0);

    auto b = sdbusplus::get_mocked_new(&interface);
    auto r = MakeInterface<DummyInterfaceWithoutProperties>::op(b, "foo", i);

    EXPECT_NO_THROW(
        std::any_cast<std::shared_ptr<DummyInterfaceWithoutProperties>>(r));
}

TEST(InterfaceOpsTest, TestMakePropertylessInterfaceWithOneArgument)
{
    MockInterface mock;
    Interface i{{"foo"s, static_cast<int64_t>(1ll)}};
    sdbusplus::SdBusMock interface;

    EXPECT_CALL(mock, constructWithoutProperties("foo")).Times(1);
    EXPECT_CALL(mock, constructWithProperties(_, _)).Times(0);

    auto b = sdbusplus::get_mocked_new(&interface);
    auto r = MakeInterface<DummyInterfaceWithoutProperties>::op(b, "foo", i);

    EXPECT_NO_THROW(
        std::any_cast<std::shared_ptr<DummyInterfaceWithoutProperties>>(r));
}

TEST(InterfaceOpsTest, TestMakeInterfaceWithWithoutArguments)
{
    MockInterface mock;
    Interface i;
    sdbusplus::SdBusMock interface;

    EXPECT_CALL(mock, constructWithoutProperties(_)).Times(0);
    EXPECT_CALL(mock, constructWithProperties("bar", _)).Times(1);

    auto b = sdbusplus::get_mocked_new(&interface);
    auto r = MakeInterface<DummyInterfaceWithProperties>::op(b, "bar", i);

    EXPECT_NO_THROW(
        std::any_cast<std::shared_ptr<DummyInterfaceWithProperties>>(r));
}

TEST(InterfaceOpsTest, TestMakeInterfaceWithOneArgument)
{
    MockInterface mock;
    Interface i{{"foo"s, static_cast<int64_t>(1ll)}};
    sdbusplus::SdBusMock interface;

    EXPECT_CALL(mock, constructWithoutProperties(_)).Times(0);
    EXPECT_CALL(mock, constructWithProperties("foo", _)).Times(1);

    auto b = sdbusplus::get_mocked_new(&interface);
    auto r = MakeInterface<DummyInterfaceWithProperties>::op(b, "foo", i);

    EXPECT_NO_THROW(
        std::any_cast<std::shared_ptr<DummyInterfaceWithProperties>>(r));
}

TEST(InterfaceOpsTest, TestAssignPropertylessInterfaceWithoutArguments)
{
    MockInterface mock;
    Interface i;
    sdbusplus::SdBusMock interface;

    EXPECT_CALL(mock, setPropertyByName(_, _)).Times(0);

    auto b = sdbusplus::get_mocked_new(&interface);
    auto r = MakeInterface<DummyInterfaceWithoutProperties>::op(b, "foo", i);

    AssignInterface<DummyInterfaceWithoutProperties>::op(i, r);
}

TEST(InterfaceOpsTest, TestAssignPropertylessInterfaceWithOneArgument)
{
    MockInterface mock;
    Interface i{{"foo"s, static_cast<int64_t>(1ll)}};
    sdbusplus::SdBusMock interface;

    EXPECT_CALL(mock, setPropertyByName(_, _)).Times(0);

    auto b = sdbusplus::get_mocked_new(&interface);
    auto r = MakeInterface<DummyInterfaceWithoutProperties>::op(b, "foo", i);

    AssignInterface<DummyInterfaceWithoutProperties>::op(i, r);
}

TEST(InterfaceOpsTest, TestAssignInterfaceWithoutArguments)
{
    MockInterface mock;
    Interface i;
    sdbusplus::SdBusMock interface;

    EXPECT_CALL(mock, setPropertyByName(_, _)).Times(0);

    auto b = sdbusplus::get_mocked_new(&interface);
    auto r = MakeInterface<DummyInterfaceWithProperties>::op(b, "foo", i);

    AssignInterface<DummyInterfaceWithProperties>::op(i, r);
}

TEST(InterfaceOpsTest, TestAssignInterfaceWithOneArgument)
{
    MockInterface mock;
    Interface i{{"foo"s, static_cast<int64_t>(1ll)}};
    sdbusplus::SdBusMock interface;

    EXPECT_CALL(mock, setPropertyByName("foo"s, 1ll)).Times(1);

    auto b = sdbusplus::get_mocked_new(&interface);
    auto r = MakeInterface<DummyInterfaceWithProperties>::op(b, "bar", i);

    AssignInterface<DummyInterfaceWithProperties>::op(i, r);
}

TEST(InterfaceOpsTest, TestSerializePropertylessInterfaceWithoutArguments)
{
    MockInterface mock;
    Interface i;
    sdbusplus::SdBusMock interface;

    auto b = sdbusplus::get_mocked_new(&interface);
    auto r = MakeInterface<DummyInterfaceWithoutProperties>::op(b, "foo", i);

    EXPECT_CALL(mock, serializeTwoArgs("/foo"s, "bar"s)).Times(1);

    SerializeInterface<DummyInterfaceWithoutProperties, SerialForwarder>::op(
        "/foo"s, "bar"s, r);
}

TEST(InterfaceOpsTest, TestSerializePropertylessInterfaceWithOneArgument)
{
    MockInterface mock;
    Interface i{{"foo"s, static_cast<int64_t>(1ll)}};
    sdbusplus::SdBusMock interface;

    auto b = sdbusplus::get_mocked_new(&interface);
    auto r = MakeInterface<DummyInterfaceWithoutProperties>::op(b, "foo", i);

    EXPECT_CALL(mock, serializeTwoArgs("/foo"s, "bar"s)).Times(1);

    SerializeInterface<DummyInterfaceWithoutProperties, SerialForwarder>::op(
        "/foo"s, "bar"s, r);
}

TEST(InterfaceOpsTest, TestSerializeInterfaceWithNoArguments)
{
    MockInterface mock;
    Interface i;
    sdbusplus::SdBusMock interface;

    auto b = sdbusplus::get_mocked_new(&interface);
    auto r = MakeInterface<DummyInterfaceWithProperties>::op(b, "foo", i);

    EXPECT_CALL(mock, serializeThreeArgs("/foo"s, "bar"s, _)).Times(1);

    SerializeInterface<DummyInterfaceWithProperties, SerialForwarder>::op(
        "/foo"s, "bar"s, r);
}

TEST(InterfaceOpsTest, TestSerializeInterfaceWithOneArgument)
{
    MockInterface mock;
    Interface i{{"foo"s, static_cast<int64_t>(1ll)}};
    sdbusplus::SdBusMock interface;

    auto b = sdbusplus::get_mocked_new(&interface);
    auto r = MakeInterface<DummyInterfaceWithProperties>::op(b, "foo", i);

    EXPECT_CALL(mock, serializeThreeArgs("/foo"s, "bar"s, _)).Times(1);

    SerializeInterface<DummyInterfaceWithProperties, SerialForwarder>::op(
        "/foo"s, "bar"s, r);
}

TEST(InterfaceOpsTest, TestDeserializePropertylessInterfaceWithoutArguments)
{
    MockInterface mock;
    Interface i;
    sdbusplus::SdBusMock interface;

    auto b = sdbusplus::get_mocked_new(&interface);
    auto r = MakeInterface<DummyInterfaceWithoutProperties>::op(b, "foo", i);

    EXPECT_CALL(mock, deserializeNoop()).Times(1);

    DeserializeInterface<DummyInterfaceWithoutProperties, SerialForwarder>::op(
        "/foo"s, "bar"s, r);
}

TEST(InterfaceOpsTest, TestDeserializePropertylessInterfaceWithOneArgument)
{
    MockInterface mock;
    Interface i{{"foo"s, static_cast<int64_t>(1ll)}};
    sdbusplus::SdBusMock interface;

    auto b = sdbusplus::get_mocked_new(&interface);
    auto r = MakeInterface<DummyInterfaceWithoutProperties>::op(b, "foo", i);

    EXPECT_CALL(mock, deserializeNoop()).Times(1);

    DeserializeInterface<DummyInterfaceWithoutProperties, SerialForwarder>::op(
        "/foo"s, "bar"s, r);
}

TEST(InterfaceOpsTest, TestDeserializeInterfaceWithNoArguments)
{
    MockInterface mock;
    Interface i;
    sdbusplus::SdBusMock interface;

    auto b = sdbusplus::get_mocked_new(&interface);
    auto r = MakeInterface<DummyInterfaceWithProperties>::op(b, "foo", i);

    EXPECT_CALL(mock, deserializeThreeArgs("/foo"s, "bar"s, _)).Times(1);

    DeserializeInterface<DummyInterfaceWithProperties, SerialForwarder>::op(
        "/foo"s, "bar"s, r);
}

TEST(InterfaceOpsTest, TestDeserializeInterfaceWithOneArgument)
{
    MockInterface mock;
    Interface i{{"foo"s, static_cast<int64_t>(1ll)}};
    sdbusplus::SdBusMock interface;

    auto b = sdbusplus::get_mocked_new(&interface);
    auto r = MakeInterface<DummyInterfaceWithProperties>::op(b, "foo", i);

    EXPECT_CALL(mock, deserializeThreeArgs("/foo"s, "bar"s, _)).Times(1);

    DeserializeInterface<DummyInterfaceWithProperties, SerialForwarder>::op(
        "/foo"s, "bar"s, r);
}