#include "SocketHelpers.h"
#include <ICMPSocket.h>

uint8_t* arduino::MbedSocketClass::macAddress(uint8_t* mac) {
  const char* mac_str = getNetwork()->get_mac_address();
  for (int b = 0; b < 6; b++) {
    uint32_t tmp;
    sscanf(&mac_str[b * 2 + (b)], "%02x", (unsigned int*)&tmp);
    mac[b] = (uint8_t)tmp;
  }
  return mac;
}

String arduino::MbedSocketClass::macAddress() {
  const char* mac_str = getNetwork()->get_mac_address();
  if (!mac_str) {
    return String("ff:ff:ff:ff:ff:ff");
  }
  return String(mac_str);
}

int arduino::MbedSocketClass::setHostname(const char* hostname) {
  NetworkInterface* interface = getNetwork();
  interface->set_hostname(hostname);
  return 1;
}

int arduino::MbedSocketClass::hostByName(const char* aHostname, IPAddress& aResult) {
  SocketAddress socketAddress = SocketAddress();
  nsapi_error_t returnCode = gethostbyname(getNetwork(), aHostname, &socketAddress);
  nsapi_addr_t address = socketAddress.get_addr();
  aResult[0] = address.bytes[0];
  aResult[1] = address.bytes[1];
  aResult[2] = address.bytes[2];
  aResult[3] = address.bytes[3];
  return returnCode == NSAPI_ERROR_OK ? 1 : 0;
}

arduino::IPAddress arduino::MbedSocketClass::localIP() {
  SocketAddress ip;
  NetworkInterface* interface = getNetwork();
  interface->get_ip_address(&ip);
  return ipAddressFromSocketAddress(ip);
}

arduino::IPAddress arduino::MbedSocketClass::subnetMask() {
  SocketAddress ip;
  NetworkInterface* interface = getNetwork();
  interface->get_netmask(&ip);
  return ipAddressFromSocketAddress(ip);
}

arduino::IPAddress arduino::MbedSocketClass::gatewayIP() {
  SocketAddress ip;
  NetworkInterface* interface = getNetwork();
  interface->get_gateway(&ip);
  return ipAddressFromSocketAddress(ip);
}

arduino::IPAddress arduino::MbedSocketClass::dnsServerIP() {
  SocketAddress ip;
  NetworkInterface* interface = getNetwork();
  char _if_name[5] {};
  interface->get_interface_name(_if_name);
  interface->get_dns_server(0, &ip, _if_name);
  return ipAddressFromSocketAddress(ip);
}

arduino::IPAddress arduino::MbedSocketClass::dnsIP(int n) {
  SocketAddress ip;
  NetworkInterface* interface = getNetwork();
  char _if_name[5] {};
  interface->get_interface_name(_if_name);
  interface->get_dns_server(n, &ip, _if_name);
  return ipAddressFromSocketAddress(ip);
}

int arduino::MbedSocketClass::ping(const char *hostname, uint8_t ttl)
{
  SocketAddress socketAddress;
  gethostbyname(getNetwork(),hostname, &socketAddress);
  return ping(socketAddress, ttl);
}

int arduino::MbedSocketClass::ping(const String &hostname, uint8_t ttl)
{
  return ping(hostname.c_str(), ttl);
}

int arduino::MbedSocketClass::ping(IPAddress host, uint8_t ttl)
{
  SocketAddress socketAddress = socketAddressFromIpAddress(host, 0);
  return ping(socketAddress, ttl);
}

void arduino::MbedSocketClass::config(arduino::IPAddress local_ip) {
  IPAddress dns = local_ip;
  dns[3] = 1;
  config(local_ip, dns);
}

void arduino::MbedSocketClass::config(const char* local_ip) {
  _ip = SocketAddress(local_ip);
}

void arduino::MbedSocketClass::config(IPAddress local_ip, IPAddress dns_server) {
  IPAddress gw = local_ip;
  gw[3] = 1;
  config(local_ip, dns_server, gw);
}

void arduino::MbedSocketClass::config(IPAddress local_ip, IPAddress dns_server, IPAddress gateway) {
  IPAddress nm(255, 255, 255, 0);
  config(local_ip, dns_server, gateway, nm);
}

void arduino::MbedSocketClass::config(IPAddress local_ip, IPAddress dns_server, IPAddress gateway, IPAddress subnet) {
  _useStaticIP = (local_ip != INADDR_NONE);
  if (!_useStaticIP)
    return;
  nsapi_addr_t convertedIP = { NSAPI_IPv4, { local_ip[0], local_ip[1], local_ip[2], local_ip[3] } };
  _ip = SocketAddress(convertedIP);
  nsapi_addr_t convertedGatewayIP = { NSAPI_IPv4, { gateway[0], gateway[1], gateway[2], gateway[3] } };
  _gateway = SocketAddress(convertedGatewayIP);
  nsapi_addr_t convertedSubnetMask = { NSAPI_IPv4, { subnet[0], subnet[1], subnet[2], subnet[3] } };
  _netmask = SocketAddress(convertedSubnetMask);
  setDNS(dns_server);
}

