#if defined(__MINGW32__) && !defined(_WIN64)
/*
 * All the user-mode scaffolding necessary to backport GetAdaptersAddresses(2))
 * to the NT 5.x series. See further comments for any limitations.
 * NOTE: this is dead code, i haven't had time to debug it yet due to illness.
 * For now, downlevel platforms use GetAdaptersInfo(2) which is inet4 only.
 * -despair86 20/08/18
 */
#include <assert.h>
#include <stdio.h>

// apparently mingw-w64 loses its shit over this
// but only for 32-bit builds, naturally
#ifdef WIN32_LEAN_AND_MEAN
#undef WIN32_LEAN_AND_MEAN
#endif

// these need to be in a specific order
#include <windows.h>
#include <winternl.h>
#include <tdi.h>
#include "win32_intrnl.h"

const PWCHAR TcpFileName = L"\\Device\\Tcp";

// from ntdll.dll
typedef void(FAR PASCAL *pRtlInitUString)(UNICODE_STRING *, const WCHAR *);
typedef NTSTATUS(FAR PASCAL *pNTOpenFile)(HANDLE *, ACCESS_MASK,
                                          OBJECT_ATTRIBUTES *,
                                          IO_STATUS_BLOCK *, ULONG, ULONG);
typedef NTSTATUS(FAR PASCAL *pNTClose)(HANDLE);

#define FSCTL_TCP_BASE FILE_DEVICE_NETWORK

#define _TCP_CTL_CODE(Function, Method, Access) \
  CTL_CODE(FSCTL_TCP_BASE, Function, Method, Access)

#define IOCTL_TCP_QUERY_INFORMATION_EX \
  _TCP_CTL_CODE(0, METHOD_NEITHER, FILE_ANY_ACCESS)

typedef struct _InterfaceIndexTable
{
  DWORD numIndexes;
  DWORD numAllocated;
  DWORD indexes[1];
} InterfaceIndexTable;

NTSTATUS
tdiGetMibForIfEntity(HANDLE tcpFile, TDIEntityID *ent,
                     IFEntrySafelySized *entry)
{
  TCP_REQUEST_QUERY_INFORMATION_EX req;
  NTSTATUS status = 0;
  DWORD returnSize;

#ifdef DEBUG
  fprintf(stderr, "TdiGetMibForIfEntity(tcpFile %x,entityId %x)\n",
          (int)tcpFile, (int)ent->tei_instance);
#endif

  memset(&req, 0, sizeof(req));
  req.ID.toi_class  = INFO_CLASS_PROTOCOL;
  req.ID.toi_type   = INFO_TYPE_PROVIDER;
  req.ID.toi_id     = 1;
  req.ID.toi_entity = *ent;

  status =
      DeviceIoControl(tcpFile, IOCTL_TCP_QUERY_INFORMATION_EX, &req,
                      sizeof(req), entry, sizeof(*entry), &returnSize, NULL);

  if(!status)
  {
    perror("IOCTL Failed\n");
    return 0xc0000001;
  }

  fprintf(stderr,
          "TdiGetMibForIfEntity() => {\n"
          "  if_index ....................... %lx\n"
          "  if_type ........................ %lx\n"
          "  if_mtu ......................... %ld\n"
          "  if_speed ....................... %lx\n"
          "  if_physaddrlen ................. %ld\n",
          entry->ent.if_index, entry->ent.if_type, entry->ent.if_mtu,
          entry->ent.if_speed, entry->ent.if_physaddrlen);
  fprintf(stderr,
          "  if_physaddr .................... %02x:%02x:%02x:%02x:%02x:%02x\n"
          "  if_descr ....................... %s\n",
          entry->ent.if_physaddr[0] & 0xff, entry->ent.if_physaddr[1] & 0xff,
          entry->ent.if_physaddr[2] & 0xff, entry->ent.if_physaddr[3] & 0xff,
          entry->ent.if_physaddr[4] & 0xff, entry->ent.if_physaddr[5] & 0xff,
          entry->ent.if_descr);
  fprintf(stderr, "} status %08lx\n", status);

  return 0;
}

