#define SOCK_DEBUG
#include <socket.hh>
#include <iostream>
#include <sstream>

using namespace std;
using sw::sock::ip_address;

#ifdef OS_WIN
sw::sock::winsock_initializer winit;
#endif

// <editor-fold desc="check_assign" defaultstate="collapsed">
int check_assign(string s, bool expect_error)
{
  std::stringstream ss;
  ss << "INPUT STRING  = " << s << endl;
  ip_address adr(s);
  ss << "valid()       = " << adr.valid() << endl;
  ss << "str()         = " << adr.str() << endl;
  ss << "family()      = " << adr.family() << endl;
  ss << "is_v4()       = " << adr.is_v4() << endl;
  ss << "is_v6()       = " << adr.is_v6() << endl;
  ss << "port()        = " << adr.port() << endl;
  ss << "subnet_mask() = " << adr.subnet_mask() << endl;
  if(adr.valid() == expect_error) {
    ss << "!!! UNEXPECTED RESULT" << endl;
  }
  ss << endl;
  if(adr.valid() == expect_error) {
    cerr << ss.str();
    return 1;
  }
  return 0;
}

int check_assign_all()
{
  int nerr = 0;
  nerr += check_assign("", true);
  nerr += check_assign("::", false);
  nerr += check_assign("[::]", false);
  nerr += check_assign("[::/64]", false);
  nerr += check_assign("[::/64]:9999", false);
  nerr += check_assign("::1", false);
  nerr += check_assign("[::1]", false);
  nerr += check_assign("[::1/64]", false);
  nerr += check_assign("[::1/64]:9999", false);
  nerr += check_assign("a::bcd0", false);
  nerr += check_assign("[a::bcd0]", false);
  nerr += check_assign("[a::bcd0/64]", false);
  nerr += check_assign("a::bcd0/64", false);
  nerr += check_assign("[a::bcd0]:9999", false);
  nerr += check_assign("[a::bcd0]:9", false);
  nerr += check_assign("192.168.0.1", false);
  nerr += check_assign("192.168.0.1/8", false);
  nerr += check_assign("192.168.0.1/8:9999", false);
  nerr += check_assign("192.168.0.1:9999", false);
  nerr += check_assign("[192.168.0.1]", false);
  nerr += check_assign("[192.168.0.1/24]", false);
  nerr += check_assign("[192.168.0.124]:100", false);
  nerr += check_assign("rewkll", true);
  nerr += check_assign("[]", true);
  nerr += check_assign("192.168..1", true);
  nerr += check_assign("320.168.0.1", true);
  nerr += check_assign("192.168.0.1:abc", true);
  nerr += check_assign("192.168.0.1/abs:abc", true);
  nerr += check_assign("192.168.0.1/abs:123", true);
  nerr += check_assign("192.168.0.a/8:999", true);
  nerr += check_assign(":", true);
  nerr += check_assign("[:]", true);
  nerr += check_assign(":::", true);
  nerr += check_assign("a::Xcd0", true);
  nerr += check_assign("a:b:bcd0", true);
  nerr += check_assign("[a::bcd0", true);
  nerr += check_assign("a::bcd0]", true);
  nerr += check_assign("[a::bcd0", true);
  nerr += check_assign("[a::bcd0/128:10]:10", true);
  nerr += check_assign("[a::bcd0/abc]", true);
  cerr << "check_assign_all() number of fails: " << nerr << endl;
  return nerr;
}
// </editor-fold>

