#include <glib.h>
#ifdef G_OS_WIN32
#include <ws2tcpip.h>
#else
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#define WSAGetLastError() errno
#define WSAEWOULDBLOCK EWOULDBLOCK
#endif
#include <glib/gstdio.h>
#include <string.h>
#include <stdlib.h>

#undef gai_strerror
#define gai_strerror(i) gai_strerror_does_not_link (i)

static GMainLoop *loop;

static const char*
gai_strerror_does_not_link (int errval)
{
    static char buf[32];
    g_snprintf(buf, sizeof(buf), "Winsock error %d", errval);
    return buf;
}

static GIOChannel *ch=NULL;
static guint tag=0;
enum state_e {
    start,
    list,
    read,
    done
};
static guint state = start;

static gboolean io_func(GIOChannel *lch, GIOCondition cond, gpointer data);

static gboolean addwritewatch(gpointer d)
{
    g_print("add write watch\n");
    tag=g_io_add_watch(ch, G_IO_OUT | G_IO_ERR | G_IO_NVAL, io_func, NULL);
    return FALSE;
}

static gboolean addreadwatch(gpointer d)
{
    g_print("add read watch\n");
    tag=g_io_add_watch(ch, G_IO_IN | G_IO_ERR | G_IO_NVAL, io_func, NULL);
    return FALSE;
}

static gboolean io_func(GIOChannel *lch, GIOCondition cond, gpointer data)
{
    static int wc=0,rc=0;
    gboolean ret=TRUE;
    
    g_print("io_func: %#x: ", cond);
    if(cond & G_IO_IN)
    {
        GError *err=NULL;
        GString *s=g_string_new("");
        
	g_print("read %d\n",++rc);
        switch (g_io_channel_read_line_string(lch, s, NULL,  &err))
        {
            case G_IO_STATUS_AGAIN:
                g_print("again\n");
                break;

            case G_IO_STATUS_ERROR:
            case G_IO_STATUS_EOF:
                g_print("error\n");
                if (err != NULL)
                {
                    g_print("msg: %s\n",err->message);
                    g_error_free(err);
                }
		ret=FALSE;
		state=done;
                break;
    
            case G_IO_STATUS_NORMAL:
                g_print("line: %s",s->str);
                if(state==start)
                {
                    //g_source_remove(tag);
                    ret=FALSE;
                    state=list;
                    g_idle_add(addwritewatch,NULL);
                }
                if(state==read && s->str[0]=='.' && s->str[1]=='\r')
                {
		    g_print("it finished.\n");
                    state=done;
		    g_main_loop_quit(loop);
                    ret=FALSE;
                }
		break;
        }
        g_string_free(s,TRUE);
    }
    if(cond & G_IO_OUT)
    {
        g_print("write %d\n",++wc);
        if(state==list)
        {
            guint stat;
            stat=g_io_channel_write_chars(lch,"LIST\r\n",6,NULL,NULL);
            g_print("status %d\n",stat);
            g_io_channel_flush(lch,NULL);
            //g_source_remove(tag);
            ret=FALSE;
            state=read;
            g_idle_add(addreadwatch,NULL);
        }
    }
    if(cond & G_IO_ERR)
    {
        g_print("error\n");
    }
    if(cond & G_IO_HUP)
    {
        g_print("hup\n");
    }
    if(cond & G_IO_NVAL)
    {
        g_print("invalid\n");
    }
    if(!ret)
        g_print("removing watch\n");
    fflush(stdout);
    return ret;
}

static gboolean idle_start(gpointer data)
{
    int sockfd=GPOINTER_TO_INT(data);
    
#ifdef G_OS_WIN32
    ch = g_io_channel_win32_new_socket(sockfd);
#else
    ch = g_io_channel_unix_new(sockfd);
    g_io_channel_set_flags(ch, G_IO_FLAG_NONBLOCK, NULL);
#endif
    if (g_io_channel_get_encoding(ch) != NULL)
        g_io_channel_set_encoding(ch, NULL, NULL);
    g_io_channel_set_buffered(ch, TRUE);
    g_io_channel_set_line_term(ch, "\r\n", 2);
    
    addreadwatch(ch);
    
    return FALSE;
}

int main(int argc, char **argv)
{
#ifdef G_OS_WIN32
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 0), &wsaData);
#endif
    int err;
    int sockfd;
    char hostbuf[128];
    char portbuf[32]="119";
    struct addrinfo hints, *ans;
    GList * l=NULL, *list=NULL, *ipv4_list=NULL, *ipv6_list=NULL;

	if (argc != 2)
	  fprintf (stderr, "Usage: %s hostname\n", g_path_get_basename (argv[0])), exit (1);

    strcpy(hostbuf,argv[1]);

    /* get an addrinfo for the host */
    //g_snprintf (hostbuf, sizeof(hostbuf), "%*.*s", host->len, host->len, host->str);
    //g_snprintf (portbuf, sizeof(portbuf), "%d", port);
    memset (&hints, 0, sizeof(struct addrinfo));
    hints.ai_flags = 0;
    hints.ai_family = 0;
    hints.ai_socktype = SOCK_STREAM;
    err = getaddrinfo (hostbuf, portbuf, &hints, &ans);
    if (err != 0)
    {
        g_message("Lookup error: %s", gai_strerror(err));
        return 0;
    }

    /* build lists of ipv6 and ipv4 addrinfos */
    {
        struct addrinfo *answalk;
        for (answalk=ans; answalk!=NULL; answalk=answalk->ai_next)
        {
            if (answalk->ai_family == PF_INET)
                ipv4_list = g_list_append(ipv4_list, answalk);
            else if (answalk->ai_family == PF_INET6)
                ipv6_list = g_list_append(ipv6_list, answalk);
        }
    }

    /* merge the lists, putting ipv6 addrinfos before the ipv4 */
    list = NULL;
    list = g_list_concat(list, ipv6_list);
    list = g_list_concat(list, ipv4_list);
    ipv4_list = ipv6_list = NULL;

    /* walk through the merged list until we get a good socket */
    sockfd = -1;
    for (l=list; l!=NULL; l=l->next)
    {
        struct addrinfo * ainfo = (struct addrinfo*) l->data;
        sockfd = socket(ainfo->ai_family,
                         ainfo->ai_socktype,
                         ainfo->ai_protocol);
        if (sockfd >= 0)
            break;
    }
    g_list_free(list);
    if (sockfd < 0)
    {
        g_message("couldn't create a socket.");
        freeaddrinfo(ans);
        return 0;
    }

    loop=g_main_loop_new(NULL,FALSE);
    
    /* try to connect to the host */
    if ((connect(sockfd, ans->ai_addr, ans->ai_addrlen) < 0) &&
      WSAGetLastError() != WSAEWOULDBLOCK)
    {
        g_message("Connect failed: %s", g_strerror(errno));
        freeaddrinfo(ans);
        return 0;
    }
    freeaddrinfo(ans);
    
    idle_start(GINT_TO_POINTER(sockfd));
    g_main_loop_run(loop);

    return 0;
}