static NTSTATUS
tdiGetSetOfThings(HANDLE tcpFile, DWORD toiClass, DWORD toiType, DWORD toiId,
                  DWORD teiEntity, DWORD teiInstance, DWORD fixedPart,
                  DWORD entrySize, PVOID *tdiEntitySet, PDWORD numEntries)
{
  TCP_REQUEST_QUERY_INFORMATION_EX req;
  PVOID entitySet                    = 0;
  NTSTATUS status                    = 0;
  DWORD allocationSizeForEntityArray = entrySize * MAX_TDI_ENTITIES,
        arraySize                    = entrySize * MAX_TDI_ENTITIES;

  memset(&req, 0, sizeof(req));
  req.ID.toi_class               = toiClass;
  req.ID.toi_type                = toiType;
  req.ID.toi_id                  = toiId;
  req.ID.toi_entity.tei_entity   = teiEntity;
  req.ID.toi_entity.tei_instance = teiInstance;

  /* There's a subtle problem here...
   * If an interface is added at this exact instant, (as if by a PCMCIA
   * card insertion), the array will still not have enough entries after
   * have allocated it after the first DeviceIoControl call.
   *
   * We'll get around this by repeating until the number of interfaces
   * stabilizes.
   */
  do
  {
    status =
        DeviceIoControl(tcpFile, IOCTL_TCP_QUERY_INFORMATION_EX, &req,
                        sizeof(req), 0, 0, &allocationSizeForEntityArray, NULL);

    if(!status)
      return 0xc0000001;

    arraySize = allocationSizeForEntityArray;
    entitySet = HeapAlloc(GetProcessHeap(), 0, arraySize);

    if(!entitySet)
    {
      status = ((NTSTATUS)0xC000009A);
      return status;
    }

    status = DeviceIoControl(tcpFile, IOCTL_TCP_QUERY_INFORMATION_EX, &req,
                             sizeof(req), entitySet, arraySize,
                             &allocationSizeForEntityArray, NULL);

    /* This is why we have the loop -- we might have added an adapter */
    if(arraySize == allocationSizeForEntityArray)
      break;

    HeapFree(GetProcessHeap(), 0, entitySet);
    entitySet = 0;

    if(!status)
      return 0xc0000001;
  } while(TRUE); /* We break if the array we received was the size we
                  * expected.  Therefore, we got here because it wasn't */

  *numEntries   = (arraySize - fixedPart) / entrySize;
  *tdiEntitySet = entitySet;

  return 0;
}

static NTSTATUS
tdiGetEntityIDSet(HANDLE tcpFile, TDIEntityID **entitySet, PDWORD numEntities)
{
  NTSTATUS status =
      tdiGetSetOfThings(tcpFile, INFO_CLASS_GENERIC, INFO_TYPE_PROVIDER,
                        ENTITY_LIST_ID, GENERIC_ENTITY, 0, 0,
                        sizeof(TDIEntityID), (PVOID *)entitySet, numEntities);
  return status;
}

NTSTATUS
tdiGetIpAddrsForIpEntity(HANDLE tcpFile, TDIEntityID *ent, IPAddrEntry **addrs,
                         PDWORD numAddrs)
{
  NTSTATUS status;

#ifdef DEBUG
  fprintf(stderr, "TdiGetIpAddrsForIpEntity(tcpFile 0x%p, entityId 0x%lx)\n",
          tcpFile, ent->tei_instance);
#endif

  status = tdiGetSetOfThings(tcpFile, INFO_CLASS_PROTOCOL, INFO_TYPE_PROVIDER,
                             0x102, CL_NL_ENTITY, ent->tei_instance, 0,
                             sizeof(IPAddrEntry), (PVOID *)addrs, numAddrs);

  return status;
}

static VOID
tdiFreeThingSet(PVOID things)
{
  HeapFree(GetProcessHeap(), 0, things);
}

