#include <u.h>
#include <libc.h>
#include <bio.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 <regexp.h>
#include "dat.h"
#include "fns.h"

static  Point           prevmouse;
static  Window  *mousew;

int
min(int a, int b)
{
       if(a < b)
               return a;
       return b;
}

int
max(int a, int b)
{
       if(a > b)
               return a;
       return b;
}

void
cvttorunes(char *p, int n, Rune *r, int *nb, int *nr, int *nulls)
{
       uchar *q;
       Rune *s;
       int j, w;

       /*
        * Always guaranteed that n bytes may be interpreted
        * without worrying about partial runes.  This may mean
        * reading up to UTFmax-1 more bytes than n; the caller
        * knows this.  If n is a firm limit, the caller should
        * set p[n] = 0.
        */
       q = (uchar*)p;
       s = r;
       for(j=0; j<n; j+=w){
               if(*q < Runeself){
                       w = 1;
                       *s = *q++;
               }else{
                       w = chartorune(s, (char*)q);
                       q += w;
               }
               if(*s)
                       s++;
               else if(nulls)
                       *nulls = TRUE;
       }
       *nb = (char*)q-p;
       *nr = s-r;
}

void
bytetorunestr(char *s, Runestr *rs)
{
       Rune *r;
       int nb, nr;

       nb = strlen(s);
       r = runemalloc(nb+1);
       cvttorunes(s, nb, r, &nb, &nr, nil);
       r[nr] = '\0';
       rs->nr = nr;
       rs->r = r;
}

void
error(char *s)
{
       fprint(2, "abaco: %s: %r\n", s);
//      abort();
       threadexitsall(s);
}

void*
emalloc(ulong n)
{
       void *p;

       p = malloc(n);
       if(p == nil)
               error("malloc failed");
       setmalloctag(p, getcallerpc(&n));
       memset(p, 0, n);
       return p;
}

void*
erealloc(void *p, ulong n)
{
       p = realloc(p, n);
       if(p == nil)
               error("realloc failed");
       setmalloctag(p, getcallerpc(&n));
       return p;
}

Rune*
erunestrdup(Rune *r)
{
       void *p;

       if(r == nil)
               return nil;
       p = runestrdup(r);
       if(p == nil)
               error("runestrdup failed");
       setmalloctag(p, getcallerpc(&r));
       return p;
}

char*
estrdup(char *s)
{
       char *t;

       t = strdup(s);
       if(t == nil)
               error("strdup failed");
       setmalloctag(t, getcallerpc(&s));
       return t;
}

int
runestreq(Runestr a, Runestr b)
{
       return runeeq(a.r, a.nr, b.r, b.nr);
}

int
runeeq(Rune *s1, uint n1, Rune *s2, uint n2)
{
       if(n1 != n2)
               return FALSE;
       return memcmp(s1, s2, n1*sizeof(Rune)) == 0;
}

void
closerunestr(Runestr *rs)
{

       rs->nr = 0;
       if(rs->r)
               free(rs->r);
       rs->r = nil;
}

void
copyrunestr(Runestr *a, Runestr *b)
{
       closerunestr(a);
       a->r = runemalloc(b->nr+1);
       runemove(a->r, b->r, b->nr);
       a->r[b->nr] = 0;
       a->nr = b->nr;
}

int
isalnum(Rune c)
{
       /*
        * Hard to get absolutely right.  Use what we know about ASCII
        * and assume anything above the Latin control characters is
        * potentially an alphanumeric.
        */
       if(c <= ' ')
               return FALSE;
       if(0x7F<=c && c<=0xA0)
               return FALSE;
       if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
               return FALSE;
       return TRUE;
}

Rune*
skipbl(Rune *r, int n, int *np)
{
       while(n>0 && (*r==' ' || *r=='\t' || *r=='\n')){
               --n;
               r++;
       }
       *np = n;
       return r;
}

Rune*
findbl(Rune *r, int n, int *np)
{
       while(n>0 && *r!=' ' && *r!='\t' && *r!='\n'){
               --n;
               r++;
       }
       *np = n;
       return r;
}

int
istextfield(Item *i)
{
       Formfield *ff;

       ff = ((Iformfield *)i)->formfield;
       if(ff->ftype==Ftext || ff->ftype==Ftextarea || ff->ftype==Fpassword)
               return TRUE;

       return FALSE;
}

