/*
 * Document-method: getaddrinfo
 * call-seq: Socket.getaddrinfo(host, service, family=nil, socktype=nil, protocol=nil, flags=nil) => addrinfo
 *
 * Return address information for +host+ and +port+. The remaining arguments
 * are hints that limit the address information returned.
 *
 * This method corresponds closely to the POSIX.1g getaddrinfo() definition.
 *
 * === Parameters
 * - +host+ is a host name or an address string (dotted decimal for IPv4, or a hex string
 *   for IPv6) for which to return information. A nil is also allowed, its meaning
 *   depends on +flags+, see below.
 * - +service+ is a service name ("http", "ssh", ...), or 
 *   a port number (80, 22, ...), see Socket.getservbyname for more
 *   information. A nil is also allowed, meaning zero.
 * - +family+ limits the output to a specific address family, one of the
 *   Socket::AF_* constants. Socket::AF_INET (IPv4) and Socket::AF_INET6 (IPv6)
 *   are the most commonly used families. You will usually pass either nil or
 *   Socket::AF_UNSPEC, allowing the IPv6 information to be returned first if
 *   +host+ is reachable via IPv6, and IPv4 information otherwise.  The two
 *   strings "AF_INET" or "AF_INET6" are also allowed, they are converted to
 *   their respective Socket::AF_* constants.
 * - +socktype+ limits the output to a specific type of socket, one of the
 *   Socket::SOCK_* constants. Socket::SOCK_STREAM (for TCP) and
 *   Socket::SOCK_DGRAM (for UDP) are the most commonly used socket types. If
 *   nil, then information for all types of sockets supported by +service+ will
 *   be returned. You will usually know what type of socket you intend to
 *   create, and should pass that socket type in.
 * - +protocol+ limits the output to a specific protocol numpber, one of the
 *   Socket::IPPROTO_* constants. It is usually implied by the socket type
 *   (Socket::SOCK_STREAM => Socket::IPPROTO_TCP, ...), if you pass other than
 *   nil you already know what this is for.
 * - +flags+ is one of the Socket::AI_* constants. They mean:
 *   - Socket::AI_PASSIVE: when set, if +host+ is nil the 'any' address will be
 *     returned, Socket::INADDR_ANY or 0 for IPv4, "0::0" or "::" for IPv6.  This
 *     address is suitable for use by servers that will bind their socket and do
 *     a passive listen, thus the name of the flag. Otherwise the local or
 *     loopback address will be returned, this is "127.0.0.1" for IPv4 and "::1'
 *     for IPv6.
 *   - ...
 *
 *
 * === Returns
 *
 * Returns an array of arrays, where each subarray contains:
 * - address family, a string like "AF_INET" or "AF_INET6"
 * - port number, the port number for +service+
 * - host name, either a canonical name for +host+, or it's address in presentation
 *   format if the address could not be looked up.
 * - host IP, the address of +host+ in presentation format
 * - address family, as a numeric value (one of the Socket::AF_* constants).
 * - socket type, as a numeric value (one of the Socket::SOCK_* constants).
 * - protocol number, as a numeric value (one of the Socket::IPPROTO_* constants).
 *
 * The first four values are identical to what is commonly returned as an
 * address array, see IPSocket for more information.
 *
 * === Examples
 *
 * Not all input combinations are valid, and while there are many combinations,
 * only a few cases are common.
 *
 * A typical client will call getaddrinfo with the +host+ and +service+ it
 * wants to connect to. It knows that it will attempt to connect with either
 * TCP or UDP, and specifies +socktype+ accordingly. It loops through all
 * returned addresses, and try to connect to them in turn:
 *
 *   addrinfo = Socket::getaddrinfo('www.example.com', 'www', nil, Socket::SOCK_STREAM)
 *   addrinfo.each do |af, port, name, addr|
 *     begin
 *       sock = TCPSocket.new(addr, port)
 *       # ...
 *       exit 1
 *     rescue
 *     end
 *   end
 *
 * With UDP you don't know if connect suceeded, but if communication fails,
 * the next address can be tried.
 *
 * A typical server will call getaddrinfo with a +host+ of nil, the +service+
 * it listens to, and a +flags+ of Socket::AI_PASSIVE. It will listen for
 * connections on the first returned address:
 *   addrinfo = Socket::getaddrinfo(nil, 'www', nil, Socket::SOCK_STREAM, nil, Socket::AI_PASSIVE)
 *   af, port, name, addr = addrinfo.first
 *   sock = TCPServer(addr, port)
 *   while( client = s.accept )
 *     # ...
 *   end
 */
static VALUE
sock_s_getaddrinfo(argc, argv)
    int argc;
    VALUE *argv;
{
    VALUE host, port, family, socktype, protocol, flags, ret;
    char hbuf[1024], pbuf[1024];
    char *hptr, *pptr, *ap;
    struct addrinfo hints, *res;
    int error;

    host = port = family = socktype = protocol = flags = Qnil;
    rb_scan_args(argc, argv, "24", &host, &port, &family, &socktype, &protocol, &flags);
    if (NIL_P(host)) {
        hptr = NULL;
    }
    else {
        strncpy(hbuf, StringValuePtr(host), sizeof(hbuf));
        hbuf[sizeof(hbuf) - 1] = '\0';
        hptr = hbuf;
    }
    if (NIL_P(port)) {
        pptr = NULL;
    }
    else if (FIXNUM_P(port)) {
        snprintf(pbuf, sizeof(pbuf), "%ld", FIX2LONG(port));
        pptr = pbuf;
    }
    else {
        strncpy(pbuf, StringValuePtr(port), sizeof(pbuf));
        pbuf[sizeof(pbuf) - 1] = '\0';
        pptr = pbuf;
    }

    MEMZERO(&hints, struct addrinfo, 1);
    if (NIL_P(family)) {
        hints.ai_family = PF_UNSPEC;
    }
    else if (FIXNUM_P(family)) {
        hints.ai_family = FIX2INT(family);
    }
    else if ((ap = StringValuePtr(family)) != 0) {
        if (strcmp(ap, "AF_INET") == 0) {
            hints.ai_family = PF_INET;
        }
#ifdef INET6
        else if (strcmp(ap, "AF_INET6") == 0) {
            hints.ai_family = PF_INET6;
        }
#endif
    }

    if (!NIL_P(socktype)) {
        hints.ai_socktype = NUM2INT(socktype);
    }
    if (!NIL_P(protocol)) {
        hints.ai_protocol = NUM2INT(protocol);
    }
    if (!NIL_P(flags)) {
        hints.ai_flags = NUM2INT(flags);
    }
    error = getaddrinfo(hptr, pptr, &hints, &res);
    if (error) {
        rb_raise(rb_eSocket, "getaddrinfo: %s", gai_strerror(error));
    }

    ret = make_addrinfo(res);
    freeaddrinfo(res);
    return ret;
}