#include <u.h>
#include <libc.h>
#include <bio.h>
#include <String.h>
#include <ctype.h>
#include <thread.h>
#include "wiki.h"

static Wpage*
mkwtxt(int type, char *text)
{
       Wpage *w;

       w = emalloc(sizeof(*w));
       w->type = type;
       w->text = text;
       setmalloctag(w, getcallerpc(&type));
       return w;
}

/*
* turn runs of whitespace into single spaces,
* eliminate whitespace at beginning and end.
*/
char*
strcondense(char *s, int cutbegin)
{
       char *r, *w, *es;
       int inspace;

       es = s+strlen(s);
       inspace = cutbegin;
       for(r=w=s; *r; r++){
               if(isspace(*r)){
                       if(!inspace){
                               inspace=1;
                               *w++ = ' ';
                       }
               }else{
                       inspace=0;
                       *w++ = *r;
               }
       }
       assert(w <= es);
       if(inspace && w>s){
               --w;
               *w = '\0';
       }
       else
               *w = '\0';
       return s;
}

/*
* turn runs of Wplain into single Wplain.
*/
static Wpage*
wcondense(Wpage *wtxt)
{
       Wpage *ow, *w;

       for(w=wtxt; w; ){
               if(w->type == Wplain)
                       strcondense(w->text, 1);

               if(w->type != Wplain || w->next==nil
               || w->next->type != Wplain){
                       w=w->next;
                       continue;
               }

               w->text = erealloc(w->text, strlen(w->text)+1+strlen(w->next->text)+1);
               strcat(w->text, " ");
               strcat(w->text, w->next->text);

               ow = w->next;
               w->next = ow->next;
               ow->next = nil;
               freepage(ow);
       }
       return wtxt;
}

/*
* Parse a link, without the brackets.
*/
static Wpage*
mklink(char *s)
{
       char *q;
       Wpage *w;

       for(q=s; *q && *q != '|'; q++)
               ;

       if(*q == '\0'){
               w = mkwtxt(Wlink, estrdup(strcondense(s, 1)));
               w->url = nil;
       }else{
               *q = '\0';
               w = mkwtxt(Wlink, estrdup(strcondense(s, 1)));
               w->url = estrdup(strcondense(q+1, 1));
       }
       setmalloctag(w, getcallerpc(&s));
       return w;
}

/*
* Parse Wplains, inserting Wlink nodes where appropriate.
*/
static Wpage*
wlink(Wpage *wtxt)
{
       char *p, *q, *r, *s;
       Wpage *w, *nw;

       for(w=wtxt; w; w=nw){
               nw = w->next;
               if(w->type != Wplain)
                       continue;
               while(w->text[0]){
                       p = w->text;
                       for(q=p; *q && *q != '['; q++)
                               ;
                       if(*q == '\0')
                               break;
                       for(r=q; *r && *r != ']'; r++)
                               ;
                       if(*r == '\0')
                               break;
                       *q = '\0';
                       *r = '\0';
                       s = w->text;
                       w->text = estrdup(w->text);
                       w->next = mklink(q+1);
                       w = w->next;
                       w->next = mkwtxt(Wplain, estrdup(r+1));
                       free(s);
                       w = w->next;
                       w->next = nw;
               }
               assert(w->next == nw);
       }
       return wtxt;
}

static int
ismanchar(int c)
{
       return ('a' <= c && c <= 'z')
               || ('A' <= c && c <= 'Z')
               || ('0' <= c && c <= '9')
               || c=='_' || c=='-' || c=='.' || c=='/'
               || (c < 0);     /* UTF */
}