int
forceitem(Item *i)
{
       if(i->state&IFwrap && i->tag!=Iruletag && i->tag!=Itabletag)
               return FALSE;

       return TRUE;
}

int
dimwidth(Dimen d, int w)
{
       int s, k;

       k = dimenkind(d);
       if(k == Dnone)
               return w;
       s = dimenspec(d);
       if(k == Dpixels)
                w = s;
       else if(k==Dpercent && s<100)
               w = s*w/100;

       return w;
}

void
frdims(Dimen *d, int n, int t, int **ret)
{
       int totpix, totpcnt, totrel;
       double spix, spcnt, relu, vd;
       int tt, trest, totpixrel, minrelu, i;
       int *x, *spec, *kind;

       if(n == 1){
               *ret = x = emalloc(sizeof(int));
               x[0] = t;
               return;
       }
       totpix = totpcnt = totrel = 0;
       spec = emalloc(n*sizeof(int));
       kind = emalloc(n*sizeof(int));
       for(i=0; i<n; i++){
               spec[i] = dimenspec(d[i]);
               if(spec[i] < 0)
                       spec[i] = 0;
               kind[i] = dimenkind(d[i]);
               switch(kind[i]){
               case Dpixels:
                       totpix += spec[i];
                       break;
               case Dpercent:
                       totpcnt += spec[i];
                       break;
               case Drelative:
                       totrel += spec[i];
                       break;
               case Dnone:
                       totrel++;
                       break;
               }
       }
       spix = spcnt = 1.0;
       minrelu = 0;
       if(totrel > 0)
               minrelu = Scrollsize+Scrollgap;
       relu = (double)minrelu;
       tt = totpix + t*totpcnt/100 + totrel*minrelu;
       if(tt < t){
               if(totrel == 0){
                       if(totpcnt != 0)
                               spcnt = (double)((t-totpix)*100)/(double)(t*totpcnt);
                       else
                               spix = (double)t/(double)totpix;
               }else
                       relu += (double)(t-tt)/(double)totrel;
       }else{
               totpixrel = totpix + totrel*minrelu;
               if(totpixrel < t)
                       spcnt = (double)((t-totpixrel)*100)/(double)(t*totpcnt);
               else{
                       trest = t - totrel*minrelu;
                       if(trest > 0)
                               spcnt = (double)trest/(double)(totpix + (t*totpcnt/100));
                       else{
                               spcnt = (double)t/(double)tt;
                               relu = 0.0;
                       }
                       spix = spcnt;
               }
       }
       x = emalloc(n * sizeof(int));
       tt = 0;
       for(i=0; i<n-1; i++){
               vd = (double)spec[i];
               switch(kind[i]){
               case Dpixels:
                       vd = vd*spix;
                       break;
               case Dpercent:
                       vd = vd*(double)t*spcnt/100.0;
                       break;
               case Drelative:
                       vd = vd*relu;
                       break;
               case Dnone:
                       vd = relu;
                       break;
               }
               x[i] = (int)(vd+.5);
               tt += x[i];
       }
       x[n - 1] = t - tt;
       *ret = x;
       free(spec);
       free(kind);
}

Image *
getbg(Page *p)
{
       Docinfo *d;
       Cimage *ci;
       Image *bg;

       d = p->doc;
       if(d->backgrounditem){
               if(d->backgrounditem->aux){
                       ci = d->backgrounditem->aux;
                       if(ci->mi)
                               getimage(ci, d->backgrounditem->altrep);
                       bg = ci->i;
               }else
                       bg = display->white;
       }else
               bg = getcolor(d->background.color);

       return bg;
}

Rune *
getbase(Page *p)
{
       if(p->doc)
               return p->doc->base;
       if(p->url->act.r)
               return p->url->act.r;
       return p->url->src.r;
}

Image *
eallocimage(Display *d, Rectangle r, ulong chan, int repl, int col)
{
       Image *i;

       i = allocimage(d, r, chan, repl, col);
       if(i == nil)
               error("allocimage failed");
       return i;
}

