// Copyright (C) Microsoft Corporation. All rights reserved.
#include <iostream>
#include <regex>
#include "address.h"
#include "Syscall.h"

Address::Address(short family, size_t prefixLength, const std::string& address, IpPrefixOrigin prefixOrigin, IpSuffixOrigin suffixOrigin, int preferredLifetime) :
    m_family(family), m_prefixLength(prefixLength), m_address(address), m_prefixOrigin(prefixOrigin), m_suffixOrigin(suffixOrigin)
{
    // We cannot plumb temporary addresses (identified by RA prefix and random suffix) in Linux.
    // In order for Linux to prefer temporary addresses over public addresses during source address
    // selection, we instead mark public addresses as deprecated and plumb temporary addresses as
    // public preferred addresses.
    if (preferredLifetime == 0 || (prefixOrigin == IpPrefixOrigin::RouterAdvertisement && suffixOrigin == IpSuffixOrigin::LinkLayerAddress))
    {
        m_preferredLifetime = 0;
    }
    else
    {
        m_preferredLifetime = 0xFFFFFFFF;
    }
}

Address::Address(short family, size_t prefixLength, const std::string& address) :
    Address(family, prefixLength, address, IpPrefixOrigin::Manual, IpSuffixOrigin::Manual, 0xFFFFFFFF)
{
}

bool Address::operator==(const Address& other) const
{
    return m_family == other.Family() && m_address == other.Addr() && m_prefixLength == other.PrefixLength();
}

bool Address::operator!=(const Address& other) const
{
    return !(*this == other);
}

std::ostream& operator<<(std::ostream& out, const Address& address)
{
    return out << address.Addr() << "/" << address.PrefixLength();
}

Address Address::FromBytes(int family, int prefixLength, const void* ptr)
{
    static_assert(INET6_ADDRSTRLEN > INET_ADDRSTRLEN);

    std::string addressString(INET6_ADDRSTRLEN, '\0');

    if (inet_ntop(family, ptr, addressString.data(), addressString.size()) == nullptr)
    {
        throw RuntimeErrorWithSourceLocation(std::format("inet_ntop failed, {}", errno));
    }

    return {family, static_cast<size_t>(prefixLength), addressString.c_str()};
}

void Address::ConvertToBytes(void* ptr) const
{
    Syscall(inet_pton, m_family, m_address.c_str(), ptr);
}

Address Address::FromPrefixString(int family, const std::string& prefix)
{
    const std::regex exp(R"((.*)\/((\d){1,3}))");
    std::smatch match;
    if (!std::regex_search(prefix.begin(), prefix.end(), match, exp) || match.size() != 4)
    {
        throw RuntimeErrorWithSourceLocation(std::format("Failed to parse prefix: {}", prefix));
    }

    in6_addr tmpAddr;
    int err = inet_pton(family, match.str(1).c_str(), &tmpAddr);
    if (err != 1)
    {
        throw RuntimeErrorWithSourceLocation(std::format(
            "Parsed invalid address ({}) from prefix: {} and family: {} with error {} and errno {}", match.str(1), prefix, family, err, errno));
    }

    return Address{family, std::stoul(match.str(2)), match.str(1)};
}

Address Address::FromBinary(int family, int prefixLength, const void* data)
{
    std::string addr(INET6_ADDRSTRLEN, '\0');
    if (inet_ntop(family, data, addr.data(), addr.size()) == nullptr)
    {
        throw RuntimeErrorWithSourceLocation(
            std::format("inet_ntop failed for family {} prefixLength {} with errno {}", family, prefixLength, errno));
    }

    addr.resize(strlen(addr.c_str()));

    return {family, static_cast<size_t>(prefixLength), addr};
}

short Address::Family() const noexcept
{
    return m_family;
}

size_t Address::PrefixLength() const noexcept
{
    return m_prefixLength;
}

const std::string Address::Addr() const noexcept
{
    return m_address;
}

int Address::Flags() const noexcept
{
    // Note: the below is only useful if the kernel ever supports plumbing temporary addresses
    int flags = 0;
    if (m_prefixOrigin == IpPrefixOrigin::RouterAdvertisement && m_suffixOrigin == IpSuffixOrigin::Random)
    {
        flags |= IFA_F_TEMPORARY;
    }

    return flags;
}

int Address::Scope() const noexcept
{
    if (m_prefixOrigin == IpPrefixOrigin::WellKnown && m_suffixOrigin == IpSuffixOrigin::LinkLayerAddress)
    {
        return RT_SCOPE_LINK;
    }

    return RT_SCOPE_UNIVERSE;
}

int Address::PreferredLifetime() const noexcept
{
    return m_preferredLifetime;
}

bool Address::IsIpv4() const noexcept
{
    return m_family == AF_INET;
}

bool Address::IsPrefixRouteAutogenerationDisabled() const noexcept
{
    return m_disableAutogeneratedPrefixRoute;
}
