#line 26 "ag-gopher.c-nw"
#include <config.h>
#include <fcntl.h>
#include <w3a.h>
#include <tcp.h>
#include <str.h>
#include <url.h>
#include "gopher2mime.e"

#define min(a, b) ((a) < (b) ? (a) : (b))

#ifndef howmany
#define howmany(x,y)  (((x)+((y)-1))/(y))
#endif
#define NRBITS (sizeof(int) * 8)                /* 8 bits per byte */


/*
 * FD_SETSIZE is the maximum number of file descriptors per process
 */
typedef struct {
    W3ADocumentInfo doc;
    char *request;                              /* The Gopher request */
    int offset, len;                            /* Of request */
    Bool is_menu;                               /* TRUE if Gopher directory */
    FILE *f;                                    /* The socket */
    char buf[BUFSIZ];                           /* For synthesized HTML */
    int bufstart, buflen;                       /* Of buf */
    Bool eod;                                   /* End of data */
    Bool non_blocking_io;                       /* O_NONBLOCK flag used? */
} ConnInfo;

static ConnInfo *conn_info[FD_SETSIZE];


EXPORT Bool initGopher(char ***protocols, int *nrprotocols)
{
    static char *protos[] = {"gopher"};

    *protocols = protos;
    *nrprotocols = 1;
    return TRUE;
}
#line 89 "ag-gopher.c-nw"
EXPORT int openGopher(const char *url, int method, int flags,
                      const char *referer)
{
    URI uri;
    char type;
    char *selector, *mime;
    char *port, *query;
    int s;
    char *request;
    size_t len;
    FILE *f;

    /* Analyze the URL */
    if (! URL_parse(url, &uri)
        || uri.scheme != str2strip("gopher")
        || ! URL_parse_selector(strip2str(uri.path), &type, &selector)) {
        errno = EURL;
        return -1;
    }

    /* Connect; 70 is the default port */
    port = uri.port ? strip2str(uri.port) : "70";
    /*
    if ((s = connectTCP(strip2str(uri.host), port, flags & O_NONBLOCK)) == -1
    */
    if ((s = connectTCP(strip2str(uri.host), port, FALSE)) == -1
        && errno != EINPROGRESS)
        return -1;
    debug("Connected by Gopher to \"%s.%s\"\n", strip2str(uri.host), port);

    /* Create request string into `request' and `len' */
    if (! uri.search) {
        newarray(request, strlen(selector) + 3);
        len = sprintf(request, "%s\015\012", selector);
    } else if ((query = strchr(strip2str(uri.search), '='))) {
        query = URL_unescape(newstring(query + 1));
        newarray(request, strlen(selector) + strlen(query) + 4);
        len = sprintf(request, "%s\t%s\015\012", selector, query);
    } else {
        query = URL_unescape(newstring(strip2str(uri.search)));
        newarray(request, strlen(selector) + strlen(query) + 4);
        len = sprintf(request, "%s\t%s\015\012", selector, query);
    }

    if ((flags & O_NONBLOCK) == 0) {            /* Normal, blocking, I/O */
        if (write(s, request, len) == -1) return -1;
        dispose(request);
        len = 0;
#if 0
    } else if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) { /* Set non-blocking */
        return -1;
#endif
}
    if (!(f = fdopen(s, "r+"))) return -1;

    /* Create document info and store in `conn_info[fd]' */
    gopher2mime(type, selector, &mime);
    new(conn_info[s]);
    conn_info[s]->doc.url = newstring(url);
    conn_info[s]->doc.mime_type = newstring(mime);
    conn_info[s]->doc.mime_params = NULL;
    conn_info[s]->doc.title = NULL;
    conn_info[s]->doc.referer = newstring(referer);
    conn_info[s]->doc.status = NULL;
    conn_info[s]->doc.size = -1;
    conn_info[s]->is_menu = type == '1' || type == '7';
    conn_info[s]->f = f;
    conn_info[s]->eod = FALSE;
    conn_info[s]->non_blocking_io = (flags & O_NONBLOCK) != 0;
    conn_info[s]->request = request;
    conn_info[s]->offset = 0;
    conn_info[s]->len = len;
    conn_info[s]->bufstart = 0;
    conn_info[s]->buflen = !conn_info[s]->is_menu ? 0 :
        sprintf(conn_info[s]->buf,
                "<BASE HREF=\"%s\">\n<H1>Gopher menu</H1>\n<MENU>\n", url);

    return s;
}
#line 184 "ag-gopher.c-nw"
static Bool ready_for_write(int fd)
{
#if USE_POLL
    struct pollfd fds[1];

    fds[0].fd = fd;
    fds[0].events = POLLOUT;
    /* return: -1 = err; 0 = timed out; 1 = input available */
    return poll(fds, 1, 0) > 0;
#else /* USE_POLL */
    int mask[howmany(FD_SETSIZE, NRBITS)];
    struct timeval timeout;
    int n, i;

    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
    for (i = 0; i < XtNumber(mask); i++) mask[i] = 0;
    mask[fd/NRBITS] |= 1 << (fd % NRBITS);
    return select(fd + 1, NULL, mask, NULL, &timeout) > 0;
#endif /* USE_POLL */
}


