/*
 * libjingle
 * Copyright 2004--2005, Google Inc.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice, 
 *     this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products 
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <algorithm>
#include <cassert>
#include <cfloat>
#include <cmath>
#include <sstream>
#include <string.h>

#ifdef POSIX
extern "C" {
#include <sys/socket.h>
#include <sys/utsname.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <unistd.h>
#include <errno.h>
}
#endif // POSIX

#ifdef WIN32
#include "talk/base/win32.h"
#include <Iphlpapi.h>
#endif

#include "talk/base/host.h"
#include "talk/base/logging.h"
#include "talk/base/network.h"
#include "talk/base/socket.h" // this includes something that makes windows happy
#include "talk/base/stringencode.h"
#include "talk/base/time.h"
#include "talk/base/basicdefs.h"

namespace {

const double kAlpha = 0.5; // weight for data infinitely far in the past
const double kHalfLife = 2000; // half life of exponential decay (in ms)
const double kLog2 = 0.693147180559945309417;
const double kLambda = kLog2 / kHalfLife;

// assume so-so quality unless data says otherwise
const double kDefaultQuality = talk_base::QUALITY_FAIR;

typedef std::map<std::string,std::string> StrMap;

void BuildMap(const StrMap& map, std::string& str) {
  str.append("{");
  bool first = true;
  for (StrMap::const_iterator i = map.begin(); i != map.end(); ++i) {
    if (!first) str.append(",");
    str.append(i->first);
    str.append("=");
    str.append(i->second);
    first = false;
  }
  str.append("}");
}

void ParseCheck(std::istringstream& ist, char ch) {
  if (ist.get() != ch)
    LOG(LERROR) << "Expecting '" << ch << "'";
}

std::string ParseString(std::istringstream& ist) {
  std::string str;
  int count = 0;
  while (ist) {
    char ch = ist.peek();
    if ((count == 0) && ((ch == '=') || (ch == ',') || (ch == '}'))) {
      break;
    } else if (ch == '{') {
      count += 1;
    } else if (ch == '}') {
      count -= 1;
      if (count < 0)
        LOG(LERROR) << "mismatched '{' and '}'";
    }
    str.append(1, static_cast<char>(ist.get()));
  }
  return str;
}

void ParseMap(const std::string& str, StrMap& map) {
  if (str.size() == 0)
    return;
  std::istringstream ist(str);
  ParseCheck(ist, '{');
  for (;;) {
    std::string key = ParseString(ist);
    ParseCheck(ist, '=');
    std::string val = ParseString(ist);
    map[key] = val;
    if (ist.peek() == ',')
      ist.get();
    else
      break;
  }
  ParseCheck(ist, '}');
  if (ist.rdbuf()->in_avail() != 0)
    LOG(LERROR) << "Unexpected characters at end";
}

#if 0
const std::string TEST_MAP0_IN = "";
const std::string TEST_MAP0_OUT = "{}";
const std::string TEST_MAP1 = "{a=12345}";
const std::string TEST_MAP2 = "{a=12345,b=67890}";
const std::string TEST_MAP3 = "{a=12345,b=67890,c=13579}";
const std::string TEST_MAP4 = "{a={d=12345,e=67890}}";
const std::string TEST_MAP5 = "{a={d=12345,e=67890},b=67890}";
const std::string TEST_MAP6 = "{a=12345,b={d=12345,e=67890}}";
const std::string TEST_MAP7 = "{a=12345,b={d=12345,e=67890},c=13579}";

class MyTest {
public:
  MyTest() {
    test(TEST_MAP0_IN, TEST_MAP0_OUT);
    test(TEST_MAP1, TEST_MAP1);
    test(TEST_MAP2, TEST_MAP2);
    test(TEST_MAP3, TEST_MAP3);
    test(TEST_MAP4, TEST_MAP4);
    test(TEST_MAP5, TEST_MAP5);
    test(TEST_MAP6, TEST_MAP6);
    test(TEST_MAP7, TEST_MAP7);
  }
  void test(const std::string& input, const std::string& exp_output) {
    StrMap map;
    ParseMap(input, map);
    std::string output;
    BuildMap(map, output);
    LOG(INFO) << "  ********  " << (output == exp_output);
  }
};

static MyTest myTest;
#endif

} // namespace

namespace talk_base {

#ifdef POSIX
void NetworkManager::CreateNetworks(std::vector<Network*>& networks) {
  int fd;
  if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
    PLOG(LERROR, errno) << "socket";
    return;
  }

  struct ifconf ifc;
  ifc.ifc_len = 64 * sizeof(struct ifreq);
  ifc.ifc_buf = new char[ifc.ifc_len];

  if (ioctl(fd, SIOCGIFCONF, &ifc) < 0) {
    PLOG(LERROR, errno) << "ioctl";
    return;
  }
  assert(ifc.ifc_len < static_cast<int>(64 * sizeof(struct ifreq)));

  struct ifreq* ptr = reinterpret_cast<struct ifreq*>(ifc.ifc_buf);
  struct ifreq* end =
      reinterpret_cast<struct ifreq*>(ifc.ifc_buf + ifc.ifc_len);

  while (ptr < end) {
    if (strcmp(ptr->ifr_name, "lo")) { // Ignore the loopback device
      struct sockaddr_in* inaddr =
          reinterpret_cast<struct sockaddr_in*>(&ptr->ifr_ifru.ifru_addr);
      if (inaddr->sin_family == AF_INET) {
        uint32 ip = ntohl(inaddr->sin_addr.s_addr);
        networks.push_back(new Network(std::string(ptr->ifr_name), ip));
      }
    }
#ifdef _SIZEOF_ADDR_IFREQ
    ptr = reinterpret_cast<struct ifreq*>(
        reinterpret_cast<char*>(ptr) + _SIZEOF_ADDR_IFREQ(*ptr));
#else
    ptr++;
#endif
  }

  delete [] ifc.ifc_buf;
  close(fd);
}
#endif

#ifdef WIN32
void NetworkManager::CreateNetworks(std::vector<Network*>& networks) {
  IP_ADAPTER_INFO info_temp;
  ULONG len = 0;
  
  if (GetAdaptersInfo(&info_temp, &len) != ERROR_BUFFER_OVERFLOW)
    return;
  IP_ADAPTER_INFO *infos = new IP_ADAPTER_INFO[len];
  if (GetAdaptersInfo(infos, &len) != NO_ERROR)
    return;

  int count = 0;
  for (IP_ADAPTER_INFO *info = infos; info != NULL; info = info->Next) {
    if (info->Type == MIB_IF_TYPE_LOOPBACK)
      continue;
    if (strcmp(info->IpAddressList.IpAddress.String, "0.0.0.0") == 0)
      continue;

    // In production, don't transmit the network name because of
    // privacy concerns. Transmit a number instead.

    std::string name;
#if defined(PRODUCTION)
    std::ostringstream ost;
    ost << count;
    name = ost.str();
    count++;
#else
    name = info->Description;
#endif

    networks.push_back(new Network(name,
        SocketAddress::StringToIP(info->IpAddressList.IpAddress.String)));
  }

  delete infos;
}
#endif

void NetworkManager::GetNetworks(std::vector<Network*>& result) {
  std::vector<Network*> list;
  CreateNetworks(list);

  for (uint32 i = 0; i < list.size(); ++i) {
    NetworkMap::iterator iter = networks_.find(list[i]->name());

    Network* network;
    if (iter == networks_.end()) {
      network = list[i];
    } else {
      network = iter->second;
      network->set_ip(list[i]->ip());
      delete list[i];
    }

    networks_[network->name()] = network;
    result.push_back(network);
  }
}

std::string NetworkManager::GetState() {
  StrMap map;
  for (NetworkMap::iterator i = networks_.begin(); i != networks_.end(); ++i)
    map[i->first] = i->second->GetState();

  std::string str;
  BuildMap(map, str);
  return str;
}

void NetworkManager::SetState(std::string str) {
  StrMap map;
  ParseMap(str, map);

  for (StrMap::iterator i = map.begin(); i != map.end(); ++i) {
    std::string name = i->first;
    std::string state = i->second;

    Network* network = new Network(name, 0);
    network->SetState(state);
    networks_[name] = network;
  }
}

Network::Network(const std::string& name, uint32 ip)
  : name_(name), ip_(ip), uniform_numerator_(0), uniform_denominator_(0),
    exponential_numerator_(0), exponential_denominator_(0),
    quality_(kDefaultQuality) {

  last_data_time_ = Time();

  // TODO: seed the historical data with one data point based on the link speed
  //       metric from XP (4.0 if < 50, 3.0 otherwise).
}

void Network::StartSession(NetworkSession* session) {
  assert(std::find(sessions_.begin(), sessions_.end(), session) == sessions_.end());
  sessions_.push_back(session);
}

void Network::StopSession(NetworkSession* session) {
  SessionList::iterator iter = std::find(sessions_.begin(), sessions_.end(), session);
  if (iter != sessions_.end())
    sessions_.erase(iter);
}

void Network::EstimateQuality() {
  uint32 now = Time();

  // Add new data points for the current time.
  for (uint32 i = 0; i < sessions_.size(); ++i) {
    if (sessions_[i]->HasQuality())
      AddDataPoint(now, sessions_[i]->GetCurrentQuality());
  }

  // Construct the weighted average using both uniform and exponential weights.

  double exp_shift = exp(-kLambda * (now - last_data_time_));
  double numerator = uniform_numerator_ + exp_shift * exponential_numerator_;
  double denominator = uniform_denominator_ + exp_shift * exponential_denominator_;

  if (denominator < DBL_EPSILON)
    quality_ = kDefaultQuality;
  else
    quality_ = numerator / denominator;
}

std::string Network::ToString() const {
  std::stringstream ss;
  // Print out the first space-terminated token of the network name, plus
  // the IP address.
  ss << "Net[" << name_.substr(0, name_.find(' ')) 
     << ":" << SocketAddress::IPToString(ip_) << "]";
  return ss.str();
}

void Network::AddDataPoint(uint32 time, double quality) {
  uniform_numerator_ += kAlpha * quality;
  uniform_denominator_ += kAlpha;

  double exp_shift = exp(-kLambda * (time - last_data_time_));
  exponential_numerator_ = (1 - kAlpha) * quality + exp_shift * exponential_numerator_;
  exponential_denominator_ = (1 - kAlpha) + exp_shift * exponential_denominator_;

  last_data_time_ = time;
}

std::string Network::GetState() {
  StrMap map;
  map["lt"] = talk_base::ToString<uint32>(last_data_time_);
  map["un"] = talk_base::ToString<double>(uniform_numerator_);
  map["ud"] = talk_base::ToString<double>(uniform_denominator_);
  map["en"] = talk_base::ToString<double>(exponential_numerator_);
  map["ed"] = talk_base::ToString<double>(exponential_denominator_);

  std::string str;
  BuildMap(map, str);
  return str;
}

void Network::SetState(std::string str) {
  StrMap map;
  ParseMap(str, map);

  last_data_time_ = FromString<uint32>(map["lt"]);
  uniform_numerator_ = FromString<double>(map["un"]);
  uniform_denominator_ = FromString<double>(map["ud"]);
  exponential_numerator_ = FromString<double>(map["en"]);
  exponential_denominator_ = FromString<double>(map["ed"]);
}

} // namespace talk_base
