// Copyright 2016 Proyectos y Sistemas de Mantenimiento SL (eProsima).
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <fastrtps/transport/test_UDPv4Transport.h>
#include <gtest/gtest.h>
#include <thread>
#include <fastrtps/rtps/common/CDRMessage_t.h>
#include <fastrtps/rtps/messages/RTPSMessageCreator.h>
#include <fastrtps/qos/ParameterTypes.h>
#include <fastdds/dds/log/Log.hpp>
#include <fastrtps/utils/IPLocator.h>

#include <memory>
#include <string>

#if defined(_WIN32)
#define GET_PID _getpid
#else
#define GET_PID getpid
#endif

using eprosima::fastrtps::rtps::IPLocator;

static uint16_t g_default_port = 0;

uint16_t get_port()
{
    uint16_t port = static_cast<uint16_t>(GET_PID());

    if(4000 > port)
    {
        port += 4000;
    }

    return port;
}

using namespace eprosima::fastrtps;
using namespace eprosima::fastrtps::rtps;

class test_UDPv4Tests: public ::testing::Test
{
    public:
    test_UDPv4Tests()
    {
        HELPER_SetDescriptorDefaults();
    }

    ~test_UDPv4Tests()
    {
        eprosima::fastdds::dds::Log::KillThread();
    }

   void HELPER_SetDescriptorDefaults();
   void HELPER_WarmUpOutput(test_UDPv4Transport& transport);
   void HELPER_FillDataMessage(CDRMessage_t& message, SequenceNumber_t sequenceNumber);
   void HELPER_FillAckNackMessage(CDRMessage_t& message);
   void HELPER_FillHeartbeatMessage(CDRMessage_t& message);

   test_UDPv4TransportDescriptor descriptor;
   std::unique_ptr<std::thread> senderThread;
   std::unique_ptr<std::thread> receiverThread;
};

