#include <u.h>
#include <libc.h>
#include <draw.h>
#include <memdraw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <plumb.h>
#include <html.h>
#include "dat.h"
#include "fns.h"

static void pageload1(Page *, Url *, int);

static
void
addchild(Page *p, Page *c)
{
       Page *t;

       c->parent = p;
       c->w = p->w;
       c->b = p->b;
       c->col = p->col;
       c->row = p->row;
       if(p->child == nil)
               p->child = c;
       else{
               for(t=p->child; t->next!=nil; t=t->next)
                       ;
               t->next = c;
       }
}

static
void
loadchilds(Page *p, Kidinfo *k)
{
       Runestr rs;
       Kidinfo *t;
       Page *c;

       addrefresh(p, "loading frames...");
       p->kidinfo = k;
       for(t=k->kidinfos; t!=nil; t=t->next){
               c = emalloc(sizeof(Page));
               addchild(p, c);
               if(t->isframeset){
                       c->url = urldup(p->url);
                       loadchilds(c, t);
               }else{
                       c->kidinfo = t;
                       /* this check shouldn't be necessary, but... */
                       if(t->src){
                               rs.r = urlcombine(p->url->act.r, t->src);
                               rs.nr = runestrlen(rs.r);
                               pageload1(c, urlalloc(&rs, nil, HGet), FALSE);
                               closerunestr(&rs);
                       }
               }
       }
}

static struct {
       char *mime;
       char *filter;
}filtertab[] = {
       "image/gif",    "gif -t9",
       "image/jpeg",   "jpg -t9",
       "image/jpg",    "jpg -t9",
       "image/pjpeg",  "jpg -t9",
       "image/png",    "png -t9",
       "image/ppm",    "ppm -t9",
       "image/x-icon", "ico -c",
       nil,    nil,
};

char *
getfilter(Rune *r, int x, int y)
{
       char buf[128];
       int i;

       snprint(buf, sizeof(buf), "%S", r);
       for(i=0; filtertab[i].mime!=nil; i++)
               if(cistrncmp(buf, filtertab[i].mime, strlen(filtertab[i].mime)) == 0)
                       break;

       if(filtertab[i].filter == nil)
               return nil;

       if(x==0 && y==0)
               return smprint("%s", filtertab[i].filter);
       if(x!=0 && y!=0)
               return smprint("%s | resize -x %d -y %d", filtertab[i].filter, x, y);
       if(x != 0)
               return smprint("%s | resize -x %d", filtertab[i].filter, x);
       /* y != 0 */
       return smprint("%s | resize -y %d", filtertab[i].filter, y);
}

static Cimage *cimages = nil;
static QLock cimagelock;

static
void
freecimage(Cimage *ci)
{
       Cimage *ci1;

       qlock(&cimagelock);
       if(decref(ci) == 0){
               if(ci->i)
                       freeimage(ci->i);
               else if(ci->mi)
                       freememimage(ci->mi);
               urlfree(ci->url);
               ci1 = cimages;
               if(ci1 == ci)
                       cimages = ci->next;
               else{
                       while(ci1){
                               if(ci1->next == ci){
                                       ci1->next = ci->next;
                                       break;
                               }
                               ci1 = ci1->next;
                       }
               }
               free(ci);
       }
       qunlock(&cimagelock);
}

static
void
closeimages(Page *p)
{
       int i;

       for(i=0; i<p->ncimage; i++)
               freecimage(p->cimage[i]);
       free(p->cimage);
       p->cimage =nil;
       p->ncimage = 0;
}

static
Cimage *
loadimg(Rune *src, int x , int y)
{
       Cimage *ci;
       Runestr rs;
       char *filter;
       int fd;

       ci = emalloc(sizeof(Cimage));
       rs. r = src;
       rs.nr = runestrlen(rs.r);
       ci->url = urlalloc(&rs, nil, HGet);
       fd = urlopen(ci->url);
       if(fd < 0){
   Err1:
               return ci;
       }
       filter = getfilter(ci->url->ctype.r, x, y);
       if(filter == nil){
               werrstr("%S unsupported: %S", ci->url->ctype.r, ci->url->act.r);
   Err2:
               close(fd);
               goto Err1;
       }
       fd = pipeline(fd, "%s", filter);
       free(filter);
       if(fd < 0)
               goto Err2;
       ci->mi = readmemimage(fd);
       close(fd);
       if(ci->mi == nil){
               werrstr("can't read image");
               goto Err2;
       }
       return ci;
}