NTSTATUS
openTcpFile(PHANDLE tcpFile, ACCESS_MASK DesiredAccess)
{
  UNICODE_STRING fileName;
  OBJECT_ATTRIBUTES objectAttributes;
  IO_STATUS_BLOCK ioStatusBlock;
  NTSTATUS status;
  pRtlInitUString _RtlInitUnicodeString;
  pNTOpenFile _NTOpenFile;
  HANDLE ntdll;

  ntdll = GetModuleHandle("ntdll.dll");
  _RtlInitUnicodeString =
      (pRtlInitUString)GetProcAddress(ntdll, "RtlInitUnicodeString");
  _NTOpenFile = (pNTOpenFile)GetProcAddress(ntdll, "NtOpenFile");
  _RtlInitUnicodeString(&fileName, TcpFileName);
  InitializeObjectAttributes(&objectAttributes, &fileName, OBJ_CASE_INSENSITIVE,
                             NULL, NULL);
  status = _NTOpenFile(tcpFile, DesiredAccess | SYNCHRONIZE, &objectAttributes,
                       &ioStatusBlock, FILE_SHARE_READ | FILE_SHARE_WRITE,
                       FILE_SYNCHRONOUS_IO_NONALERT);
  /* String does not need to be freed: it points to the constant
   * string we provided */
  if(!NT_SUCCESS(status))
    *tcpFile = INVALID_HANDLE_VALUE;
  return status;
}
VOID
closeTcpFile(HANDLE h)
{
  pNTClose _NTClose;
  HANDLE ntdll = GetModuleHandle("ntdll.dll");
  _NTClose     = (pNTClose)GetProcAddress(ntdll, "NtClose");
  assert(h != INVALID_HANDLE_VALUE);
  _NTClose(h);
}

BOOL
isLoopback(HANDLE tcpFile, TDIEntityID *loop_maybe)
{
  IFEntrySafelySized entryInfo;
  NTSTATUS status;

  status = tdiGetMibForIfEntity(tcpFile, loop_maybe, &entryInfo);

  return NT_SUCCESS(status)
      && (entryInfo.ent.if_type == IFENT_SOFTWARE_LOOPBACK);
}

BOOL
isIpEntity(HANDLE tcpFile, TDIEntityID *ent)
{
  UNREFERENCED_PARAMETER(tcpFile);
  return (ent->tei_entity == CL_NL_ENTITY || ent->tei_entity == CO_NL_ENTITY);
}

NTSTATUS
getNthIpEntity(HANDLE tcpFile, DWORD index, TDIEntityID *ent)
{
  DWORD numEntities      = 0;
  DWORD numRoutes        = 0;
  TDIEntityID *entitySet = 0;
  NTSTATUS status        = tdiGetEntityIDSet(tcpFile, &entitySet, &numEntities);
  unsigned i;

  if(!NT_SUCCESS(status))
    return status;

  for(i = 0; i < numEntities; i++)
  {
    if(isIpEntity(tcpFile, &entitySet[i]))
    {
      fprintf(stderr, "Entity %d is an IP Entity\n", i);
      if(numRoutes == index)
        break;
      else
        numRoutes++;
    }
  }

  if(numRoutes == index && i < numEntities)
  {
    fprintf(stderr, "Index %lu is entity #%d - %04lx:%08lx\n", index, i,
            entitySet[i].tei_entity, entitySet[i].tei_instance);
    memcpy(ent, &entitySet[i], sizeof(*ent));
    tdiFreeThingSet(entitySet);
    return 0;
  }
  else
  {
    tdiFreeThingSet(entitySet);
    return 0xc000001;
  }
}

BOOL
isInterface(TDIEntityID *if_maybe)
{
  return if_maybe->tei_entity == IF_ENTITY;
}

