/* Copyright(C) 2007-2016 VoIP objects (voipobjects.com)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "ICENetworkHelper.h"
#include "ICEPlatform.h"
#include "ICELog.h"
#include <string>
#include <vector>
#include <algorithm>
#include <stdexcept>
#include <assert.h>

#ifdef TARGET_WIN
# include <tchar.h>
# if defined(WINDOWS_RT)
#   include "ICEWinRtSupport.h"
# endif
#elif defined(TARGET_IOS)
# include "ICEIosSupport.h"
#else
# include <unistd.h>
# if defined(TARGET_ANDROID) || defined(TARGET_LINUX)
#  include <linux/if.h>
#  include <linux/in6.h>
#  if defined(TARGET_ANDROID)
#    if __ANDROID_API__ < 24
#      include "android-ifaddrs/android-ifaddrs.h"
//#      error not supported for ANDROID_API < 24
#    else
#      include <ifaddrs.h>
#    endif
#  endif
# endif
#endif

#include "ICEError.h"
using namespace ice;

#define LOG_SUBSYSTEM "ICE"

#if defined(TARGET_WIN) && !defined(WINDOWS_RT)
class IPHlpApi
{
public:
  HMODULE mHandle;
  DWORD (WINAPI *pGetBestInterfaceEx) (struct sockaddr* pDestAddr, PDWORD pdwBestIfIndex);
  DWORD (WINAPI *pGetBestInterface) (IPAddr dwDestAddr, PDWORD pdwBestIfIndex);
  ULONG (WINAPI *pGetAdaptersAddresses) (ULONG Family, ULONG Flags, PVOID Reserved, PIP_ADAPTER_ADDRESSES AdapterAddresses, PULONG SizePointer);

  void ResolveProc(const char* name, FARPROC& proc)
  {
    proc = GetProcAddress(mHandle, name);
  }

public:
  IPHlpApi()
  {
    pGetBestInterfaceEx = NULL;
    pGetBestInterface = NULL;
    pGetAdaptersAddresses = NULL;
    mHandle = ::LoadLibraryW(L"iphlpapi.dll");
    if (mHandle)
    {
      ResolveProc("GetBestInterfaceEx", (FARPROC&)pGetBestInterfaceEx);
      ResolveProc("GetBestInterface", (FARPROC&)pGetBestInterface);
      ResolveProc("GetAdaptersAddresses", (FARPROC&)pGetAdaptersAddresses);
    }
  }

  ~IPHlpApi()
  {
    if (mHandle)
      FreeLibrary(mHandle);
  }

};
IPHlpApi IPHelper;
#endif

NetworkHelper::NetworkHelper()
{
  reload(NetworkType_None);
}

NetworkHelper::~NetworkHelper()
{
}

void NetworkHelper::reload(int networkType)
{
  Lock l(mInstanceGuard);
  
  // Get list of interfaces
#if defined(TARGET_WIN) && !defined(WINDOWS_RT)
  mInterfaceList.clear();
  mIP2IndexList.clear();

  DWORD dwRet, dwSize;
  DWORD flags = GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER;
  IP_ADAPTER_ADDRESSES* ipAdapters = NULL;
  dwRet = IPHelper.pGetAdaptersAddresses(AF_INET, flags, NULL, NULL, &dwSize);
  if (dwRet == ERROR_BUFFER_OVERFLOW)
  {
    ipAdapters = (IP_ADAPTER_ADDRESSES*)malloc(dwSize);
    if (!ipAdapters)
      return;

    dwRet = IPHelper.pGetAdaptersAddresses(AF_INET, flags, NULL, ipAdapters, &dwSize);
    if (dwRet != ERROR_SUCCESS) 
    {
      free(ipAdapters);
      throw Exception(CANNOT_FIND_INTERFACES);
    }
    processAdaptersList(ipAdapters);
    free(ipAdapters);
  }

#ifdef ICE_IPV6SUPPORT
  dwRet = IPHelper.pGetAdaptersAddresses(AF_INET6, flags, NULL, NULL, &dwSize);
  if (dwRet == ERROR_BUFFER_OVERFLOW)
  {
    ipAdapters = (IP_ADAPTER_ADDRESSES*)malloc(dwSize);
    if (!ipAdapters)
      return;

    dwRet = IPHelper.pGetAdaptersAddresses(AF_INET6, flags, NULL, ipAdapters, &dwSize);
    if (dwRet != ERROR_SUCCESS) 
    {
      free(ipAdapters);
      throw NetworkAddress(CANNOT_FIND_INTERFACES);
    }
    processAdaptersList(ipAdapters);
    free(ipAdapters);
  }
#endif

  for (unsigned i=0; i<mIP2IndexList.size(); i++)
  {
    bool handleIt = true;
    NetworkAddress ip = mIP2IndexList[i].mIP;
#ifndef ICE_LOOPBACK_SUPPORT
    handleIt = !ip.isLoopback();
#endif
#ifdef ICE_SKIP_LINKLOCAL
    handleIt &= !ip.isLinkLocal();
#endif
    if (handleIt)
      mInterfaceList.push_back(ip);
  }
#else
  mIPList.clear();
#if TARGET_OS_IPHONE
  ICELogDebug(<< "Obtaining IPv4 interfaces.");
  fillIosInterfaceList(AF_INET, networkType, mIPList);
  ICELogDebug(<< "Obtaining IPv6 interfaces.");
  fillIosInterfaceList(AF_INET6, networkType, mIPList);
  for (auto& addr: mIPList)
    ICELogDebug(<< "  " << addr.toStdString());
#elif defined(WINDOWS_RT)
  fillUwpInterfaceList(AF_INET, networkType, mIPList);
  fillUwpInterfaceList(AF_INET6, networkType, mIPList);
#else
  // https://github.com/golang/go/issues/40569
  struct ifaddrs* il = NULL;
  if (getifaddrs(&il))
    throw Exception(GETIFADDRS_FAILED, errno);
  if (il)
  {
    struct ifaddrs* current = il;
    while (current)
    {
      //char ipbuffer[64];
      NetworkAddress addr;
      addr.setPort(1000); // Set fake address to keep NetworkAddress initialized
      bool handleIt = true;
      switch(current->ifa_addr->sa_family)
      {
      case AF_INET:
        // just for debug
        // printf("%s", inet_ntoa(reinterpret_cast<sockaddr_in*>(current->ifa_addr)->sin_addr));
        addr.setIp(reinterpret_cast<sockaddr_in*>(current->ifa_addr)->sin_addr);
#ifndef ICE_LOOPBACK_SUPPORT
        handleIt = !addr.isLoopback();
#endif
#ifdef ICE_SKIP_LINKLOCAL
        handleIt &= !addr.isLinkLocal();
#endif
        if (handleIt)
          mIPList.push_back(addr);
        break;

#ifdef ICE_IPV6SUPPORT
      case AF_INET6:
        addr.setIp(reinterpret_cast<sockaddr_in6*>(current->ifa_addr)->sin6_addr);
#ifndef ICE_LOOPBACK_SUPPORT
        if (!addr.isLoopback())
#endif
          mIPList.push_back(addr);
        break;
#endif
      }
      current = current->ifa_next;
    }
    freeifaddrs(il);
  }
#endif
#endif
}

#if defined(TARGET_WIN) && !defined(WINDOWS_RT)
void NetworkHelper::processAdaptersList(IP_ADAPTER_ADDRESSES* addresses)
{
  IP_ADAPTER_ADDRESSES *AI;
  int i;
  for (i = 0, AI = addresses; AI != NULL; AI = AI->Next, i++) 
  {
    for (PIP_ADAPTER_UNICAST_ADDRESS unicast = AI->FirstUnicastAddress; unicast; unicast = unicast->Next)
    {
#ifdef ICE_IPV6SUPPORT
      if (unicast->Address.lpSockaddr->sa_family != AF_INET && unicast->Address.lpSockaddr->sa_family != AF_INET6)
        continue;
#else
      if (unicast->Address.lpSockaddr->sa_family != AF_INET)
        continue;
#endif
      IP2Index rec;
      rec.mIP = NetworkAddress(*unicast->Address.lpSockaddr, unicast->Address.iSockaddrLength);
      rec.mIndex = AI->IfIndex;
      mIP2IndexList.push_back(rec);
    }
  }
}

#endif

Mutex& NetworkHelper::guard()
{
  return mInstanceGuard;
}

std::vector<NetworkAddress>& NetworkHelper::interfaceList()
{
#if defined(TARGET_WIN) && !defined(WINDOWS_RT)
  return mInterfaceList;
#else
  return mIPList;
#endif
}



NetworkAddress NetworkHelper::sourceInterface(const NetworkAddress& remoteIP)
{
  //Lock l(mInstanceGuard);
  
  if (remoteIP.isLoopback())
    return remoteIP.family() == AF_INET ? NetworkAddress::LoopbackAddress4 : NetworkAddress::LoopbackAddress6;

#if defined(TARGET_WIN) && !defined(WINDOWS_RT)
  if (IPHelper.pGetBestInterfaceEx)
  {
    DWORD index = 0;
    DWORD rescode = IPHelper.pGetBestInterfaceEx(remoteIP.genericsockaddr(), &index);
    if (rescode == NO_ERROR)
      return ipInterfaceByIndex(index);
  }
  else
  if (IPHelper.pGetBestInterface)
  {
    DWORD index = 0;
    DWORD rescode = IPHelper.pGetBestInterface(remoteIP.sockaddr4()->sin_addr.S_un.S_addr, &index);
    if (rescode == NO_ERROR)
      return ipInterfaceByIndex(index);
  }
  #ifdef ICE_LOOPBACK_SUPPORT
  return NetworkAddress::LoopbackAddress4;
  #else
  return remoteIP;
  #endif

#elif defined(WINDOWS_RT)
  std::vector<NetworkAddress>& il = interfaceList();
  if (il.size())
    return il.front();
  else
    return NetworkAddress();
#else
  if (remoteIP.isLoopback())
    return remoteIP;

/*#if defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR)
  // There is only one interface - return it
  std::vector<NetworkAddress>& il = interfaceList();
  if (il.size())
  {
    if (!il.front().isZero())
      return il.front();
  }
    
#endif*/
    
  // Here magic goes.
  // 1) Check if remoteIP is IP4 or IP6
  // 2) Check if it is LAN or loopback or internet address
  // 3) For LAN address - try to find interface from the same network
  //    For Loopback address - return loopback address
  //    For internet address - find default interface

	//printf("remote ip %s\n", remoteIP.GetIP().c_str());
  if (remoteIP.isLAN())
  {
    for (unsigned i=0; i<mIPList.size(); i++)
		{
			//printf("local ip %s\n", mIPList[i].GetIP().c_str());
      if (NetworkAddress::isSameLAN(mIPList[i], remoteIP))
        return mIPList[i];
		}
  }
  #ifdef ICE_LOOPBACK_SUPPORT
  NetworkAddress result = remoteIP.type() == AF_INET ? NetworkAddress::LoopbackAddress4 : NetworkAddress::LoopbackAddress6;
  #else
  NetworkAddress result = remoteIP;
  #endif

  // Find default interface - this operation costs, so the result must be cached
  SOCKET s = INVALID_SOCKET;
  int rescode = 0;
  sockaddr_in ipv4;