static
Cimage *
findimg(Rune *s)
{
       Cimage *ci;

       qlock(&cimagelock);
       for(ci=cimages; ci!=nil; ci=ci->next)
               if(runestrcmp(ci->url->src.r, s) == 0)
                       break;

       qunlock(&cimagelock);
       return ci;
}

void
loadimages(Page *p)
{
       Cimage *ci;
       Iimage *i;
       Rune *src;

       addrefresh(p, "loading images...");
       reverseimages(&p->doc->images);
       for(i=p->doc->images; i!=nil; i=i->nextimage){
               if(p->aborting)
                       break;
               src = urlcombine(getbase(p), i->imsrc);
               ci = findimg(src);
               if(ci == nil){
                       ci = loadimg(src, i->imwidth, i->imheight);
                       qlock(&cimagelock);
                       ci->next = cimages;
                       cimages = ci;
                       qunlock(&cimagelock);
               }
               free(src);
               incref(ci);
               i->aux = ci;
               p->cimage = erealloc(p->cimage, ++p->ncimage*sizeof(Cimage *));
               p->cimage[p->ncimage-1] = ci;
               p->changed = TRUE;
               addrefresh(p, "");
       }
}

static char *mimetab[] = {
       "text/html",
       "application/xhtml",
       nil,
};

static
void
pageloadproc(void *v)
{
       Page *p;
       char buf[BUFSIZE], cs[32], *s;
       long n, l;
       int fd, i, ctype;

       threadsetname("pageloadproc");
       rfork(RFFDG);

       p = v;
       addrefresh(p, "opening: %S...", p->url->src.r);
       fd = urlopen(p->url);
       if(fd < 0){
               addrefresh(p, "%S: %r", p->url->src.r);
   Err:
               p->loading = FALSE;
               return;
       }
       if(runestrlen(p->url->ctype.r) == 0) /* assume .html when headers don't say anyting */
               goto Html;

       snprint(buf, sizeof(buf), "%S", p->url->ctype.r);
       for(i=0; mimetab[i]!=nil; i++)
               if(cistrncmp(buf, mimetab[i], strlen(mimetab[i])) == 0)
                       break;

       if(mimetab[i]){
   Html:
               ctype = TextHtml;
       }else if(cistrncmp(buf, "text/", 5) == 0)
               ctype = TextPlain;
       else{
               close(fd);
               addrefresh(p, "%S: unsupported mime type: '%S'", p->url->act.r, p->url->ctype.r);
               goto Err;
       }
       addrefresh(p, "loading: %S...", p->url->src.r);

       s = nil;
       if(p->url->ctype.nr > 0){
               snprint(buf, sizeof(buf), "%.*S", p->url->ctype.nr, p->url->ctype.r);
               if(findctype(cs, sizeof(cs), "charset", buf) == 0)
                       s = cs;
       }
       if((fd = pipeline(fd, s != nil ? "uhtml -c %q" : "uhtml", s)) < 0)
               goto Err;

       s = nil;
       l = 0;
       while((n=read(fd, buf, sizeof(buf))) > 0){
               if(p->aborting){
                       if(s){
                               free(s);
                               s = nil;
                       }
                       break;
               }
               s = erealloc(s, l+n+1);
               memmove(s+l, buf, n);
               l += n;
               s[l] = '\0';
       }
       close(fd);
       n = l;
       if(s){
               p->items = parsehtml((uchar *)s, n, p->url->act.r, ctype, UTF_8, &p->doc);
               free(s);
               fixtext(p);
               if(ctype==TextHtml && p->aborting==FALSE){
                       p->changed = TRUE;
                       addrefresh(p, "");
                       if(p->doc->doctitle){
                               p->title.r = erunestrdup(p->doc->doctitle);
                               p->title.nr = runestrlen(p->title.r);
                       }
                       p->loading = XXX;
                       if(p->doc->kidinfo)
                               loadchilds(p, p->doc->kidinfo);
                       else if(p->doc->images)
                               loadimages(p);
               }
       }
       p->changed = TRUE;
       p->loading = FALSE;
       addrefresh(p, "");
}