void
rect3d(Image *im, Rectangle r, int i, Image **c, Point sp)
{
       Point p[6];

       if(i < 0){
               r = insetrect(r, i);
               sp = addpt(sp, Pt(i,i));
               i = -i;
       }
       draw(im, Rect(r.min.x+i, r.min.y+i, r.max.x-i, r.max.y-i), c[2], nil, sp);
       p[0] = r.min;
       p[1] = Pt(r.min.x, r.max.y);
       p[2] = Pt(r.min.x+i, r.max.y-i);
       p[3] = Pt(r.min.x+i, r.min.y+i);
       p[4] = Pt(r.max.x-i, r.min.y+i);
       p[5] = Pt(r.max.x, r.min.y);
       fillpoly(im, p, 6, 0, c[0], sp);
       p[0] = r.max;
       p[1] = Pt(r.min.x, r.max.y);
       p[2] = Pt(r.min.x+i, r.max.y-i);
       p[3] = Pt(r.max.x-i, r.max.y-i);
       p[4] = Pt(r.max.x-i, r.min.y+i);
       p[5] = Pt(r.max.x, r.min.y);
       fillpoly(im, p, 6, 0, c[1], sp);
}

void
ellipse3d(Image *im, Point p, int rad, int i, Image **c, Point sp)
{
       fillarc(im, p, rad, rad, c[0], sp, 45, 180);
       fillarc(im, p, rad, rad, c[1], sp,  45, -180);
       fillellipse(im, p, rad-i, rad-i, c[2], sp);
}

void
colarray(Image **c, Image *c0, Image *c1, Image *c2, int checked)
{
       if(checked){
               c[0] = c0;
               c[1] = c1;
       }else{
               c[0] = c1;
               c[1] = c0;
       }
       c[2] = c2;
}

static char *deffontpaths[] = {
#include "fonts.h"
};

static char *fontpaths[NumFnt];
static Font *fonts[NumFnt];

void
initfontpaths(void)
{
       Biobufhdr *bp;
       char buf[128];
       char *s;
       int i;

       /* we don't care if getenv(2) fails */
       s = getenv("home");
       snprint(buf, sizeof(buf)-1, "%s/lib/abaco.fonts", s);
       free(s);
       if((bp=Bopen(buf, OREAD)) == nil)
               goto Default;

       for(i=0; i<NumFnt; i++)
               if((fontpaths[i]=Brdstr(bp, '\n', 1)) == nil)
                       goto Error;

       Bterm(bp);
       return;
Error:
       fprint(2, "abaco: not enough fontpaths in '%s'\n", buf);
       Bterm(bp);
       for(i--; i>=0; i--)
               free(fontpaths[i]);
Default:
       for(i=0; i<NumFnt; i++)
               fontpaths[i] = deffontpaths[i];
}

Font *
getfont(int i)
{
       if(fonts[i] == nil){
               fonts[i] = openfont(display, fontpaths[i]);
               if(fonts[i] == nil)
                       error("can't open font file");
       }
       return fonts[i];
}

typedef struct Color Color;

struct Color {
       int     rgb;
       Image   *i;
       Color   *next;
};

enum {
       NHASH = 19,
};

static Color *colortab[NHASH];

Image *
getcolor(int rgb)
{
       Color *c;
       int h;

       if(rgb == 0xFFFFFF)
               return display->white;
       else if(rgb == 0x000000)
               return display->black;

       h = rgb%NHASH;
       for(c=colortab[h]; c!=nil; c=c->next)
               if(c->rgb == rgb){
                       flushimage(display, 0); /* BUG? */
                       return c->i;
               }
       c = emalloc(sizeof(Color));
       c->i = eallocimage(display, Rect(0,0,1,1), screen->chan, 1, (rgb<<8)|0xFF);
       c->rgb = rgb;
       c->next = colortab[h];
       colortab[h] = c;

       return c->i;
}

int
plumbrunestr(Runestr *rs, char *attr)
{
       Plumbmsg *m;
       int i;

       i = -1;
       if(plumbsendfd >= 0){
               m = emalloc(sizeof(Plumbmsg));
               m->src = estrdup("abaco");
               m->dst = nil;
               m->wdir = estrdup("/tmp");
               m->type = estrdup("text");
               if(attr)
                       m->attr = plumbunpackattr(attr);
               else
                       m->attr = nil;
               m->data = smprint("%.*S", rs->nr, rs->r);
               m->ndata = -1;
               i = plumbsend(plumbsendfd, m);
               plumbfree(m);
       }
       return i;
}

int
hexdigit(int v)
{
       if(0<=v && v<=9)
               return '0' + v;
       else
               return 'A' + v - 10;
}