/*
TEST_F(test_UDPv4Tests, DATA_messages_dropped)
{
   // Given
   descriptor.dropDataMessagesPercentage = 100;
   test_UDPv4Transport transportUnderTest(descriptor);
   transportUnderTest.init();
   CDRMessage_t testDataMessage;
   HELPER_FillDataMessage(testDataMessage, SequenceNumber_t());
   HELPER_WarmUpOutput(transportUnderTest);
   Locator_t locator;
   locator.port = g_default_port;
   locator.kind = LOCATOR_KIND_UDPv4;

   // Then
   ASSERT_TRUE(transportUnderTest.send(testDataMessage.buffer, testDataMessage.length, locator, locator));
   ASSERT_EQ(1u, test_UDPv4Transport::test_UDPv4Transport_DropLog.size());

   ASSERT_TRUE(transportUnderTest.CloseOutputChannel(locator));
}

TEST_F(test_UDPv4Tests, ACKNACK_messages_dropped)
{
   // Given
   descriptor.dropAckNackMessagesPercentage = 100;
   test_UDPv4Transport transportUnderTest(descriptor);
   transportUnderTest.init();
   CDRMessage_t testDataMessage;
   HELPER_FillAckNackMessage(testDataMessage);
   HELPER_WarmUpOutput(transportUnderTest);
   Locator_t locator;
   locator.port = g_default_port;
   locator.kind = LOCATOR_KIND_UDPv4;

   // Then
   ASSERT_TRUE(transportUnderTest.send(testDataMessage.buffer, testDataMessage.length, locator, locator));
   ASSERT_EQ(1u, test_UDPv4Transport::test_UDPv4Transport_DropLog.size());
   ASSERT_TRUE(transportUnderTest.CloseOutputChannel(locator));
}

TEST_F(test_UDPv4Tests, HEARTBEAT_messages_dropped)
{
   // Given
   descriptor.dropHeartbeatMessagesPercentage = 100;
   test_UDPv4Transport transportUnderTest(descriptor);
   transportUnderTest.init();
   CDRMessage_t testDataMessage;
   HELPER_FillHeartbeatMessage(testDataMessage);
   HELPER_WarmUpOutput(transportUnderTest);
   Locator_t locator;
   locator.port = g_default_port;
   locator.kind = LOCATOR_KIND_UDPv4;

   // Then
   ASSERT_TRUE(transportUnderTest.send(testDataMessage.buffer, testDataMessage.length, locator, locator));
   ASSERT_EQ(1u, test_UDPv4Transport::test_UDPv4Transport_DropLog.size());
   ASSERT_TRUE(transportUnderTest.CloseOutputChannel(locator));
}

TEST_F(test_UDPv4Tests, Dropping_by_random_chance)
{
   // Given
   descriptor.percentageOfMessagesToDrop = 100; // To avoid a non-deterministic test
   test_UDPv4Transport transportUnderTest(descriptor);
   transportUnderTest.init();
   CDRMessage_t testDataMessage;
   HELPER_FillAckNackMessage(testDataMessage);
   HELPER_WarmUpOutput(transportUnderTest);
   Locator_t locator;
   locator.port = g_default_port;
   locator.kind = LOCATOR_KIND_UDPv4;

   // Then
   ASSERT_TRUE(transportUnderTest.send(testDataMessage.buffer, testDataMessage.length, locator, locator));
   ASSERT_TRUE(transportUnderTest.send(testDataMessage.buffer, testDataMessage.length, locator, locator));
   ASSERT_TRUE(transportUnderTest.send(testDataMessage.buffer, testDataMessage.length, locator, locator));
   ASSERT_EQ(3u, test_UDPv4Transport::test_UDPv4Transport_DropLog.size());
   ASSERT_TRUE(transportUnderTest.CloseOutputChannel(locator));
}

TEST_F(test_UDPv4Tests, dropping_by_sequence_number)
{
   // Given
   std::vector<SequenceNumber_t> sequenceNumbersToDrop(1);
   sequenceNumbersToDrop.back().low = 1;

   descriptor.sequenceNumberDataMessagesToDrop = sequenceNumbersToDrop;
   test_UDPv4Transport transportUnderTest(descriptor);
   transportUnderTest.init();
   CDRMessage_t testDataMessage;
   HELPER_FillDataMessage(testDataMessage, sequenceNumbersToDrop.back());
   HELPER_WarmUpOutput(transportUnderTest);
   Locator_t locator;
   locator.port = g_default_port;
   locator.kind = LOCATOR_KIND_UDPv4;

   // Then
   ASSERT_TRUE(transportUnderTest.send(testDataMessage.buffer, testDataMessage.length, locator, locator));
   ASSERT_EQ(1u, test_UDPv4Transport::test_UDPv4Transport_DropLog.size());
   ASSERT_TRUE(transportUnderTest.CloseOutputChannel(locator));
}

TEST_F(test_UDPv4Tests, No_drops_when_unrequested)
{
   // Given
   descriptor.dropHeartbeatMessagesPercentage = 100;
   descriptor.dropDataMessagesPercentage = 100;
   descriptor.granularMode = false;

   test_UDPv4Transport transportUnderTest(descriptor); // Default, no drops
   transportUnderTest.init();
   CDRMessage_t testDataMessage;
   HELPER_FillAckNackMessage(testDataMessage);
   HELPER_WarmUpOutput(transportUnderTest);
   Locator_t locator;
   locator.port = g_default_port;
   locator.kind = LOCATOR_KIND_UDPv4;
   IPLocator::setIPv4(locator, 239, 255, 1, 4);

   // Then
   ASSERT_TRUE(transportUnderTest.send(testDataMessage.buffer, testDataMessage.length, locator, locator));
   ASSERT_EQ(0u, test_UDPv4Transport::test_UDPv4Transport_DropLog.size());
   ASSERT_TRUE(transportUnderTest.CloseOutputChannel(locator));
}

void test_UDPv4Tests::HELPER_SetDescriptorDefaults()
{
   descriptor.sendBufferSize = 80;
   descriptor.receiveBufferSize = 80;
   descriptor.dropDataMessagesPercentage = 0;
   descriptor.dropDataFragMessagesPercentage = 0;
   descriptor.dropAckNackMessagesPercentage = 0;
   descriptor.dropHeartbeatMessagesPercentage = 0;
   descriptor.percentageOfMessagesToDrop = 0;
   descriptor.dropLogLength = 10;
   descriptor.granularMode = false;
}

void test_UDPv4Tests::HELPER_WarmUpOutput(test_UDPv4Transport& transport)
{
   Locator_t outputChannelLocator;
   outputChannelLocator.port = g_default_port;
   outputChannelLocator.kind = LOCATOR_KIND_UDPv4;
   ASSERT_TRUE(transport.OpenOutputChannel(outputChannelLocator));
}
*/

void test_UDPv4Tests::HELPER_FillDataMessage(CDRMessage_t& message, SequenceNumber_t sequenceNumber)
{
   GuidPrefix_t prefix;
   TopicKind_t topic = WITH_KEY;
   EntityId_t entityID;
   CacheChange_t change;
   change.sequenceNumber = sequenceNumber; // Here is where the SN propagates from
   RTPSMessageCreator::addMessageData(&message, prefix, &change, topic, entityID, false, nullptr);
}

void test_UDPv4Tests::HELPER_FillAckNackMessage(CDRMessage_t& message)
{
   GuidPrefix_t prefix;
   EntityId_t entityID;
   SequenceNumberSet_t set;
	RTPSMessageCreator::addMessageAcknack(&message, prefix, prefix, entityID, entityID, set, 0, false);
}

void test_UDPv4Tests::HELPER_FillHeartbeatMessage(CDRMessage_t& message)
{
   GuidPrefix_t prefix;
   EntityId_t entityID;
   SequenceNumber_t sn1;
   SequenceNumber_t sn2;
	RTPSMessageCreator::addMessageHeartbeat(&message, prefix, entityID, entityID, sn1, sn2, 0, false, false);
}

int main(int argc, char **argv)
{
    g_default_port = get_port();

    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
