#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
#include <panel.h>
#include "pldefs.h"
typedef struct List List;
struct List{
       void (*hit)(Panel *, int, int); /* call user back on hit */
       char *(*gen)(Panel *, int);     /* return text given index or 0 if out of range */
       int lo;                         /* indices of first, last items displayed */
       int sel;                        /* index of hilited item */
       int len;                        /* # of items in list */
       Rectangle listr;
       Point minsize;
       int buttons;
};
#define MAXHGT  12
void pl_listsel(Panel *p, int sel, int on){
       List *lp;
       int hi;
       Rectangle r;
       lp=p->data;
       hi=lp->lo+(lp->listr.max.y-lp->listr.min.y)/font->height;
       if(lp->lo>=0 && lp->lo<=sel && sel<hi && sel<lp->len){
               r=lp->listr;
               r.min.y+=(sel-lp->lo)*font->height;
               r.max.y=r.min.y+font->height;
               if(on)
                       pl_highlight(p->b, r);
               else{
                       pl_fill(p->b, r);
                       pl_drawicon(p->b, r, PLACEW, 0, lp->gen(p, sel));
               }
       }
}
void pl_liststrings(Panel *p, int lo, int hi, Rectangle r){
       Panel *sb;
       List *lp;
       char *s;
       int i;
       lp=p->data;
       for(i=lo;i!=hi && (s=lp->gen(p, i));i++){
               r.max.y=r.min.y+font->height;
               pl_drawicon(p->b, r, PLACEW, 0, s);
               r.min.y+=font->height;
       }
       if(lo<=lp->sel && lp->sel<hi) pl_listsel(p, lp->sel, 1);
       sb=p->yscroller;
       if(sb && sb->setscrollbar)
               sb->setscrollbar(sb, lp->lo,
                       lp->lo+(lp->listr.max.y-lp->listr.min.y)/font->height, lp->len);
}
void pl_drawlist(Panel *p){
       List *lp;
       lp=p->data;
       lp->listr=pl_box(p->b, p->r, UP);
       pl_liststrings(p, lp->lo, lp->lo+(lp->listr.max.y-lp->listr.min.y)/font->height,
               lp->listr);
}
int pl_hitlist(Panel *p, Mouse *m){
       int oldsel, hitme;
       Point ul, size;
       List *lp;
       lp=p->data;
       hitme=0;
       ul=p->r.min;
       size=subpt(p->r.max, p->r.min);
       pl_interior(p->state, &ul, &size);
       oldsel=lp->sel;
       if(m->buttons&OUT){
               p->state=UP;
               if(m->buttons&~OUT) lp->sel=-1;
       }
       else if(p->state==DOWN || m->buttons&7){
               lp->sel=(m->xy.y-ul.y)/font->height+lp->lo;
               if(m->buttons&7){
                       lp->buttons=m->buttons;
                       p->state=DOWN;
               }
               else{
                       hitme=1;
                       p->state=UP;
               }
       }
       if(oldsel!=lp->sel){
               pl_listsel(p, oldsel, 0);
               pl_listsel(p, lp->sel, 1);
       }
       if(hitme && 0<=lp->sel && lp->sel<lp->len && lp->hit)
               lp->hit(p, lp->buttons, lp->sel);
       return 0;
}
void pl_scrolllist(Panel *p, int dir, int buttons, int val, int len){
       Point ul, size;
       int nlist, oldlo, hi, nline, y;
       List *lp;
       Rectangle r;
       lp=p->data;
       ul=p->r.min;
       size=subpt(p->r.max, p->r.min);
       pl_interior(p->state, &ul, &size);
       nlist=size.y/font->height;
       oldlo=lp->lo;
       if(dir==VERT) switch(buttons){
       case 1: lp->lo-=nlist*val/len; break;
       case 2: lp->lo=lp->len*val/len; break;
       case 4: lp->lo+=nlist*val/len; break;
       }
       if(lp->lo<0) lp->lo=0;
       if(lp->lo>=lp->len) lp->lo=lp->len-1;
       if(lp->lo==oldlo) return;
       p->scr.pos.y=lp->lo;
       r=lp->listr;
       nline=(r.max.y-r.min.y)/font->height;
       hi=lp->lo+nline;
       if(hi<=oldlo || lp->lo>=oldlo+nline){
               pl_box(p->b, r, PASSIVE);
               pl_liststrings(p, lp->lo, hi, r);
       }
       else if(lp->lo<oldlo){
               y=r.min.y+(oldlo-lp->lo)*font->height;
               pl_cpy(p->b, Pt(r.min.x, y),
                       Rect(r.min.x, r.min.y, r.max.x, r.min.y+(hi-oldlo)*font->height));
               r.max.y=y;
               pl_box(p->b, r, PASSIVE);
               pl_liststrings(p, lp->lo, oldlo, r);
       }
       else{
               pl_cpy(p->b, r.min, Rect(r.min.x, r.min.y+(lp->lo-oldlo)*font->height,
                       r.max.x, r.max.y));
               r.min.y=r.min.y+(oldlo+nline-lp->lo)*font->height;
               pl_box(p->b, r, PASSIVE);
               pl_liststrings(p, oldlo+nline, hi, r);
       }
}
void pl_typelist(Panel *g, Rune c){
       USED(g, c);
}
Point pl_getsizelist(Panel *p, Point children){
       USED(children);
       return pl_boxsize(((List *)p->data)->minsize, p->state);
}
void pl_childspacelist(Panel *g, Point *ul, Point *size){
       USED(g, ul, size);
}
void plinitlist(Panel *v, int flags, char *(*gen)(Panel *, int), int nlist, void (*hit)(Panel *, int, int)){
       List *lp;
       int wid, max;
       char *str;
       lp=v->data;
       v->flags=flags|LEAF;
       v->state=UP;
       v->draw=pl_drawlist;
       v->hit=pl_hitlist;
       v->type=pl_typelist;
       v->getsize=pl_getsizelist;
       v->childspace=pl_childspacelist;
       lp->gen=gen;
       lp->hit=hit;
       max=0;
       for(lp->len=0;str=gen(v, lp->len);lp->len++){
               wid=stringwidth(font, str);
               if(wid>max) max=wid;
       }
       if(flags&(FILLX|EXPAND)){
               for(lp->len=0;gen(v, lp->len);lp->len++);
               lp->minsize=Pt(0, nlist*font->height);
       }
       else{
               max=0;
               for(lp->len=0;str=gen(v, lp->len);lp->len++){
                       wid=stringwidth(font, str);
                       if(wid>max) max=wid;
               }
               lp->minsize=Pt(max, nlist*font->height);
       }
       lp->sel=-1;
       lp->lo=0;
       v->scroll=pl_scrolllist;
       v->scr.pos=Pt(0,0);
       v->scr.size=Pt(0,lp->len);
       v->kind="list";
}
Panel *pllist(Panel *parent, int flags, char *(*gen)(Panel *, int), int nlist, void (*hit)(Panel *, int, int)){
       Panel *v;
       v=pl_newpanel(parent, sizeof(List));
       plinitlist(v, flags, gen, nlist, hit);
       return v;
}