static int
inclass(char c, Rune* cl)
{
       int n, ans, negate, i;

       n = runestrlen(cl);
       if(n == 0)
               return 0;
       ans = 0;
       negate = 0;
       if(cl[0] == '^'){
               negate = 1;
               cl++;
               n--;
       }
       for(i=0; i<n; i++){
               if(cl[i]=='-' && i>0 && i<n-1){
                       if(c>=cl[i - 1] && c<=cl[i+1]){
                               ans = 1;
                               break;
                       }
                       i++;
               }
               else if(c == cl[i]){
                       ans = 1;
                       break;
               }
       }
       if(negate)
               ans = !ans;
       return ans;
}

Rune*
ucvt(Rune* s)
{
       Rune* u;
       char *t;
       int i, c, n, j, len;

       t = smprint("%S", s);
       n = strlen(t);
       len = 0;
       for(i=0; i<n; i++){
               c = t[i];
               if(inclass(c, L"- /$_@.!*'(),a-zA-Z0-9"))
                       len++;
               else
                       len += 3;
       }
       u = runemalloc(len+1);
       j = 0;

       for(i=0; i<n; i++){
               c = t[i];
               if(inclass(c, L"-/$_@.!*'(),a-zA-Z0-9"))
                       u[j++] = c;
               else if(c == ' ')
                       u[j++] = '+';
               else {
                       u[j++] = '%';
                       u[j++] = hexdigit((c >> 4)&15);
                       u[j++] = hexdigit(c&15);
               }
       }
       u[j] = 0;
       free(t);
       return u;
}

void
reverseimages(Iimage **head)
{
       Iimage *r, *c, *n;

       r = nil;
       for(c=*head; c!=nil; c=n){
               n = c->nextimage;
               c->nextimage = r;
               r = c;
       }
       *head = r;
}

char urlexpr[] = "^(https?|ftp|file|gopher|mailto|news|nntp|telnet|wais|"
       "prospero)://[^/]+";
Reprog  *urlprog;

int
validurl(Rune *r)
{
       Resub rs[10];

       if(urlprog == nil){
               urlprog = regcomp(urlexpr);
               if(urlprog == nil)
                       error("regcomp");
       }
       memset(rs, 0, sizeof(rs));
       if(rregexec(urlprog, r, rs, nelem(rs)) == 0)
               return FALSE;
       return TRUE;
}

static void
execproc(void *v)
{
       Exec *e;

       threadsetname("execproc");
       e = v;
       rfork(RFFDG);
       dup(e->p[0], 0);
       close(e->p[0]);
       close(e->p[1]);
       if(e->q[0]){
               dup(e->q[1], 1);
               close(e->q[0]);
               close(e->q[1]);
       }
       if(!procstderr)
               close(2);
       procexecl(e->sync, "/bin/rc", "rc", "-c", e->cmd, nil);
       error("can't exec");
}

int
pipeline(int fd, char *cmd, ...)
{
       Exec *e;
       va_list a;

       e = emalloc(sizeof(Exec));
       if(pipe(e->p)<0 || pipe(e->q)<0)
               error("can't create pipe");
       close(e->p[0]);
       e->p[0] = fd;
       va_start(a, cmd);
       e->cmd = vsmprint(cmd, a);
       va_end(a);
       e->sync = chancreate(sizeof(ulong), 0);
       if(e->sync == nil)
               error("can't create channel");
       proccreate(execproc, e, STACK);
       recvul(e->sync);
       chanfree(e->sync);
       free(e->cmd);
       close(e->p[0]);
       close(e->p[1]);
       close(e->q[1]);
       fd = e->q[0];
       free(e);
       return fd;
}

static
int
isspace(char c)
{
       return c==' ' || c== '\t' || c=='\r' || c=='\n';
}

int
findctype(char *b, int l, char *keyword, char *s)
{
       char *p, *e, c;
       int i;

       p = cistrstr(s, keyword);
       if(!p)
               return -1;
       p += strlen(keyword);
       while(*p && isspace(*p))
               p++;
       if(*p != '=')
               return -1;
       p++;
       while(*p && isspace(*p))
               p++;
       if(!*p)
               return -1;
       switch (c = *p){
       case '"':
       case '\'':
               p++;
               e = strchr(p, c);
               if(!e)
                       return -1;
               break;
       default:
               for(e = p; *e < 127 && *e > ' ' ; e++)
                       ;
       }
       i = utfnlen(p, e - p);
       if(i < 1)
               return -1;
       snprint(b, l, "%.*s", i, p);
       return 0;
}

