#include <u.h>
#include <libc.h>
#include <bio.h>
#include "httpd.h"
#include "httpsrv.h"

static  Hio             *hout;
static  Hio             houtb;
static  HConnect        *connect;

void    doconvert(char*, int);

void
error(char *title, char *fmt, ...)
{
       va_list arg;
       char buf[1024], *out;

       va_start(arg, fmt);
       out = vseprint(buf, buf+sizeof(buf), fmt, arg);
       va_end(arg);
       *out = 0;

       hprint(hout, "%s 404 %s\n", hversion, title);
       hprint(hout, "Date: %D\n", time(nil));
       hprint(hout, "Server: Plan9\n");
       hprint(hout, "Content-type: text/html\n");
       hprint(hout, "\n");
       hprint(hout, "<head><title>%s</title></head>\n", title);
       hprint(hout, "<body><h1>%s</h1></body>\n", title);
       hprint(hout, "%s\n", buf);
       hflush(hout);
       writelog(connect, "Reply: 404\nReason: %s\n", title);
       exits(nil);
}

typedef struct Hit      Hit;
struct Hit
{
       Hit *next;
       char *file;
};

void
lookup(char *object, int section, Hit **list)
{
       int fd;
       char *p, *f;
       Biobuf b;
       char file[256];
       Hit *h;

       while(*list != nil)
               list = &(*list)->next;

       snprint(file, sizeof(file), "/sys/man/%d/INDEX", section);
       fd = open(file, OREAD);
       if(fd >= 0){
               Binit(&b, fd, OREAD);
               for(;;){
                       p = Brdline(&b, '\n');
                       if(p == nil)
                               break;
                       p[Blinelen(&b)-1] = 0;
                       f = strchr(p, ' ');
                       if(f == nil)
                               continue;
                       *f++ = 0;
                       if(strcmp(p, object) == 0){
                               h = ezalloc(sizeof *h);
                               *list = h;
                               h->next = nil;
                               snprint(file, sizeof(file), "/%d/%s", section, f);
                               h->file = estrdup(file);
                               close(fd);
                               return;
                       }
               }
               close(fd);
       }
       snprint(file, sizeof(file), "/sys/man/%d/%s", section, object);
       if(access(file, 0) == 0){
               h = ezalloc(sizeof *h);
               *list = h;
               h->next = nil;
               h->file = estrdup(file+8);
       }
}

void
manindex(int sect, int vermaj)
{
       int i;

       if(vermaj){
               hokheaders(connect);
               hprint(hout, "Content-type: text/html\r\n");
               hprint(hout, "\r\n");
       }

       hprint(hout, "<head><title>plan 9 section index");
       if(sect)
               hprint(hout, "(%d)\n", sect);
       hprint(hout, "</title></head><body>\n");
       hprint(hout, "<H6>Section Index");
       if(sect)
               hprint(hout, "(%d)\n", sect);
       hprint(hout, "</H6>\n");

       if(sect)
               hprint(hout, "<p><a href=\"/plan9/man%d.html\">/plan9/man%d.html</a>\n",
                       sect, sect);
       else for(i = 1; i < 10; i++)
               hprint(hout, "<p><a href=\"/plan9/man%d.html\">/plan9/man%d.html</a>\n",
                       i, i);
       hprint(hout, "</body>\n");
}

void
man(char *o, int sect, int vermaj)
{
       int i;
       Hit *list;

       list = nil;

       if(*o == 0){
               manindex(sect, vermaj);
               return;
       }

       if(sect > 0 && sect < 10)
               lookup(o, sect, &list);
       else
               for(i = 1; i < 9; i++)
                       lookup(o, i, &list);

       if(list != nil && list->next == nil){
               doconvert(list->file, vermaj);
               return;
       }

       if(vermaj){
               hokheaders(connect);
               hprint(hout, "Content-type: text/html\r\n");
               hprint(hout, "\r\n");
       }

       hprint(hout, "<head><title>plan 9 man %H", o);
       if(sect)
               hprint(hout, "(%d)\n", sect);
       hprint(hout, "</title></head><body>\n");
       hprint(hout, "<H6>Search for %H", o);
       if(sect)
               hprint(hout, "(%d)\n", sect);
       hprint(hout, "</H6>\n");

       for(; list; list = list->next)
               hprint(hout, "<p><a href=\"/magic/man2html%U\">/magic/man2html%H</a>\n",
                       list->file, list->file);
       hprint(hout, "</body>\n");
}