// <editor-fold desc="check_sockaddr_in6" defaultstate="collapsed">
int check_sockaddr_in6(string inp_addr_str)
{
  struct sockaddr_in6 addr6;
  memset(&addr6, 0, sizeof(struct sockaddr_in6));
  addr6.sin6_family = AF_INET6;
  if(::inet_pton(AF_INET6, inp_addr_str.c_str(), &(addr6.sin6_addr)) != 1) {
    cerr << "!!! TEST INPUT ADDRESS INVALID" << endl;
    return 1;
  }
  string backcheck_str;
  do {
    char s[INET6_ADDRSTRLEN+2]; s[sizeof(s)-1] = '\0';
    if(!::inet_ntop(AF_INET6, &(addr6.sin6_addr), s, INET6_ADDRSTRLEN)) {
      cerr << "!!! inet_ntop FAILED" << endl;
      return 1;
    }
    backcheck_str = s;
  } while(0);

  ip_address adr(addr6);
  if(!adr.valid()) {
    cerr << "!!! Constructor assignment failed" << endl;
    return 1;
  } else if(backcheck_str != adr.str()) {
    cerr << "!!! Constructor assignment: backcheck_str != adr.str(): '" << backcheck_str << "' != " << adr.str() << endl;
    return 1;
  }

  adr.clear();
  adr = addr6;
  if(!adr.valid()) {
    cerr << "!!! Operator assignment failed" << endl;
    return 1;
  } else if(backcheck_str != adr.str()) {
    cerr << "!!! Operator assignment: backcheck_str != adr.str(): '" << backcheck_str << "' != " << adr.str() << endl;
    return 1;
  }
  return 0;

}
int check_sockaddr_in6_all()
{
  int nerr = 0;
  nerr += check_sockaddr_in6("::1");
  cerr << "check_sockaddr_in6_all() number of fails: " << nerr << endl;
  return nerr;
}
// </editor-fold>

// <editor-fold desc="check_sockaddr_in" defaultstate="collapsed">
int check_sockaddr_in4(string inp_addr_str)
{
  struct sockaddr_in addr4;
  memset(&addr4, 0, sizeof(struct sockaddr_in));
  addr4.sin_family = AF_INET;
  if(::inet_pton(AF_INET, inp_addr_str.c_str(), &(addr4.sin_addr)) != 1) {
    cerr << "!!! TEST INPUT ADDRESS INVALID" << endl;
    return 1;
  }
  string backcheck_str;
  do {
    char s[INET6_ADDRSTRLEN+2]; s[sizeof(s)-1] = '\0';
    if(!::inet_ntop(AF_INET, &(addr4.sin_addr), s, INET6_ADDRSTRLEN)) {
      cerr << "!!! inet_ntop FAILED" << endl;
      return 1;
    }
    backcheck_str = s;
  } while(0);

  ip_address adr(addr4);
  if(!adr.valid()) {
    cerr << "!!! Constructor assignment failed" << endl;
    return 1;
  } else if(backcheck_str != adr.str()) {
    cerr << "!!! Constructor assignment: backcheck_str != adr.str(): '" << backcheck_str << "' != " << adr.str() << endl;
    return 1;
  }

  adr.clear();
  adr = addr4;
  if(!adr.valid()) {
    cerr << "!!! Operator assignment failed" << endl;
    return 1;
  } else if(backcheck_str != adr.str()) {
    cerr << "!!! Operator assignment: backcheck_str != adr.str(): '" << backcheck_str << "' != " << adr.str() << endl;
    return 1;
  }
  return 0;

}
int check_sockaddr_in4_all()
{
  int nerr = 0;
  nerr += check_sockaddr_in4("127.0.0.1");
  nerr += check_sockaddr_in4("192.168.0.1");
  nerr += check_sockaddr_in4("10.8.1.1");
  cerr << "check_sockaddr_in4_all() number of fails: " << nerr << endl;
  return nerr;
}
// </editor-fold>

// <editor-fold desc="check_parse_resource_address" defaultstate="collapsed">
int check_parse_resource_address(string i_service, string i_user, string i_host, string i_port,
        string i_path, enum sw::sock::ip_address::family_sel family, bool expect_valid)
{
  if(i_host.empty()) { cerr << "check_parse_resource_address(): host must be set"; return 1; }
  string i_resaddr;
  if(!i_service.empty()) i_resaddr += i_service + "://";
  if(!i_user.empty()) i_resaddr += i_user + "@";
  i_resaddr += i_host;
  if(!i_port.empty()) i_resaddr += string(":") + i_port;
  if(!i_path.empty()) i_resaddr += i_path;

  ip_address o_ip;
  string o_service, o_user, o_host, o_path;
  bool valid = (!ip_address::parse_resource_address(
    i_resaddr, o_ip, o_service, o_user, o_host, o_path, true, family, ip_address::type_tcp
  ));
  if(valid != expect_valid || o_ip.valid() != expect_valid) {
    cerr << "check_parse_resource_address(->" << i_resaddr << "): " << endl
         << "o_service    = " << o_service << endl
         << "o_user       = "  << o_user << endl
         << "o_host       = " << o_host << endl
         << "o_path       = " << o_path << endl
         << "o_ip.str()   = " << o_ip.str() << endl
         << "o_ip.port()  = " << o_ip.port() << endl
         << "o_ip.is_v4() = " << o_ip.is_v4() << endl
         << "o_ip.is_v6() = " << o_ip.is_v6() << endl
         << "o_ip.family()= " << o_ip.family() << endl
         << "o_ip.size()  = " << o_ip.size() << endl
         << endl;
    return 1;
  }
  return 0;
}