#ifdef ICE_IPV6SUPPORT
  sockaddr_in6 ipv6;
#endif
  socklen_t addrLen = 0;
  switch (remoteIP.family())
  {
  case AF_INET:
    s = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (s != INVALID_SOCKET)
    {
      rescode = ::connect(s, remoteIP.genericsockaddr(), remoteIP.sockaddrLen());
      if (rescode != -1)
      {
        addrLen = sizeof(ipv4);
        if (::getsockname(s, (sockaddr*)&ipv4, &addrLen) != -1)
          result = NetworkAddress((sockaddr&)ipv4, addrLen);
      }
      ::close(s);
    }
		break;

#ifdef ICE_IPV6SUPPORT
  case AF_INET6:
    s = ::socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
    if (s != INVALID_SOCKET)
    {
      rescode = ::connect(s, remoteIP.genericsockaddr(), remoteIP.sockaddrLen());
      if (rescode != -1)
      {
        addrLen = sizeof(ipv6);
        if (::getsockname(s, (sockaddr*)&ipv6, &addrLen) != -1)
          result = NetworkAddress((sockaddr&)ipv6, addrLen);
      }
      ::close(s);
    }
		break;
#endif

  default:

    assert(0);
  }
	
	if (result.isEmpty())
	{
		// Get first available interface
		for (unsigned i=0; i<mIPList.size() && result.isEmpty(); i++)
		{
      if (!mIPList[i].isLoopback() && mIPList[i].family() == remoteIP.family())
				result = mIPList[i];
		}
	}
  return result;