static
void
pageload1(Page *p, Url *u, int dohist)
{
       pageclose(p);
       p->loading = TRUE;
       p->url = u;
       if(dohist)
               winaddhist(p->w, p->url);
       proccreate(pageloadproc, p, STACK);
}

void
pageload(Page *p, Url *u, int dohist)
{
       if(p->parent == nil)
               textset(&p->w->url, u->src.r, u->src.nr);
       draw(p->b, p->all, display->white, nil, ZP);
       pageload1(p, u, dohist);
}

void
pageget(Page *p, Runestr *src, Runestr *post,  int m, int dohist)
{
       pageload(p, urlalloc(src, post, m), dohist);
}

void
pageclose(Page *p)
{
       Page *c, *nc;

       if(p == selpage)
               selpage = nil;
       pageabort(p);
       closeimages(p);
       urlfree(p->url);
       p->url = nil;
       if(p->doc){
               freedocinfo(p->doc);
               p->doc = nil;
       }
       layfree(p->lay);
       p->lay = nil;
       freeitems(p->items);
       p->items = nil;
       for(c=p->child; c!=nil; c=nc){
               nc = c->next;
               pageclose(c);
               free(c);
       }
       p->child = nil;
       closerunestr(&p->title);
       closerunestr(&p->refresh.rs);
       p->refresh.t = 0;
       p->pos = ZP;
       p->top = ZP;
       p->bot = ZP;
       p->loading = p->aborting = FALSE;
}

int
pageabort(Page *p)
{
       Page *c;

       for(c=p->child; c!=nil; c=c->next)
               pageabort(c);

       p->aborting = TRUE;
       while(p->loading)
               sleep(100);

       p->aborting = FALSE;
       return TRUE;
}


static Image *tmp;

void
tmpresize(void)
{
       if(tmp)
               freeimage(tmp);

       tmp = eallocimage(display, Rect(0,0,Dx(screen->r),Dy(screen->r)), screen->chan, 0, -1);
}

static
void
renderchilds(Page *p)
{
       Rectangle r;
       Kidinfo *k;
       Page *c;
       int i, j, x, y, *w, *h;

       draw(p->b, p->all, display->white, nil, ZP);
       r = p->all;
       y = r.min.y;
       c = p->child;
       k = p->kidinfo;
       frdims(k->rows, k->nrows, Dy(r), &h);
       frdims(k->cols, k->ncols, Dx(r), &w);
       for(i=0; i<k->nrows; i++){
               x = r.min.x;
               for(j=0; j<k->ncols; j++){
                       if(c->aborting)
                               return;
                       c->b = p->b;
                       c->all = Rect(x,y,x+w[j],y+h[i]);
                       c->w = p->w;
                       pagerender(c);
                       c = c->next;
                       x += w[j];
               }
               y += h[i];
       }
       free(w);
       free(h);
}

static
void
pagerender1(Page *p)
{
       Rectangle r;

       r = p->all;
       p->hscrollr = r;
       p->hscrollr.min.y = r.max.y-Scrollsize;
       p->vscrollr = r;
       p->vscrollr.max.x = r.min.x+Scrollsize;
       r.max.y -= Scrollsize;
       r.min.x += Scrollsize;
       p->r = r;
       p->vscrollr.max.y = r.max.y;
       p->hscrollr.min.x = r.min.x;
       laypage(p);
       pageredraw(p);
}

void
pagerender(Page *p)
{
       if(p->child && p->loading==FALSE)
               renderchilds(p);
       else if(p->doc)
               pagerender1(p);
}