NTSTATUS
getInterfaceInfoSet(HANDLE tcpFile, IFInfo **infoSet, PDWORD numInterfaces)
{
  DWORD numEntities;
  TDIEntityID *entIDSet = NULL;
  NTSTATUS status       = tdiGetEntityIDSet(tcpFile, &entIDSet, &numEntities);
  IFInfo *infoSetInt    = 0;
  int curInterf         = 0;
  unsigned i;

  if(!NT_SUCCESS(status))
  {
    fprintf(stderr, "getInterfaceInfoSet: tdiGetEntityIDSet() failed: 0x%lx\n",
            status);
    return status;
  }

  infoSetInt = HeapAlloc(GetProcessHeap(), 0, sizeof(IFInfo) * numEntities);

  if(infoSetInt)
  {
    for(i = 0; i < numEntities; i++)
    {
      if(isInterface(&entIDSet[i]))
      {
        infoSetInt[curInterf].entity_id = entIDSet[i];
        status = tdiGetMibForIfEntity(tcpFile, &entIDSet[i],
                                      &infoSetInt[curInterf].if_info);
        fprintf(stderr, "tdiGetMibForIfEntity: %08lx\n", status);
        if(NT_SUCCESS(status))
        {
          DWORD numAddrs;
          IPAddrEntry *addrs;
          TDIEntityID ip_ent;
          unsigned j;

          status = getNthIpEntity(tcpFile, curInterf, &ip_ent);
          if(NT_SUCCESS(status))
            status =
                tdiGetIpAddrsForIpEntity(tcpFile, &ip_ent, &addrs, &numAddrs);
          for(j = 0; NT_SUCCESS(status) && j < numAddrs; j++)
          {
            fprintf(stderr, "ADDR %d: index %ld (target %ld)\n", j,
                    addrs[j].iae_index,
                    infoSetInt[curInterf].if_info.ent.if_index);
            if(addrs[j].iae_index == infoSetInt[curInterf].if_info.ent.if_index)
            {
              memcpy(&infoSetInt[curInterf].ip_addr, &addrs[j],
                     sizeof(addrs[j]));
              curInterf++;
              break;
            }
          }
          if(NT_SUCCESS(status))
            tdiFreeThingSet(addrs);
        }
      }
    }

    tdiFreeThingSet(entIDSet);

    if(NT_SUCCESS(status))
    {
      *infoSet       = infoSetInt;
      *numInterfaces = curInterf;
    }
    else
    {
      HeapFree(GetProcessHeap(), 0, infoSetInt);
    }

    return status;
  }
  else
  {
    tdiFreeThingSet(entIDSet);
    return ((NTSTATUS)0xC000009A);
  }
}

NTSTATUS
getInterfaceInfoByName(HANDLE tcpFile, char *name, IFInfo *info)
{
  IFInfo *ifInfo;
  DWORD numInterfaces;
  unsigned i;
  NTSTATUS status = getInterfaceInfoSet(tcpFile, &ifInfo, &numInterfaces);

  if(NT_SUCCESS(status))
  {
    for(i = 0; i < numInterfaces; i++)
    {
      if(!strcmp((PCHAR)ifInfo[i].if_info.ent.if_descr, name))
      {
        memcpy(info, &ifInfo[i], sizeof(*info));
        break;
      }
    }

    HeapFree(GetProcessHeap(), 0, ifInfo);

    return i < numInterfaces ? 0 : 0xc0000001;
  }

  return status;
}

NTSTATUS
getInterfaceInfoByIndex(HANDLE tcpFile, DWORD index, IFInfo *info)
{
  IFInfo *ifInfo;
  DWORD numInterfaces;
  NTSTATUS status = getInterfaceInfoSet(tcpFile, &ifInfo, &numInterfaces);
  unsigned i;

  if(NT_SUCCESS(status))
  {
    for(i = 0; i < numInterfaces; i++)
    {
      if(ifInfo[i].if_info.ent.if_index == index)
      {
        memcpy(info, &ifInfo[i], sizeof(*info));
        break;
      }
    }

    HeapFree(GetProcessHeap(), 0, ifInfo);

    return i < numInterfaces ? 0 : 0xc0000001;
  }

  return status;
}

NTSTATUS
getIPAddrEntryForIf(HANDLE tcpFile, char *name, DWORD index, IFInfo *ifInfo)
{
  NTSTATUS status = name ? getInterfaceInfoByName(tcpFile, name, ifInfo)
                         : getInterfaceInfoByIndex(tcpFile, index, ifInfo);

  if(!NT_SUCCESS(status))
  {
    fprintf(stderr, "getIPAddrEntryForIf returning %lx\n", status);
  }

  return status;
}

