#include "Based.h"
#include "WaitGroup.h"

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    UNREFERENCED_PARAMETER(hModule);
    UNREFERENCED_PARAMETER(lpReserved);
    UNREFERENCED_PARAMETER(ul_reason_for_call);

    return TRUE;
}

WaitGroup wg;
void UnicastIPChangeCallback(PVOID ctx, PMIB_UNICASTIPADDRESS_ROW row, MIB_NOTIFICATION_TYPE type)
{
    UNREFERENCED_PARAMETER(ctx);
    UNREFERENCED_PARAMETER(row);
    UNREFERENCED_PARAMETER(type);

    wg.Done();
}

bool make(PMIB_IPFORWARD_ROW2 rule, USHORT inet, const char* address, UINT8 cidr, const char* gateway, ULONG index, ULONG metric)
{
    rule->InterfaceIndex = index;
    rule->DestinationPrefix.PrefixLength = cidr;

    if (AF_INET == inet)
    {
        rule->DestinationPrefix.Prefix.Ipv4.sin_family = inet;
        if (inet_pton(inet, address, &rule->DestinationPrefix.Prefix.Ipv4.sin_addr) != 1)
        {
            return false;
        }

        if (strlen(gateway))
        {
            rule->NextHop.Ipv4.sin_family = inet;
            if (inet_pton(inet, gateway, &rule->NextHop.Ipv4.sin_addr) != 1)
            {
                return false;
            }
        }
    }
    else if (AF_INET6 == inet)
    {
        rule->DestinationPrefix.Prefix.Ipv6.sin6_family = inet;
        if (inet_pton(inet, address, &rule->DestinationPrefix.Prefix.Ipv6.sin6_addr) != 1)
        {
            return false;
        }

        if (strlen(gateway))
        {
            rule->NextHop.Ipv6.sin6_family = inet;
            if (inet_pton(inet, gateway, &rule->NextHop.Ipv6.sin6_addr) != 1)
            {
                return false;
            }
        }
    }

    rule->ValidLifetime = 0xffffffff;
    rule->PreferredLifetime = 0xffffffff;
    rule->Metric = metric;
    rule->Protocol = MIB_IPPROTO_NETMGMT;
    return true;
}

extern "C" {
    __declspec(dllexport) ULONG __cdecl ConvertLuidToIndex(ULONG64 id)
    {
        NET_LUID luid;
        luid.Value = id;

        NET_IFINDEX index = 0;
        if (NO_ERROR != ConvertInterfaceLuidToIndex(&luid, &index))
        {
            return 0;
        }

        return index;
    }

    __declspec(dllexport) void __cdecl WaitForUnicastIP()
    {
        wg.Add(1);

        HANDLE hCallback = NULL;
        NotifyUnicastIpAddressChange(AF_INET, UnicastIPChangeCallback, NULL, FALSE, &hCallback);

        wg.Wait();

        CancelMibChangeNotify2(hCallback);
    }

    __declspec(dllexport) BOOL __cdecl CreateIPv4(const char* address, const char* netmask, ULONG index)
    {
        ULONG addr = 0;
        if (inet_pton(AF_INET, address, &addr) != 1)
        {
            return FALSE;
        }

        ULONG mask = 0;
        if (inet_pton(AF_INET, netmask, &mask) != 1)
        {
            return FALSE;
        }

        ULONG context = 0;
        ULONG instance = 0;
        return (NO_ERROR == AddIPAddress(addr, mask, index, &context, &instance)) ? TRUE : FALSE;
    }

    __declspec(dllexport) BOOL __cdecl CreateUnicastIP(USHORT inet, const char* address, UINT8 cidr, ULONG index)
    {
        MIB_UNICASTIPADDRESS_ROW addr;
        InitializeUnicastIpAddressEntry(&addr);

        addr.InterfaceIndex = index;
        addr.OnLinkPrefixLength = cidr;

        if (AF_INET == inet)
        {
            addr.Address.Ipv4.sin_family = inet;
            if (!inet_pton(inet, address, &addr.Address.Ipv4.sin_addr))
            {
                return FALSE;
            }
        }
        else if (AF_INET6 == inet)
        {
            addr.Address.Ipv6.sin6_family = inet;
            if (!inet_pton(inet, address, &addr.Address.Ipv6.sin6_addr))
            {
                return FALSE;
            }
        }
        else
        {
            return FALSE;
        }

        return (NO_ERROR == CreateUnicastIpAddressEntry(&addr)) ? TRUE : FALSE;
    }

    __declspec(dllexport) BOOL __cdecl CreateRoute(USHORT inet, const char* address, UINT8 cidr, const char* gateway, ULONG index, ULONG metric = 1)
    {
        MIB_IPFORWARD_ROW2 rule;
        InitializeIpForwardEntry(&rule);

        if (!make(&rule, inet, address, cidr, gateway, index, metric))
        {
            return FALSE;
        }

        return (NO_ERROR == CreateIpForwardEntry2(&rule)) ? TRUE : FALSE;
    }

    __declspec(dllexport) BOOL __cdecl DeleteRoute(USHORT inet, const char* address, UINT8 cidr, const char* gateway, ULONG index, ULONG metric = 1)
    {
        MIB_IPFORWARD_ROW2 rule;
        InitializeIpForwardEntry(&rule);

        if (!make(&rule, inet, address, cidr, gateway, index, metric))
        {
            return FALSE;
        }

        return (NO_ERROR == DeleteIpForwardEntry2(&rule)) ? TRUE : FALSE;
    }
}