void
strlwr(char *p)
{
       for(; *p; p++)
               if('A' <= *p && *p <= 'Z')
                       *p += 'a'-'A';
}

void
redirectto(char *uri)
{
       if(connect){
               hmoved(connect, uri);
               exits(0);
       }else
               hprint(hout, "Your selection moved to <a href=\"%U\"> here</a>.<p></body>\r\n", uri);
}

void
searchfor(char *search)
{
       int i, j, n, fd;
       char *p, *sp;
       Biobufhdr *b;
       char *arg[32];

       hprint(hout, "<head><title>plan 9 search for %H</title></head>\n", search);
       hprint(hout, "<body>\n");

       hprint(hout, "<p>This is a keyword search through Plan 9 man pages.\n");
       hprint(hout, "The search is case insensitive; blanks denote \"boolean and\".\n");
       hprint(hout, "<FORM METHOD=\"GET\" ACTION=\"/magic/man2html\">\n");
       hprint(hout, "<INPUT NAME=\"pat\" TYPE=\"text\" SIZE=\"60\">\n");
       hprint(hout, "<INPUT TYPE=\"submit\" VALUE=\"Submit\">\n");
       hprint(hout, "</FORM>\n");

       hprint(hout, "<hr><H6>Search for %H</H6>\n", search);
       n = getfields(search, arg, 32, 1, "+");
       for(i = 0; i < n; i++){
               for(j = i+1; j < n; j++){
                       if(strcmp(arg[i], arg[j]) > 0){
                               sp = arg[j];
                               arg[j] = arg[i];
                               arg[i] = sp;
                       }
               }
               sp = malloc(strlen(arg[i]) + 2);
               if(sp != nil){
                       strcpy(sp+1, arg[i]);
                       sp[0] = ' ';
                       arg[i] = sp;
               }
       }

       /*
        *  search index till line starts alphabetically < first token
        */
       fd = open("/sys/man/searchindex", OREAD);
       if(fd < 0){
               hprint(hout, "<body>error: No Plan 9 search index\n");
               hprint(hout, "</body>");
               return;
       }
       p = malloc(32*1024);
       if(p == nil){
               close(fd);
               return;
       }
       b = ezalloc(sizeof *b);
       Binits(b, fd, OREAD, (uchar*)p, 32*1024);
       for(;;){
               p = Brdline(b, '\n');
               if(p == nil)
                       break;
               p[Blinelen(b)-1] = 0;
               for(i = 0; i < n; i++){
                       sp = strstr(p, arg[i]);
                       if(sp == nil)
                               break;
                       p = sp;
               }
               if(i < n)
                       continue;
               sp = strrchr(p, '\t');
               if(sp == nil)
                       continue;
               sp++;
               hprint(hout, "<p><a href=\"/magic/man2html/%U\">/magic/man2html/%H</a>\n",
                       sp, sp);
       }
       hprint(hout, "</body>");

       Bterm(b);
       free(b);
       free(p);
       close(fd);
}

/*
*  find man pages mentioning the search string
*/
void
dosearch(int vermaj, char *search)
{
       int sect;
       char *p;

       if(strncmp(search, "man=", 4) == 0){
               sect = 0;
               search = hurlunesc(connect, search+4);
               p = strchr(search, '&');
               if(p != nil){
                       *p++ = 0;
                       if(strncmp(p, "sect=", 5) == 0)
                               sect = atoi(p+5);
               }
               man(search, sect, vermaj);
               return;
       }

       if(vermaj){
               hokheaders(connect);
               hprint(hout, "Content-type: text/html\r\n");
               hprint(hout, "\r\n");
       }

       if(strncmp(search, "pat=", 4) == 0){
               search = hurlunesc(connect, search+4);
               search = hlower(search);
               searchfor(search);
               return;
       }

       hprint(hout, "<head><title>illegal search</title></head>\n");
       hprint(hout, "<body><p>Illegally formatted Plan 9 man page search</p>\n");
       search = hurlunesc(connect, search);
       hprint(hout, "<body><p>%H</p>\n", search);
       hprint(hout, "</body>");
}