int check_parse_resource_address_all()
{
  int nerr = 0;
  const enum ip_address::family_sel V4 = ip_address::family_ipv4;
  const enum ip_address::family_sel V6 = ip_address::family_ipv6;
  const enum ip_address::family_sel VA = ip_address::family_any;
  nerr += check_parse_resource_address("ftp", "stfwi", "[::1]", "", "/home/me", V6, true);
  nerr += check_parse_resource_address("", "", "127.0.0.1", "21", "", VA, true);
  nerr += check_parse_resource_address("http", "", "localhost", "80", "/home/me", V4, true);
  cerr << "check_parse_resource_address_all() number of fails: " << nerr << endl;
  return nerr;
}
// </editor-fold>

// <editor-fold desc="check_service_lookup_all" defaultstate="collapsed">
int check_service_lookup_all()
{
  int nerr = 0;
  #define chk(X,Y) { \
    string srv = ip_address::service_by_port(X); \
    if(srv != Y) { \
      ++nerr; cerr<<"ip_address::service_by_port('"<<(X)<<"') == "<<srv<<" != "<<(Y)<<endl; \
    }\
    ip_address::port_type port = ip_address::port_by_service(Y);\
    if(port != (X)) { \
      ++nerr; cerr<<"ip_address::port_by_service('"<<(Y)<<"') == "<<port<<" != "<<(X)<<endl; \
    } \
  }
  chk(21, "ftp");
  chk(22, "ssh");
  chk(23, "telnet");
  chk(25, "smtp");
  chk(80, "http");
  chk(443, "https");
  chk(990, "ftps");
  chk(993, "imaps");
  #undef chk
  cerr << "check_service_lookup_all() number of fails: " << nerr << endl;
  return nerr;
}
// </editor-fold>

// <editor-fold desc="check_my_hostname" defaultstate="collapsed">
int check_my_hostname()
{
  int nerr = 0;
  string host = ip_address::my_hostname();
  if(host.empty()) {
    ++nerr; cerr << "ip_address::my_hostname() is empty" << endl;
  }
  cerr << "check_my_hostname() number of fails: " << nerr << endl;
  return nerr;
}
// </editor-fold>

// <editor-fold desc="hostname_by_address" defaultstate="collapsed">
int check_hostname_by_address()
{
  #define chk(X) { \
    ip_address::hostlookup_list_t hl = ip_address::host_lookup(X, "", ip_address::family_any, \
      ip_address::type_any); \
    if(!hl.empty()) { \
      string adr = hl.front().saddr; \
      string hst = hl.front().shost; \
      ip_address ip(adr); \
      string host = ip_address::hostname_by_address(ip); \
      if(host != hst) {  \
        ++nerr; cerr << "ip_address::hostname_by_address("<<adr<<") == "<<host<<" != "<<hst<<endl; \
        cerr << "     where ip_address = " << ip.str() << endl; \
      } \
    } \
  }
  int nerr = 0;


  //chk("::1");
  //chk("www.atwillys.de");

  #undef chk
  cerr << "hostname_by_address() number of fails: " << nerr << endl;
  return nerr;
}
// </editor-fold>

// <editor-fold desc="main" defaultstate="collapsed">
int main(int argc, char** argv)
{
  (void) argc; (void) argv;
  int nerr = 0;
  nerr += check_assign_all();
  nerr += check_sockaddr_in6_all();
  nerr += check_sockaddr_in4_all();
  nerr += check_parse_resource_address_all();
  nerr += check_service_lookup_all();
  nerr += check_my_hostname();
  nerr += check_hostname_by_address();
  cout << endl << "TOTAL NUMBER OF UNEXPECTED RESULTS: " << nerr << endl;
  return nerr;
}
// </editor-fold>
