/*
 * Copyright 2012 Emmanuel Engelhart <kelson@kiwix.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU  General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 */

#include <common/networkTools.h>

std::map<std::string, std::string> kiwix::getNetworkInterfaces()
{
  std::map<std::string, std::string> interfaces;

#ifdef _WIN32
  SOCKET sd = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0);
  if (sd == SOCKET_ERROR) {
    std::cerr << "Failed to get a socket. Error " << WSAGetLastError()
              << std::endl;
    return interfaces;
  }

  INTERFACE_INFO InterfaceList[20];
  unsigned long nBytesReturned;
  if (WSAIoctl(sd,
               SIO_GET_INTERFACE_LIST,
               0,
               0,
               &InterfaceList,
               sizeof(InterfaceList),
               &nBytesReturned,
               0,
               0)
      == SOCKET_ERROR) {
    std::cerr << "Failed calling WSAIoctl: error " << WSAGetLastError()
              << std::endl;
    return interfaces;
  }

  int nNumInterfaces = nBytesReturned / sizeof(INTERFACE_INFO);
  for (int i = 0; i < nNumInterfaces; ++i) {
    sockaddr_in* pAddress;
    pAddress = (sockaddr_in*)&(InterfaceList[i].iiAddress);

    /* Add to the map */
    std::string interfaceName = std::string(inet_ntoa(pAddress->sin_addr));
    std::string interfaceIp = std::string(inet_ntoa(pAddress->sin_addr));
    interfaces.insert(
        std::pair<std::string, std::string>(interfaceName, interfaceIp));
  }
#else
  /* Get Network interfaces information */
  char buf[16384];
  struct ifconf ifconf;
  int fd = socket(PF_INET, SOCK_DGRAM, 0); /* Only IPV4 */
  ifconf.ifc_len = sizeof buf;
  ifconf.ifc_buf = buf;
  if (ioctl(fd, SIOCGIFCONF, &ifconf) != 0) {
    perror("ioctl(SIOCGIFCONF)");
    exit(EXIT_FAILURE);
  }

  /* Go through each interface */
  int i;
  size_t len;
  struct ifreq* ifreq;
  ifreq = ifconf.ifc_req;
  for (i = 0; i < ifconf.ifc_len;) {
    if (ifreq->ifr_addr.sa_family == AF_INET) {
      /* Get the network interface ip */
      char host[128] = {0};
      const int error = getnameinfo(&(ifreq->ifr_addr),
                                    sizeof ifreq->ifr_addr,
                                    host,
                                    sizeof host,
                                    0,
                                    0,
                                    NI_NUMERICHOST);
      if (!error) {
        std::string interfaceName = std::string(ifreq->ifr_name);
        std::string interfaceIp = std::string(host);
        /* Add to the map */
        interfaces.insert(
            std::pair<std::string, std::string>(interfaceName, interfaceIp));
      } else {
        perror("getnameinfo()");
      }
    }

/* some systems have ifr_addr.sa_len and adjust the length that
 * way, but not mine. weird */
#ifndef __linux__
    len = IFNAMSIZ + ifreq->ifr_addr.sa_len;
#else
    len = sizeof *ifreq;
#endif
    ifreq = (struct ifreq*)((char*)ifreq + len);
    i += len;
  }
#endif
  return interfaces;
}

std::string kiwix::getBestPublicIp()
{
  std::map<std::string, std::string> interfaces = kiwix::getNetworkInterfaces();

#ifndef _WIN32
  const char* const prioritizedNames[]
      = {"eth0", "eth1", "wlan0", "wlan1", "en0", "en1"};
  const int count = (sizeof prioritizedNames) / (sizeof prioritizedNames[0]);
  for (int i = 0; i < count; ++i) {
    std::map<std::string, std::string>::const_iterator it
        = interfaces.find(prioritizedNames[i]);
    if (it != interfaces.end()) {
      return it->second;
    }
  }
#endif

  for (std::map<std::string, std::string>::iterator iter = interfaces.begin();
       iter != interfaces.end();
       ++iter) {
    std::string interfaceIp = iter->second;
    if (interfaceIp.length() >= 7 && interfaceIp.substr(0, 7) == "192.168") {
      return interfaceIp;
    }
  }

  for (std::map<std::string, std::string>::iterator iter = interfaces.begin();
       iter != interfaces.end();
       ++iter) {
    std::string interfaceIp = iter->second;
    if (interfaceIp.length() >= 7 && interfaceIp.substr(0, 7) == "172.16.") {
      return interfaceIp;
    }
  }

  for (std::map<std::string, std::string>::iterator iter = interfaces.begin();
       iter != interfaces.end();
       ++iter) {
    std::string interfaceIp = iter->second;
    if (interfaceIp.length() >= 3 && interfaceIp.substr(0, 3) == "10.") {
      return interfaceIp;
    }
  }

  return "127.0.0.1";
}