#endif
}

#if defined(TARGET_WIN) && !defined(WINDOWS_RT)
NetworkAddress NetworkHelper::ipInterfaceByIndex(int index)
{
  for (unsigned i=0; i<mIP2IndexList.size(); i++)
  {
    if (mIP2IndexList[i].mIndex == index)
      return mIP2IndexList[i].mIP;
  }

  return NetworkAddress();
}

#endif

NetworkHelper& NetworkHelper::instance()
{
  mGuard.lock();
  try
  {
    if (!mInstance)
      mInstance = new NetworkHelper();
  }
  catch(...)
  {
    ICELogError(<< "Failed to create NetworkHelper instance");
  }
  mGuard.unlock();
  
  return *mInstance;
}

void NetworkHelper::destroyInstance()
{
  delete mInstance;
}

bool NetworkHelper::hasIPv4() const
{
  // Check interface list and see if it has LAN or public IP
#if defined(TARGET_WIN) && !defined(WINDOWS_RT)
  for (const auto& item: mInterfaceList)
#else
  for (const auto& item: mIPList)
#endif
    if (item.family() == AF_INET && (item.isLAN() || item.isPublic()))
      return true;
  return false;
}

bool NetworkHelper::hasIPv6() const
{
#if defined(TARGET_WIN) && !defined(WINDOWS_RT)
  for (const auto& item : mInterfaceList)
#else
  for (const auto& item: mIPList)
#endif
    if (item.family() == AF_INET6 && (item.isLAN() || item.isPublic()))
      return true;
  return false;
}