void arduino::MbedSocketClass::setDNS(IPAddress dns_server1) {
  nsapi_addr_t convertedDNSServer = { NSAPI_IPv4, { dns_server1[0], dns_server1[1], dns_server1[2], dns_server1[3] } };
  _dnsServer1 = SocketAddress(convertedDNSServer);
}

void arduino::MbedSocketClass::setDNS(IPAddress dns_server1, IPAddress dns_server2) {
  setDNS(dns_server1);
  nsapi_addr_t convertedDNSServer2 = { NSAPI_IPv4, { dns_server2[0], dns_server2[1], dns_server2[2], dns_server2[3] } };
  _dnsServer2 = SocketAddress(convertedDNSServer2);
}

int arduino::MbedSocketClass::ping(SocketAddress &socketAddress, uint8_t ttl, uint32_t timeout)
{
  /* ttl is not supported by mbed ICMPSocket. Default value used is 255 */
  (void)ttl;
  ICMPSocket s;
  s.set_timeout(timeout);
  s.open(getNetwork());
  int response = s.ping(socketAddress, timeout);
  s.close();

  return response;
}

arduino::IPAddress arduino::MbedSocketClass::ipAddressFromSocketAddress(SocketAddress socketAddress) {
  nsapi_addr_t address = socketAddress.get_addr();
  return IPAddress(address.bytes[0], address.bytes[1], address.bytes[2], address.bytes[3]);
}

SocketAddress arduino::MbedSocketClass::socketAddressFromIpAddress(arduino::IPAddress ip, uint16_t port) {
  nsapi_addr_t convertedIP = { NSAPI_IPv4, { ip[0], ip[1], ip[2], ip[3] } };
  return SocketAddress(convertedIP, port);
}

nsapi_error_t arduino::MbedSocketClass::gethostbyname(NetworkInterface* interface, const char* aHostname, SocketAddress* socketAddress) {
  char ifname[5] {};
  interface->get_interface_name(ifname);
  nsapi_version_t version = NSAPI_IPv4;
#if MBED_CONF_LWIP_IPV6_ENABLED
  version = NSAPI_UNSPEC;
#endif
  return interface->gethostbyname(aHostname, socketAddress, version, ifname);
}

// Download helper

#include "utility/http_request.h"
#include "utility/https_request.h"

void MbedSocketClass::setFeedWatchdogFunc(voidFuncPtr func) {
  _feed_watchdog_func = func;
}

void MbedSocketClass::feedWatchdog() {
  if (_feed_watchdog_func)
    _feed_watchdog_func();
}

void MbedSocketClass::body_callback(const char* data, uint32_t data_len) {
  feedWatchdog();
  fwrite(data, sizeof(data[0]), data_len, download_target);
}

int MbedSocketClass::download(const char* url, const char* target_file, bool const is_https) {
  download_target = fopen(target_file, "wb");

  int res = this->download(url, is_https, mbed::callback(this, &MbedSocketClass::body_callback));

  fclose(download_target);
  download_target = nullptr;

  return res;
}

int MbedSocketClass::download(const char* url, bool const is_https, mbed::Callback<void(const char*, uint32_t)> cbk) {
  if(cbk == nullptr) {
    return 0; // a call back must be set
  }

  HttpRequest* req_http = nullptr;
  HttpsRequest* req_https = nullptr;
  HttpResponse* rsp = nullptr;
  int res=0;
  std::vector<string*> header_fields;

  if (is_https) {
    req_https = new HttpsRequest(getNetwork(), nullptr, HTTP_GET, url, cbk);
    rsp = req_https->send(NULL, 0);
    if (rsp == NULL) {
      res = req_https->get_error();
      goto exit;
    }
  } else {
    req_http = new HttpRequest(getNetwork(), HTTP_GET, url, cbk);
    rsp = req_http->send(NULL, 0);
    if (rsp == NULL) {
      res = req_http->get_error();
      goto exit;
    }
  }

  while (!rsp->is_message_complete()) {
    delay(10);
  }

  // find the header containing the "Content-Length" value and return that
  header_fields = rsp->get_headers_fields();
  for(int i=0; i<header_fields.size(); i++) {

    if(strcmp(header_fields[i]->c_str(), "Content-Length") == 0) {
      res = std::stoi(*rsp->get_headers_values()[i]);
      break;
    }
  }

exit:
  if(req_http)  delete req_http;
  if(req_https) delete req_https;
  // no need to delete rsp, it is already deleted by deleting the request
  // this may be harmful since it can allow dangling pointers existence

  return res;
}