static Wpage*
findmanref(char *p, char **beginp, char **endp)
{
       char *q, *r;
       Wpage *w;

       q=p;
       for(;;){
               for(; q[0] && (q[0] != '(' || !isdigit(q[1]) || q[2] != ')'); q++)
                       ;
               if(*q == '\0')
                       break;
               for(r=q; r>p && ismanchar(r[-1]); r--)
                       ;
               if(r==q){
                       q += 3;
                       continue;
               }
               *q = '\0';
               w = mkwtxt(Wman, estrdup(r));
               *beginp = r;
               *q = '(';
               w->section = q[1]-'0';
               *endp = q+3;
               setmalloctag(w, getcallerpc(&p));
               return w;
       }
       return nil;
}

/*
* Parse Wplains, looking for man page references.
* This should be done by using a plumb(6)-style
* control file rather than hard-coding things here.
*/
static Wpage*
wman(Wpage *wtxt)
{
       char *q, *r;
       Wpage *w, *mw, *nw;

       for(w=wtxt; w; w=nw){
               nw = w->next;
               if(w->type != Wplain)
                       continue;
               while(w->text[0]){
                       if((mw = findmanref(w->text, &q, &r)) == nil)
                               break;
                       *q = '\0';
                       w->next = mw;
                       w = w->next;
                       w->next = mkwtxt(Wplain, estrdup(r));
                       w = w->next;
                       w->next = nw;
               }
               assert(w->next == nw);
       }
       return wtxt;
}

static int isheading(char *p) {
       Rune r;
       int hasupper=0;
       while(*p) {
               p+=chartorune(&r,p);
               if(isupperrune(r))
                       hasupper=1;
               else if(islowerrune(r))
                       return 0;
       }
       return hasupper;
}

Wpage*
Brdpage(char *(*rdline)(void*,int), void *b)
{
       char *p, *c;
       int waspara;
       Wpage *w, **pw;

       w = nil;
       pw = &w;
       waspara = 1;
       while((p = rdline(b, '\n')) != nil){
               if(p[0] != '!')
                       p = strcondense(p, 1);
               if(p[0] == '\0'){
                       if(waspara==0){
                               waspara=1;
                               *pw = mkwtxt(Wpara, nil);
                               pw = &(*pw)->next;
                       }
                       continue;
               }
               waspara = 0;
               switch(p[0]){
               case '*':
                       *pw = mkwtxt(Wbullet, nil);
                       pw = &(*pw)->next;
                       *pw = mkwtxt(Wplain, estrdup(p+1));
                       pw = &(*pw)->next;
                       break;
               case '!':
                       *pw = mkwtxt(Wpre, estrdup(p[1]==' '?p+2:p+1));
                       pw = &(*pw)->next;
                       break;
               case '-':
                       for(c = p; *c != '\0'; c++) {
                               if(*c != '-') {
                                       c = p;
                                       break;
                               }
                       }

                       if( (c-p) > 4) {
                               *pw = mkwtxt(Whr, nil);
                               pw = &(*pw)->next;
                               break;
                       }
                       /* else fall thru */
               default:
                       if(isheading(p)){
                               *pw = mkwtxt(Wheading, estrdup(p));
                               pw = &(*pw)->next;
                               continue;
                       }
                       *pw = mkwtxt(Wplain, estrdup(p));
                       pw = &(*pw)->next;
                       break;
               }
       }
       if(w == nil)
               werrstr("empty page");

       *pw = nil;
       w = wcondense(w);
       w = wlink(w);
       w = wman(w);
       setmalloctag(w, getcallerpc(&rdline));

       return w;
}

void
printpage(Wpage *w)
{
       for(; w; w=w->next){
               switch(w->type){
               case Wpara:
                       print("para\n");
                       break;
               case Wheading:
                       print("heading '%s'\n", w->text);
                       break;
               case Wbullet:
                       print("bullet\n");
                       break;
               case Wlink:
                       print("link '%s' '%s'\n", w->text, w->url);
                       break;
               case Wman:
                       print("man %d %s\n", w->section, w->text);
                       break;
               case Wplain:
                       print("plain '%s'\n", w->text);
                       break;
               case Whr:
                       print("hr\n");
                       break;
               case Wpre:
                       print("pre '%s'\n", w->text);
                       break;
               }
       }
}