EXPORT Bool doneGopher(int fd)
{
    int n;

    /* Try to send the request, if it hasn't been sent already */
    if (conn_info[fd]->len != 0
        && (! conn_info[fd]->non_blocking_io || ready_for_write(fd))) {
        debug("Write gopher request: %s",
              conn_info[fd]->request + conn_info[fd]->offset);
        n = fwrite(conn_info[fd]->request + conn_info[fd]->offset,
                   1, conn_info[fd]->len, conn_info[fd]->f);
        if (errno == EINPROGRESS) errno = EAGAIN;
        if (n == -1) return FALSE;              /* Error or `EAGAIN' */
        if (n > 0) {
            conn_info[fd]->offset += n;
            conn_info[fd]->len -= n;
        }
        fflush(conn_info[fd]->f);               /* Make ready for reading */
    }
    if (conn_info[fd]->len != 0) {
        errno = EAGAIN;                         /* Not ready yet */
        return FALSE;
    } else {
        return TRUE;                            /* Everything's been sent */
    }
}


EXPORT int peekGopher(int fd)
{
    int mask[howmany(FD_SETSIZE, NRBITS)];
    struct timeval timeout;
    int n, i;

    /* Check if there's anything to read */
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
    for (i = 0; i < XtNumber(mask); i++) mask[i] = 0;
    mask[fd/NRBITS] |= 1 << (fd % NRBITS);
    if ((n = select(fd + 1, mask, NULL, NULL, &timeout)) == -1) return -1;
    return n;                                   /* 0 or 1 */
}


/* tp_to_icon -- select HTML icon entity based on Gopher type */
static char *tp_to_icon(char tp)
{
    switch (tp) {
    case '0': return "&text.document;";
    case '1': return "&folder;";
    case '2': return "&telephone;";
    case '4': return "&binhex.document;";
    case '7': return "&index;";
    case '8': return "&telnet;";
    case '9': return "&binary.document;";
    case 's': return "&audio;";
    case 'I': return "&image;";
    case 'g': return "&image;";
    case 'G': return "&image;";
    case 'h': return "&text.document;";
    default: return "&unknown.document;";
    }
}


/* read_menu -- read available Gopher menu lines and convert them to HTML */
static int read_menu(int fd, char *buf, int nbytes)
{
    int n;
    char line[BUFSIZ], tp, *title, *selector, *host, *port, *p;

    /* Read something, only if there is nothing left in the buffer */
    if (conn_info[fd]->buflen == 0) {
        if (conn_info[fd]->eod) {               /* End of data has been seen */
            /* skip */;
        } else if (!fgets(line, sizeof(line), conn_info[fd]->f)) {
            return -1;                          /* Error or EAGAIN */
        } else if (line[0] != '.') {            /* Not end of menu */
            tp = line[0];
            title = tokenize(line + 1, "\t", &p);
            selector = tokenize(p, "\t", &p);
            host = tokenize(p, "\t", &p);
            port = tokenize(p, "\t\012\015", &p);
            conn_info[fd]->buflen = sprintf
                (conn_info[fd]->buf,
                 "<LI>%s <A HREF=\"gopher://%s:%s/%c%s\">%s</A>\n",
                 tp_to_icon(tp), host, port, tp, selector, title);
            conn_info[fd]->bufstart = 0;
            debug("Read Gopher menu line: %s", conn_info[fd]->buf);
        } else {                                /* End of menu */
            conn_info[fd]->buflen = sprintf(conn_info[fd]->buf, "</MENU>\n");
            conn_info[fd]->bufstart = 0;
            conn_info[fd]->eod = TRUE;
        }
    }

    /* Copy from buffer to caller's buffer */
    n = min(nbytes, conn_info[fd]->buflen);
    debug("read_menu: %d bytes: %s\n", n, conn_info[fd]->doc.url);
    strncpy(buf, conn_info[fd]->buf + conn_info[fd]->bufstart, n);
    conn_info[fd]->buflen -= n;
    conn_info[fd]->bufstart += n;
    return n;
}


EXPORT int readGopher(int fd, char *buf, size_t nbytes)
{
    if (conn_info[fd]->len > 0)
        if (peekGopher(fd) == -1) return -1;
    if (conn_info[fd]->is_menu)
        return read_menu(fd, buf, nbytes);
    else
        return fread(buf, 1, nbytes, conn_info[fd]->f);
}


EXPORT int writeGopher(int fd, const char *buf, size_t nbytes)
{
    errno = EMETHOD;
    return -1;                                  /* Impossible in Gopher */
}


EXPORT Bool infoGopher(int fd, W3ADocumentInfo *buf)
{
    buf->url = newstring(conn_info[fd]->doc.url);
    buf->mime_type = newstring(conn_info[fd]->doc.mime_type);
    buf->mime_params = newstring(conn_info[fd]->doc.mime_params);
    buf->title = newstring(conn_info[fd]->doc.title);
    buf->referer = newstring(conn_info[fd]->doc.referer);
    buf->status = newstring(conn_info[fd]->doc.status);
    debug("infoGopher: %s --> %s\n", buf->url, buf->mime_type);
    return TRUE;
}


EXPORT Bool closeGopher(int fd)
{
    int status;

    status = fclose(conn_info[fd]->f);
    dispose(conn_info[fd]->doc.url);
    dispose(conn_info[fd]->doc.mime_type);
    dispose(conn_info[fd]->doc.mime_params);
    dispose(conn_info[fd]->doc.title);
    dispose(conn_info[fd]->doc.referer);
    dispose(conn_info[fd]->doc.status);
    dispose(conn_info[fd]->request);
    dispose(conn_info[fd]);
    return status;
}


EXPORT Bool deleteGopher(const char *url)
{
    errno = EMETHOD;
    return -1;                                  /* Impossible in Gopher */
}