void NetworkHelper::NetworkToHost(const in6_addr& addr6, uint32_t* output)
{
  for (int i=0; i<4; i++)
#if defined(TARGET_WIN)
    output[i] = ntohl(((uint32_t*)addr6.u.Byte[0])[i]);
#elif defined(TARGET_IOS) || defined(TARGET_OSX)
    output[i] = ntohl(addr6.__u6_addr.__u6_addr32[i]);
#elif defined(TARGET_OPENWRT) || defined(TARGET_MUSL)
      output[i] = ntohl(addr6.__in6_union.__s6_addr32[i]);
#elif defined(TARGET_LINUX)
    output[i] = ntohl(addr6.__in6_u.__u6_addr32[i]);
#elif defined(TARGET_ANDROID)
    output[i] = ntohl(addr6.in6_u.u6_addr32[i]);
#endif
}

void NetworkHelper::HostToNetwork(const uint32_t* input, in6_addr& output)
{
  for (int i=0; i<4; i++)
#if defined(TARGET_WIN)
    ((uint32_t*)&output.u.Byte[0])[i] = htonl(input[i]);
#elif defined(TARGET_OSX) || defined(TARGET_IOS)
    output.__u6_addr.__u6_addr32[i] = htonl(input[i]);
#elif defined(TARGET_OPENWRT) || defined(TARGET_MUSL)
      output.__in6_union.__s6_addr32[i] = htonl(input[i]);
#elif defined(TARGET_LINUX)
    output.__in6_u.__u6_addr32[i] = htonl(input[i]);
#elif defined(TARGET_ANDROID)
    output.in6_u.u6_addr32[i] = htonl(input[i]);
#endif
}

NetworkHelper* NetworkHelper::mInstance = NULL;
Mutex NetworkHelper::mGuard;

//-------------------------------------- INHDestroy ---------------------------------
class INHDestroy
{
public:
  INHDestroy()
  {
  }
  
  ~INHDestroy()
  {
    NetworkHelper::destroyInstance();
  }
};

INHDestroy GINHDestroyer;
