// 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 <fastdds/rtps/network/NetworkFactory.h>

#include <limits>
#include <utility>

#include <fastdds/rtps/common/Guid.h>
#include <fastdds/rtps/participant/RTPSParticipant.h>
#include <fastdds/rtps/transport/TransportDescriptorInterface.h>
#include <fastrtps/utils/IPFinder.h>
#include <fastrtps/utils/IPLocator.h>

#include <rtps/transport/UDPv4Transport.h>

using namespace std;
using namespace eprosima::fastdds::rtps;

namespace eprosima {
namespace fastrtps {
namespace rtps {

using SendResourceList = fastdds::rtps::SendResourceList;

NetworkFactory::NetworkFactory()
    : maxMessageSizeBetweenTransports_(std::numeric_limits<uint32_t>::max())
    , minSendBufferSize_(std::numeric_limits<uint32_t>::max())
{
}

bool NetworkFactory::build_send_resources(
        SendResourceList& sender_resource_list,
        const Locator_t& locator)
{
    bool returned_value = false;

    for (auto& transport : mRegisteredTransports)
    {
        returned_value |= transport->OpenOutputChannel(sender_resource_list, locator);
    }

    return returned_value;
}

bool NetworkFactory::BuildReceiverResources(
        Locator_t& local,
        std::vector<std::shared_ptr<ReceiverResource>>& returned_resources_list,
        uint32_t receiver_max_message_size)
{
    bool returnedValue = false;
    for (auto& transport : mRegisteredTransports)
    {
        if (transport->IsLocatorSupported(local))
        {
            if (!transport->IsInputChannelOpen(local))
            {
                uint32_t max_recv_buffer_size = (std::min)(
                    transport->max_recv_buffer_size(),
                    receiver_max_message_size);

                std::shared_ptr<ReceiverResource> newReceiverResource = std::shared_ptr<ReceiverResource>(
                    new ReceiverResource(*transport, local, max_recv_buffer_size));

                if (newReceiverResource->mValid)
                {
                    returned_resources_list.push_back(newReceiverResource);
                    returnedValue = true;
                }
            }
            else
            {
                returnedValue = true;
            }
        }
    }
    return returnedValue;
}

bool NetworkFactory::RegisterTransport(
        const TransportDescriptorInterface* descriptor,
        const fastrtps::rtps::PropertyPolicy* properties)
{
    bool wasRegistered = false;

    uint32_t minSendBufferSize = std::numeric_limits<uint32_t>::max();

    std::unique_ptr<TransportInterface> transport(descriptor->create_transport());

    if (transport)
    {
        if (transport->init(properties))
        {
            minSendBufferSize = transport->get_configuration()->min_send_buffer_size();
            mRegisteredTransports.emplace_back(std::move(transport));
            wasRegistered = true;
        }

        if (wasRegistered)
        {
            if (descriptor->max_message_size() < maxMessageSizeBetweenTransports_)
            {
                maxMessageSizeBetweenTransports_ = descriptor->max_message_size();
            }

            if (minSendBufferSize < minSendBufferSize_)
            {
                minSendBufferSize_ = minSendBufferSize;
            }
        }
    }

    return wasRegistered;
}

void NetworkFactory::NormalizeLocators(
        LocatorList_t& locators)
{
    LocatorList_t normalizedLocators;

    std::for_each(locators.begin(), locators.end(), [&](Locator_t& loc)
            {
                bool normalized = false;
                for (auto& transport : mRegisteredTransports)
                {
                    // Check if the locator is supported and filter unicast locators.
                    if (transport->IsLocatorSupported(loc) &&
                    (IPLocator::isMulticast(loc) ||
                    transport->is_locator_allowed(loc)))
                    {
                        // First found transport that supports it, this will normalize the locator.
                        normalizedLocators.push_back(transport->NormalizeLocator(loc));
                        normalized = true;
                    }
                }

                if (!normalized)
                {
                    normalizedLocators.push_back(loc);
                }
            });

    locators.swap(normalizedLocators);
}

bool NetworkFactory::transform_remote_locator(
        const Locator_t& remote_locator,
        Locator_t& result_locator) const
{
    for (auto& transport : mRegisteredTransports)
    {
        if (transport->transform_remote_locator(remote_locator, result_locator))
        {
            return true;
        }
    }

    return false;
}

void NetworkFactory::select_locators(
        LocatorSelector& selector) const
{
    selector.selection_start();

    /* - for each transport:
     *   - transport_starts is called
     *   - transport handles the selection state of each entry
     *   - select may be called
     */
    for (auto& transport : mRegisteredTransports)
    {
        transport->select_locators(selector);
    }
}

bool NetworkFactory::is_local_locator(
        const Locator_t& locator) const
{
    for (auto& transport : mRegisteredTransports)
    {
        if (transport->IsLocatorSupported(locator))
        {
            return transport->is_local_locator(locator);
        }
    }

    return false;
}

size_t NetworkFactory::numberOfRegisteredTransports() const
{
    return mRegisteredTransports.size();
}

bool NetworkFactory::generate_locators(
        uint16_t physical_port,
        int locator_kind,
        LocatorList_t& ret_locators)
{
    ret_locators.clear();
    if (locator_kind == LOCATOR_KIND_TCPv4 || locator_kind == LOCATOR_KIND_UDPv4)
    {
        IPFinder::getIP4Address(&ret_locators);
    }
    else if (locator_kind == LOCATOR_KIND_TCPv6 || locator_kind == LOCATOR_KIND_UDPv6)
    {
        IPFinder::getIP6Address(&ret_locators);
    }
    for (Locator_t& loc : ret_locators)
    {
        loc.kind = locator_kind;
        loc.port = physical_port;
    }
    return !ret_locators.empty();
}

void NetworkFactory::GetDefaultOutputLocators(
        LocatorList_t& defaultLocators)
{
    defaultLocators.clear();
    for (auto& transport : mRegisteredTransports)
    {
        transport->AddDefaultOutputLocator(defaultLocators);
    }
}

bool NetworkFactory::getDefaultMetatrafficMulticastLocators(
        LocatorList_t& locators,
        uint32_t metatraffic_multicast_port) const
{
    bool result = false;

    TransportInterface* shm_transport = nullptr;

    for (auto& transport : mRegisteredTransports)
    {
        // For better fault-tolerance reasons, SHM multicast metatraffic is avoided if it is already provided
        // by another transport
        if (transport->kind() != LOCATOR_KIND_SHM)
        {
            result |= transport->getDefaultMetatrafficMulticastLocators(locators, metatraffic_multicast_port);
        }
        else
        {
            shm_transport = transport.get();
        }
    }

    if (locators.size() == 0 && shm_transport)
    {
        result |= shm_transport->getDefaultMetatrafficMulticastLocators(locators, metatraffic_multicast_port);
    }

    return result;
}

bool NetworkFactory::fillMetatrafficMulticastLocator(
        Locator_t& locator,
        uint32_t metatraffic_multicast_port) const
{
    bool result = false;
    for (auto& transport : mRegisteredTransports)
    {
        if (transport->IsLocatorSupported(locator))
        {
            result |= transport->fillMetatrafficMulticastLocator(locator, metatraffic_multicast_port);
        }
    }
    return result;
}

bool NetworkFactory::getDefaultMetatrafficUnicastLocators(
        LocatorList_t& locators,
        uint32_t metatraffic_unicast_port) const
{
    bool result = false;
    for (auto& transport : mRegisteredTransports)
    {
        result |= transport->getDefaultMetatrafficUnicastLocators(locators, metatraffic_unicast_port);
    }
    return result;
}

bool NetworkFactory::fillMetatrafficUnicastLocator(
        Locator_t& locator,
        uint32_t metatraffic_unicast_port) const
{
    bool result = false;
    for (auto& transport : mRegisteredTransports)
    {
        if (transport->IsLocatorSupported(locator))
        {
            result |= transport->fillMetatrafficUnicastLocator(locator, metatraffic_unicast_port);
        }
    }
    return result;
}

bool NetworkFactory::configureInitialPeerLocator(
        uint32_t domain_id,
        Locator_t& locator,
        RTPSParticipantAttributes& m_att) const
{
    bool result = false;
    for (auto& transport : mRegisteredTransports)
    {
        if (transport->IsLocatorSupported(locator))
        {
            result |= transport->configureInitialPeerLocator(locator, m_att.port, domain_id,
                            m_att.builtin.initialPeersList);
        }
    }
    return result;
}

bool NetworkFactory::getDefaultUnicastLocators(
        uint32_t domain_id,
        LocatorList_t& locators,
        const RTPSParticipantAttributes& m_att) const
{
    bool result = false;
    for (auto& transport : mRegisteredTransports)
    {
        result |= transport->getDefaultUnicastLocators(locators, calculate_well_known_port(domain_id, m_att, false));
    }
    return result;
}

bool NetworkFactory::fill_default_locator_port(
        uint32_t domain_id,
        Locator_t& locator,
        const RTPSParticipantAttributes& m_att,
        bool is_multicast) const
{
    bool result = false;
    for (auto& transport : mRegisteredTransports)
    {
        if (transport->IsLocatorSupported(locator))
        {
            result |= transport->fillUnicastLocator(locator, calculate_well_known_port(domain_id, m_att, is_multicast));
        }
    }
    return result;
}

void NetworkFactory::Shutdown()
{
    for (auto& transport : mRegisteredTransports)
    {
        transport->shutdown();
    }
}

uint16_t NetworkFactory::calculate_well_known_port(
        uint32_t domain_id,
        const RTPSParticipantAttributes& att,
        bool is_multicast) const
{

    uint32_t port = att.port.portBase +
            att.port.domainIDGain * domain_id +
            (is_multicast ?
            att.port.offsetd2 :
            att.port.offsetd3 + att.port.participantIDGain * att.participantID);

    if (port > 65535)
    {
        logError(RTPS, "Calculated port number is too high. Probably the domainId is over 232, there are "
                << "too much participants created or portBase is too high.");
        std::cout << "Calculated port number is too high. Probably the domainId is over 232, there are "
                  << "too much participants created or portBase is too high." << std::endl;
        std::cout.flush();
        exit(EXIT_FAILURE);
    }

    return static_cast<uint16_t>(port);
}

void NetworkFactory::update_network_interfaces()
{
    for (auto& transport : mRegisteredTransports)
    {
        transport->update_network_interfaces();
    }
}

} // namespace rtps
} // namespace fastrtps
} // namespace eprosima