int
xtofchar(Rune *s, Font *f, long p)
{
       Rune *r;
       int q;

       if(p == 0)
               return 0;

       q = 0;
       for(r=s; *r!=L'\0'; r++){
               p -= runestringnwidth(f, r, 1);
               if(p < 0)
                       break;
               q++;
       }
       return q;
}

int
istextsel(Page *p, Rectangle r, int *q0, int *q1, Rune *s, Font *f)
{
       int topinr, botinr;

       *q0 = *q1 = 0;
       topinr= ptinrect(p->top, r);
       if(topinr || (r.min.y>p->top.y && r.max.y<p->bot.y))
               p->selecting = TRUE;
       botinr = ptinrect(p->bot, r);
       if(botinr || r.min.y>p->bot.y)
               p->selecting = FALSE;

       if(topinr || botinr){
               if(topinr)
                       *q0 = xtofchar(s, f, p->top.x-r.min.x);
               if(botinr)
                       *q1 = xtofchar(s, f, p->bot.x-r.min.x);
               if(*q0!=0 || *q1!=0)
                       return TRUE;
       }
       return p->selecting;
}

Point
getpt(Page *p, Point xy)
{
       xy.x = xy.x-p->r.min.x+p->pos.x;
       xy.y = xy.y-p->r.min.y+p->pos.y;

       return xy;
}

void
getimage(Cimage *ci, Rune *altr)
{
       Rectangle r;
       Memimage *mi;
       Image *i, *i2;
       char buf[128];
       uchar *bits;
       int nbits;

       mi = ci->mi;
       if(mi == nil){
               snprint(buf, sizeof(buf), "[%S]", altr ? altr : L"IMG");
               r.min = Pt(0, 0);
               r.max.x = 2*Space + stringwidth(font, buf);
               r.max.y = 2*Space + font->height;
               ci->i = eallocimage(display, r, GREY1, 1, DBlack);
               r.min.x += Space;
               r.min.y += Space;
               string(ci->i, r.min, display->white, ZP, font, buf);
               return;
       }
       nbits = bytesperline(mi->r, mi->depth)*Dy(mi->r);
       bits = emalloc(nbits);
       unloadmemimage(mi, mi->r, bits, nbits);
/*
       /* get rid of alpha channel from transparent gif * /

       if(mi->depth == 16){
               for(y=1; y<nbits; y+=2)
                       bits[y>>1] = bits[y];
       }
*/
       i = eallocimage(display, mi->r, mi->chan, 0, DNofill);
       loadimage(i, i->r, bits, nbits);
       i2 = eallocimage(display, i->r, RGB24, 1, DNofill);
       draw(i2, i2->r, display->black, nil, ZP);
       draw(i2, i2->r, i, nil, i->r.min);
       free(bits);
       freememimage(mi);
       freeimage(i);
       ci->i = i2;
       ci->mi = nil;
}

static
void
fixtext1(Item **list)
{
       Itext *text, *ntext;
       Item *it, *prev;
       Rune *s, *s1, *s2;
       int n;

       if(*list == nil)
               return;

       prev = nil;
       for(it=*list; it!=nil; it=prev->next){
               if(it->tag!=Itexttag || forceitem(it))
                       goto Continue;

               text = (Itext *)it;
               s = text->s;
               while(*s && isspacerune(*s))
                       s++;
               if(!*s){
                       if(prev == nil)
                               prev = *list = it->next;
                       else
                               prev->next = it->next;

                       it->next = nil;
                       freeitems(it);
                       if(prev == nil)
                               return;
                       continue;
               }
               n = 0;
               while(s[n] && !isspacerune(s[n]))
                       n++;

               if(!s[n])
                       goto Continue;

               s1 = runemalloc(n+1);
               s1 = runemove(s1, s, n);
               s1[n] = L'\0';
               s += n;

               while(*s && isspacerune(*s))
                       s++;

               if(*s){
                       n = runestrlen(s);
                       s2 = runemalloc(n+1);
                       runemove(s2, s, n);
                       s2[n] = L'\0';
                       ntext = emalloc(sizeof(Itext));
                       ntext->s = s2;
                       ntext->ascent = text->ascent;
                       ntext->anchorid = text->anchorid;
                       ntext->state = text->state&~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
                       ntext->tag = text->tag;
                       ntext->fnt = text->fnt;
                       ntext->fg = text->fg;
                       ntext->ul = text->ul;
                       ntext->next = (Item *)text->next;
                       text->next = (Item *)ntext;
               }
               free(text->s);
               text->s = s1;
   Continue:
               prev = it;
       }
}

