//  Copyright (c) 2015 John Biddiscombe
//
//  SPDX-License-Identifier: BSL-1.0
//  Distributed under the Boost Software License, Version 1.0. (See accompanying
//  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#pragma once

#include <hpx/modules/serialization.hpp>
#include <hpx/modules/util.hpp>

#include <hpx/parcelport_libfabric/config/defines.hpp>
#include <hpx/parcelset_base/locality.hpp>
//
#include <array>
#include <cstdint>
#include <cstring>
#include <rdma/fabric.h>
#include <utility>

// Different providers use different address formats that we must accommodate
// in our locality object.
#ifdef HPX_PARCELPORT_LIBFABRIC_GNI
#define HPX_PARCELPORT_LIBFABRIC_LOCALITY_SIZE 48
#endif

#if defined(HPX_PARCELPORT_LIBFABRIC_VERBS) ||                                 \
    defined(HPX_PARCELPORT_LIBFABRIC_SOCKETS) ||                               \
    defined(HPX_PARCELPORT_LIBFABRIC_PSM2)
#define HPX_PARCELPORT_LIBFABRIC_LOCALITY_SIZE 16
#define HPX_PARCELPORT_LIBFABRIC_LOCALITY_SOCKADDR
#endif

//typedef struct fid* fi_addr_t;

namespace hpx { namespace parcelset { namespace policies { namespace libfabric {

    // --------------------------------------------------------------------
    // Locality, in this structure we store the informartion required by
    // libfabric to make a connection to another node.
    // With libfabric 1.4.x the array contains the fabric ip address stored
    // as the second uint32_t in the array. For this reason we use an
    // array of uint32_t rather than uint8_t/char so we can easily access
    // the ip for debug/validation purposes
    // --------------------------------------------------------------------
    struct locality
    {
        // the number of 32bit ints stored in our array
        static const uint32_t array_length =
            HPX_PARCELPORT_LIBFABRIC_LOCALITY_SIZE / 4;
        static const uint32_t array_size =
            HPX_PARCELPORT_LIBFABRIC_LOCALITY_SIZE;

        // array type of our locality data
        typedef std::array<uint32_t, array_length> locality_data;

        static const char* type()
        {
            return "libfabric";
        }

        explicit locality(const locality_data& in_data)
        {
            std::memcpy(&data_[0], &in_data[0], array_size);
            fi_address_ = 0;
            LOG_DEBUG_MSG("explicit constructing locality from "
                << ipaddress(ip_address()) << ":" << decnumber(port()));
        }

        locality()
        {
            std::memset(&data_[0], 0x00, array_size);
            fi_address_ = 0;
            LOG_DEBUG_MSG("default constructing locality from "
                << ipaddress(ip_address()) << ":" << decnumber(port()));
        }

        locality(const locality& other)
          : data_(other.data_)
          , fi_address_(other.fi_address_)
        {
            ;
            LOG_DEBUG_MSG("copy constructing locality with "
                << ipaddress(ip_address()) << ":" << decnumber(port()));
        }

        locality(locality&& other)
          : data_(HPX_MOVE(other.data_))
          , fi_address_(other.fi_address_)
        {
            LOG_DEBUG_MSG("move constructing locality with "
                << ipaddress(ip_address()) << ":" << decnumber(port()));
        }

        locality& operator=(const locality& other)
        {
            data_ = other.data_;
            fi_address_ = other.fi_address_;
            LOG_DEBUG_MSG("copy operator locality with "
                << ipaddress(ip_address()) << ":" << decnumber(port()));
            return *this;
        }

        const uint32_t& ip_address() const
        {
#if defined(HPX_PARCELPORT_LIBFABRIC_LOCALITY_SOCKADDR)
            return reinterpret_cast<const struct sockaddr_in*>(data_.data())
                ->sin_addr.s_addr;
#elif defined(HPX_PARCELPORT_LIBFABRIC_GNI)
            return data_[0];
#else
            throw fabric_error(
                0, "unsupported fabric provider, please fix ASAP");
#endif
        }

        static const uint32_t& ip_address(const locality_data& data)
        {
#if defined(HPX_PARCELPORT_LIBFABRIC_LOCALITY_SOCKADDR)
            return reinterpret_cast<const struct sockaddr_in*>(&data)
                ->sin_addr.s_addr;
#elif defined(HPX_PARCELPORT_LIBFABRIC_GNI)
            return data[0];
#else
            throw fabric_error(
                0, "unsupported fabric provider, please fix ASAP");
#endif
        }

        fi_addr_t fi_address() const
        {
            return fi_address_;
        }

        void set_fi_address(fi_addr_t fi_addr)
        {
            fi_address_ = fi_addr;
        }

        uint16_t port() const
        {
            uint16_t port =
                256 * reinterpret_cast<const uint8_t*>(data_.data())[2] +
                reinterpret_cast<const uint8_t*>(data_.data())[3];
            return port;
        }

        // some condition marking this locality as valid
        explicit operator bool() const
        {
            return (ip_address() != 0);
        }

        void save(serialization::output_archive& ar) const
        {
            ar << data_;
        }

        void load(serialization::input_archive& ar)
        {
            ar >> data_;
        }

        const void* fabric_data() const
        {
            return data_.data();
        }

        char* fabric_data_writable()
        {
            return reinterpret_cast<char*>(data_.data());
        }

        bool valid()
        {
            return true;
        }

    private:
        friend bool operator==(locality const& lhs, locality const& rhs)
        {
#if defined(HPX_PARCELPORT_LIBFABRIC_HAVE_LOGGING)
            uint32_t a1 = lhs.ip_address();
            uint32_t a2 = rhs.ip_address();
            LOG_DEBUG_MSG(
                "Testing array equality " << ipaddress(a1) << ipaddress(a2));
#endif
            return (lhs.data_ == rhs.data_);
        }

        friend bool operator<(locality const& lhs, locality const& rhs)
        {
            uint32_t a1 = lhs.ip_address();
            uint32_t a2 = rhs.ip_address();
            return a1 < a2;
        }

        friend std::ostream& operator<<(std::ostream& os, locality const& loc)
        {
            hpx::util::ios_flags_saver ifs(os);
            for (uint32_t i = 0; i < array_length; ++i)
            {
                os << loc.data_[i];
            }
            return os;
        }

    private:
        locality_data data_;
        fi_addr_t fi_address_;
    };

}}}}    // namespace hpx::parcelset::policies::libfabric