/*
*  convert a man page to html and output
*/
void
doconvert(char *uri, int vermaj)
{
       char *p;
       char file[256];
       char title[256];
       char err[ERRMAX];
       int pfd[2];
       Dir *d;
       Waitmsg *w;
       int x;

       if(strstr(uri, ".."))
               error("bad URI", "man page URI cannot contain ..");
       p = strstr(uri, "/intro");

       if(p == nil){
               while(*uri == '/')
                       uri++;
               /* redirect section requests */
               snprint(file, sizeof(file), "/sys/man/%s", uri);
               d = dirstat(file);
               if(d == nil){
                       strlwr(file);
                       if(dirstat(file) != nil){
                               snprint(file, sizeof(file), "/magic/man2html/%s", uri);
                               strlwr(file);
                               redirectto(file);
                       }
                       error(uri, "man page not found");
               }
               x = d->qid.type;
               free(d);
               if(x & QTDIR){
                       if(*uri == 0 || strcmp(uri, "/") == 0)
                               redirectto("/sys/man/index.html");
                       else {
                               snprint(file, sizeof(file), "/sys/man/%s/INDEX.html",
                                       uri+1);
                               redirectto(file);
                       }
                       return;
               }
       } else {
               /* rewrite the name intro */
               *p = 0;
               snprint(file, sizeof(file), "/sys/man/%s/0intro", uri);
               d = dirstat(file);
               free(d);
               if(d == nil)
                       error(uri, "man page not found");
       }

       if(vermaj){
               hokheaders(connect);
               hprint(hout, "Content-type: text/html\r\n");
               hprint(hout, "\r\n");
       }
       hflush(hout);

       if(pipe(pfd) < 0)
               error("out of resources", "pipe failed");

       /* troff -manhtml <file> | troff2html -t '' */
       switch(fork()){
       case -1:
               error("out of resources", "fork failed");
       case 0:
               snprint(title, sizeof(title), "Plan 9 %s", file);
               close(0);
               dup(pfd[0], 0);
               close(pfd[0]);
               close(pfd[1]);
               execl("/bin/troff2html", "troff2html", "-t", title, nil);
               errstr(err, sizeof err);
               exits(err);
       }
       switch(fork()){
       case -1:
               error("out of resources", "fork failed");
       case 0:
               snprint(title, sizeof(title), "Plan 9 %s", file);
               close(0);
               close(1);
               dup(pfd[1], 1);
               close(pfd[0]);
               close(pfd[1]);
               execl("/bin/troff", "troff", "-manhtml", file, nil);
               errstr(err, sizeof err);
               exits(err);
       }
       close(pfd[0]);
       close(pfd[1]);

       /* wait for completion */
       for(;;){
               w = wait();
               if(w == nil)
                       break;
               if(w->msg[0] != 0)
                       print("whoops %s\n", w->msg);
               free(w);
       }
}

void
main(int argc, char **argv)
{
       fmtinstall('H', httpfmt);
       fmtinstall('U', hurlfmt);

       if(argc == 2){
               hinit(&houtb, 1, Hwrite);
               hout = &houtb;
               doconvert(argv[1], 0);
               exits(nil);
       }
       close(2);

       connect = init(argc, argv);
       hout = &connect->hout;
       if(hparseheaders(connect, HSTIMEOUT) < 0)
               exits("failed");

       if(strcmp(connect->req.meth, "GET") != 0 && strcmp(connect->req.meth, "HEAD") != 0){
               hunallowed(connect, "GET, HEAD");
               exits("not allowed");
       }
       if(connect->head.expectother || connect->head.expectcont){
               hfail(connect, HExpectFail, nil);
               exits("failed");
       }

       bind("/usr/web/sys/man", "/sys/man", MREPL);

       if(connect->req.search != nil)
               dosearch(connect->req.vermaj, connect->req.search);
       else
               doconvert(connect->req.uri, connect->req.vermaj);
       hflush(hout);
       writelog(connect, "200 man2html %ld %ld\n", hout->seek, hout->seek);
       exits(nil);
}