void
fixtext(Page *p)
{
       Tablecell *c;
       Table *t;

       fixtext1(&p->items);
       for(t=p->doc->tables; t!=nil; t=t->next)
               for(c=t->cells; c!=nil; c=c->next)
                       fixtext1(&c->content);
}

typedef struct Refresh Refresh;

struct Refresh
{
       Page *p;
       Refresh *next;
};

static Refresh *refreshs = nil;
static QLock refreshlock;

void
addrefresh(Page *p, char *fmt, ...)
{
       Refresh *r;
       Rune *s;
       va_list arg;

       if(p->aborting)
               return;

       va_start(arg, fmt);
       s = runevsmprint(fmt, arg);
       va_end(arg);
       if(s == nil)
               error("runevsmprint failed");

       qlock(&refreshlock);
       if(p->status){
               free(p->status);
               p->status = nil;
       }
       p->status = s;
       for(r=refreshs; r!=nil; r=r->next)
               if(r->p == p)
                       goto Return;

       incref(p->w);                           /* flushrefresh will decref */
       r = emalloc(sizeof(Refresh));
       r->p = p;
       r->next = refreshs;
       refreshs = r;

   Return:
       nbsendp(crefresh, nil);
       qunlock(&refreshlock);
}

/* called while row is locked */
void
flushrefresh(void)
{
       Refresh *r, *next;
       Page *p;

       qlock(&refreshlock);
       for(r=refreshs; r!=nil; r=next){
               p = r->p;
               if(p->changed==TRUE && p->aborting==FALSE){
                       p->changed = FALSE;
                       if(p->parent==nil || p->loading==FALSE)
                               pagerender(p);
                       if(!p->refresh.t)
                               pagesetrefresh(p);
               }
               if(p->status){
                       winsetstatus(p->w, p->status);
                       free(p->status);
                       p->status = nil;
               }
               winseturl(p->w);
               winsettag(p->w);
               decref(p->w);
               next = r->next;
               free(r);
       }
       refreshs = nil;
       qunlock(&refreshlock);
}

void
savemouse(Window *w)
{
       prevmouse = mouse->xy;
       mousew = w;
}

void
restoremouse(Window *w)
{
       if(mousew!=nil && mousew==w)
               moveto(mousectl, prevmouse);
       mousew = nil;
}

void
clearmouse()
{
       mousew = nil;
}

/*
* Heuristic city.
*/
Window*
makenewwindow(Page *p)
{
       Column *c;
       Window *w, *bigw, *emptyw;
       Page *emptyp;
       int i, y, el;

       if(activecol)
               c = activecol;
       else if(selpage && selpage->col)
               c = selpage->col;
       else if(p && p->col)
               c = p->col;
       else{
               if(row.ncol==0 && rowadd(&row, nil, -1)==nil)
                       error("can't make column");
               c = row.col[row.ncol-1];
       }
       activecol = c;
       if(p==nil || p->w==nil || c->nw==0)
               return coladd(c, nil, nil, -1);

       /* find biggest window and biggest blank spot */
       emptyw = c->w[0];
       bigw = emptyw;
       for(i=1; i<c->nw; i++){
               w = c->w[i];
               /* use >= to choose one near bottom of screen */
               if(Dy(w->page.all) >= Dy(bigw->page.all))
                       bigw = w;
               if(w->page.lay==nil && Dy(w->page.all) >= Dy(emptyw->page.all))
                       emptyw = w;
       }
       emptyp = &emptyw->page;
       el = Dy(emptyp->all);
       /* if empty space is big, use it */
       if(el>15 || (el>3 && el>(Dy(bigw->page.all)-1)/2))
               y = emptyp->all.max.y;
       else{
               /* if this window is in column and isn't much smaller, split it */
               if(p->col==c && Dy(p->w->r)>2*Dy(bigw->r)/3)
                       bigw = p->w;
               y = (bigw->r.min.y + bigw->r.max.y)/2;
       }
       w = coladd(c, nil, nil, y);
       colgrow(w->col, w, 1);
       return w;
}