void
pageredraw(Page *p)
{
       Rectangle r;

       r = p->lay->r;
       if(Dx(r)==0 || Dy(r)==0){
               draw(p->b, p->r, display->white, nil, ZP);
               return;
       }
       if(tmp == nil)
               tmpresize();

       p->selecting = FALSE;
       draw(tmp, tmp->r, getbg(p), nil, ZP);
       laydraw(p, tmp, p->lay);
       draw(p->b, p->r, tmp, nil, tmp->r.min);
       r = p->vscrollr;
       r.min.y = r.max.y;
       r.max.y += Scrollsize;
       draw(p->b, r, tagcols[HIGH], nil, ZP);
       draw(p->b, insetrect(r, 1), tagcols[BACK], nil, ZP);
       pagescrldraw(p);
}

static
void
pageselect1(Page *p)    /* when called, button 1 is down */
{
       Point mp, npos, opos;
       int b, scrled, x, y;

       b = mouse->buttons;
       mp = mousectl->xy;
       opos = getpt(p, mp);
       do{
               x = y = 0;
               if(mp.x < p->r.min.x)
                       x -= p->r.min.x-mp.x;
               else if(mp.x > p->r.max.x)
                       x += mp.x-p->r.max.x;
               if(mp.y < p->r.min.y)
                       y -= (p->r.min.y-mp.y)*Panspeed;
               else if(mp.y > p->r.max.y)
                       y += (mp.y-p->r.max.y)*Panspeed;

               scrled = pagescrollxy(p, x, y);
               npos = getpt(p, mp);
               if(opos.y <  npos.y){
                       p->top = opos;
                       p->bot = npos;
               }else{
                       p->top = npos;
                       p->bot = opos;
               }
               pageredraw(p);
               if(scrled == TRUE)
                       scrsleep(100);
               else
                       readmouse(mousectl);

               mp = mousectl->xy;
       }while(mousectl->buttons == b);
}

static Rune left1[] =  { L'{', L'[', L'(', L'<', L'«', 0 };
static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
static Rune left2[] =  { L'\'', L'"', L'`', 0 };

void
pagedoubleclick(Page *p)
{
       Point xy;
       Line *l;
       Box *b;

       xy = getpt(p, mouse->xy);
       l = linewhich(p->lay, xy);
       if(l==nil || l->hastext==FALSE)
               return;

       if(xy.x<l->boxes->r.min.x && hasbrk(l->state)){ /* beginning of line? */
               p->top = l->boxes->r.min;
               if(l->next && !hasbrk(l->next->state)){
                       for(l=l->next; l->next!=nil; l=l->next)
                               if(hasbrk(l->next->state))
                                       break;
               }
               p->bot = l->lastbox->r.max;;
       }else if(xy.x>l->lastbox->r.max.x && hasbrk(l->next->state)){   /* end of line? */
               p->bot = l->lastbox->r.max;
               if(!hasbrk(l->state) && l->prev!=nil){
                       for(l=l->prev; l->prev!=nil; l=l->prev)
                               if(hasbrk(l->state))
                                       break;
               }
               p->top = l->boxes->r.min;
       }else{
               b = pttobox(l, xy);
               if(b!=nil && b->i->tag==Itexttag){
                       p->top = b->r.min;
                       p->bot = b->r.max;
               }
       }
       p->top.y += 2;
       p->bot.y -= 2;
       pageredraw(p);
}

static uint clickmsec;

void
pageselect(Page *p)
{
       int b, x, y;


       selpage = p;
       /*
        * To have double-clicking and chording, we double-click
        * immediately if it might make sense.
        */
       b = mouse->buttons;
       if(mouse->msec-clickmsec<500){
               pagedoubleclick(p);
               x = mouse->xy.x;
               y = mouse->xy.y;
               /* stay here until something interesting happens */
               do
                       readmouse(mousectl);
               while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3);
               mouse->xy.x = x;        /* in case we're calling pageselect1 */
               mouse->xy.y = y;
       }
       if(mousectl->buttons == b)
               pageselect1(p);

       if(eqpt(p->top, p->bot)){
               if(mouse->msec-clickmsec<500)
                       pagedoubleclick(p);
               else
                       clickmsec = mouse->msec;
       }
       while(mouse->buttons){
               mouse->msec = 0;
               b = mouse->buttons;
               if(b & 2)       /* snarf only */
                       cut(nil, nil, TRUE, FALSE, nil, 0);
               while(mouse->buttons == b)
                       readmouse(mousectl);
       }
}