InterfaceIndexTable *
getInterfaceIndexTableInt(BOOL nonLoopbackOnly)
{
  DWORD numInterfaces, curInterface = 0;
  unsigned i;
  IFInfo *ifInfo;
  InterfaceIndexTable *ret = 0;
  HANDLE tcpFile;
  NTSTATUS status = openTcpFile(&tcpFile, FILE_READ_DATA);

  ifInfo = NULL;

  if(NT_SUCCESS(status))
  {
    status = getInterfaceInfoSet(tcpFile, &ifInfo, &numInterfaces);

    fprintf(stderr, "InterfaceInfoSet: %08lx, %04lx:%08lx\n", status,
            ifInfo->entity_id.tei_entity, ifInfo->entity_id.tei_instance);

    if(NT_SUCCESS(status))
    {
      ret = (InterfaceIndexTable *)calloc(
          1, sizeof(InterfaceIndexTable) + (numInterfaces - 1) * sizeof(DWORD));

      if(ret)
      {
        ret->numAllocated = numInterfaces;
        fprintf(stderr, "NumInterfaces = %ld\n", numInterfaces);

        for(i = 0; i < numInterfaces; i++)
        {
          fprintf(stderr, "Examining interface %d\n", i);
          if(!nonLoopbackOnly || !isLoopback(tcpFile, &ifInfo[i].entity_id))
          {
            fprintf(stderr, "Interface %d matches (%ld)\n", i, curInterface);
            ret->indexes[curInterface++] = ifInfo[i].if_info.ent.if_index;
          }
        }

        ret->numIndexes = curInterface;
      }

      tdiFreeThingSet(ifInfo);
    }
    closeTcpFile(tcpFile);
  }

  return ret;
}

InterfaceIndexTable *
getInterfaceIndexTable(void)
{
  return getInterfaceIndexTableInt(FALSE);
}

#endif

// there's probably an use case for a _newer_ implementation
// of pthread_setname_np(3), in fact, I may just merge _this_
// upstream...
#if 0
#include <windows.h>

typedef HRESULT(FAR PASCAL *p_SetThreadDescription)(void *, const wchar_t *);
#define EXCEPTION_SET_THREAD_NAME ((DWORD)0x406D1388)

typedef struct _THREADNAME_INFO
{
  DWORD dwType;     /* must be 0x1000 */
  LPCSTR szName;    /* pointer to name (in user addr space) */
  DWORD dwThreadID; /* thread ID (-1=caller thread) */
  DWORD dwFlags;    /* reserved for future use, must be zero */
} THREADNAME_INFO;

void
SetThreadName(DWORD dwThreadID, LPCSTR szThreadName)
{
  THREADNAME_INFO info;
  DWORD infosize;
  HANDLE hThread;
  /* because loonix is SHIT and limits thread names to 16 bytes */
  wchar_t thr_name_w[16];
  p_SetThreadDescription _SetThreadDescription;

  /* current win10 flights now have a new named-thread API, let's try to use
   * that first! */
  /* first, dlsym(2) the new call from system library */
  hThread               = NULL;
  _SetThreadDescription = (p_SetThreadDescription)GetProcAddress(
      GetModuleHandle("kernel32"), "SetThreadDescription");
  if(_SetThreadDescription)
  {
    /* grab another reference to the thread */
    hThread = OpenThread(THREAD_SET_LIMITED_INFORMATION, FALSE, dwThreadID);
    /* windows takes unicode, our input is utf-8 or plain ascii */
    MultiByteToWideChar(CP_ACP, 0, szThreadName, -1, thr_name_w, 16);
    if(hThread)
      _SetThreadDescription(hThread, thr_name_w);
    else
      goto old; /* for whatever reason, we couldn't get a handle to the thread.
                   Just use the old method. */
  }
  else
  {
  old:
    info.dwType     = 0x1000;
    info.szName     = szThreadName;
    info.dwThreadID = dwThreadID;
    info.dwFlags    = 0;

    infosize = sizeof(info) / sizeof(DWORD);

    __try
    {
      RaiseException(EXCEPTION_SET_THREAD_NAME, 0, infosize, (DWORD *)&info);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
    }
  }
  /* clean up */
  if(hThread)
    CloseHandle(hThread);
}
#endif