Page *
pagewhich(Page *p, Point xy)
{
       Page *c;

       if(p->child == nil)
               return p;

       for(c=p->child; c!=nil; c=c->next)
               if(ptinrect(xy, c->all))
                       return pagewhich(c, xy);

       return nil;
}

void
pagemouse(Page *p, Point xy, int but)
{
       Box *b;

       p = pagewhich(p, xy);
       if(p == nil)
               return;

       if(pagerefresh(p))
               return;

       if(p->lay == nil)
               return;

       if(ptinrect(xy, p->vscrollr)){
               pagescroll(p, but, FALSE);
               return;
       }
       if(ptinrect(xy, p->hscrollr)){
               pagescroll(p, but, TRUE);
               return;
       }
       xy = getpt(p, xy);
       b = boxwhich(p->lay, xy);
       if(b && b->mouse)
               b->mouse(b, p, but);
       else if(but == 1)
               pageselect(p);
}

void
pagetype(Page *p, Rune r, Point xy)
{
       Box *b;
       int x, y;

       p = pagewhich(p, xy);
       if(p == nil)
               return;

       if(pagerefresh(p))
               return;

       if(p->lay == nil)
               return;

       /* text field? */
       xy = getpt(p, xy);
       b = boxwhich(p->lay, xy);
       if(b && b->key){
               b->key(b, p, r);
               return;
       }

       x = 0;
       y = 0;
       switch(r){
       case Kleft:
               x -= Dx(p->r)/2;
               break;
       case Kright:
               x += Dx(p->r)/2;
               break;
       case Kdown:
       case Kscrollonedown:
               y += Dy(p->r)/2;
               break;
       case Kpgdown:
               y += Dy(p->r);
               break;
       case Kup:
       case Kscrolloneup:
               y -= Dy(p->r)/2;
               break;
       case Kpgup:
               y -= Dy(p->r);
               break;
       case Khome:
               y -= Dy(p->lay->r);     /* force p->pos.y = 0 */
               break;
       case Kend:
               y = Dy(p->lay->r) - Dy(p->r);
               break;
       default:
               return;
       }
       if(pagescrollxy(p, x, y))
               pageredraw(p);
}

void
pagesnarf(Page *p)
{
       Runestr rs;

       memset(&rs, 0, sizeof(Runestr));
       laysnarf(p, p->lay, &rs);
       putsnarf(&rs);
       closerunestr(&rs);
}

void
pagesetrefresh(Page *p)
{
       Runestr rs;
       Rune *s, *q, *t;
       char *v;
       int n;

       if(!p->doc || !p->doc->refresh)
               return;

       s = p->doc->refresh;
       q = runestrchr(s, L'=');
       if(q == nil)
               return;
       q++;
       if(!q)
               return;
       n = runestrlen(q);
       if(*q == L'''){
               q++;
               n -= 2;
       }
       if(n <= 0)
               return;
       t = runesmprint("%.*S", n, q);
       rs.r = urlcombine(getbase(p), t);
       rs.nr = runestrlen(rs.r);
       copyrunestr(&p->refresh.rs, &rs);
       closerunestr(&rs);
       free(t);

       /* now the time */
       q = runestrchr(s, L';');
       if(q){
               v = smprint("%.*S", (int)(q-s),  s);
               p->refresh.t = atoi(v);
               free(v);
       }else
               p->refresh.t = 1;

       p->refresh.t += time(0);
}

int
pagerefresh(Page *p)
{
       int t;

       if(!p->refresh.t)
               return 0;

       t = p->refresh.t - time(0);
       if(t > 0)
               return 0;

       pageget(p, &p->refresh.rs, nil, HGet, FALSE);
       return 1;
}