#include <u.h>
#include <libc.h>
#include <thread.h>
#include <draw.h>
#include <mouse.h>
#include <keyboard.h>
#include <control.h>
#include <scribble.h>

int debug = 0;
typedef struct Win Win;
struct Win {
       int n;
       int dirty;
       char *label;
       Control *button;
};

Win *win;
int nwin;
int mwin;
int onwin;
int rows, cols;
int kbdonly;
int scribbleleft;
int winshow;

Channel *kc;
Channel *ec;
Channel *tc;
Rectangle rk, rs, rw;
Font *keyfont, *keyctlfont;

enum{
       Back,
       Shade,
       Light,
       Mask,
       Ncol
};

enum {
       kbdheight = 2 + 5*13,
};

enum {
       Keyback         = 0xeeee9eff,
       Keyshade                = 0xaaaa55ff,
       Keylight                = DWhite,
       Keymask         = 0x0C0C0C0C,
};

Image   *colors[Ncol];

Control *kbd;
Control *scrib;
Control *boxbox;
Controlset *cs;

int     ctldeletequits = 1;
int     wctl;

int
hidden(void)
{
       char buf[128];
       int n;

       close(wctl);
       if ((wctl = open("/dev/wctl", ORDWR)) < 0)
               return 0;
       n = read(wctl, buf, sizeof buf-1);
       if (n <= 0)
               sysfatal("wctl read: %r");
       buf[n] = 0;
       return strstr(buf, "visible") == nil;
}

void
mousemux(void *v)
{
       Mouse m;
       Channel *c;
       int ob;

       c = v;

       for(ob = 0;;ob = m.buttons){
               if(recv(c, &m) < 0)
                       break;
               if ((m.buttons & 0x20) == 0) {
                       if (ob & 0x20) {
                               /* hide button just came up */
                               if (hidden()) {
                                       if (debug) fprint(2, "unhide");
                                       if (fprint(wctl, "unhide") <= 0)
                                               fprint(2, "unhide failed: %r\n");
                               } else {
                                       if (debug) fprint(2, "hide");
                                       if (fprint(wctl, "hide") <= 0)
                                               fprint(2, "hide failed: %r\n");
                               }
                       } else
                               send(cs->mousec, &m);
               }
       }
}

void
refreshwin(void)
{
       char label[128];
       int i, fd, lfd, n, nr, nw, m;
       Dir *pd;

       if((fd = open("/dev/wsys", OREAD)) < 0)
               return;

       nw = 0;
/* i'd rather read one at a time but rio won't let me */
       while((nr=dirread(fd, &pd)) > 0){
               for(i=0; i<nr; i++){
                       n = atoi(pd[i].name);
                       sprint(label, "/dev/wsys/%d/label", n);
                       if((lfd = open(label, OREAD)) < 0)
                               continue;
                       m = read(lfd, label, sizeof(label)-1);
                       close(lfd);
                       if(m < 0)
                               continue;
                       label[m] = '\0';
                       if(nw < nwin && win[nw].n == n && strcmp(win[nw].label, label)==0){
                               nw++;
                               continue;
                       }

                       if(nw < nwin){
                               free(win[nw].label);
                               win[nw].label = nil;
                       }
                       if(nw >= mwin){
                               mwin += 8;
                               win = ctlrealloc(win, mwin*sizeof(win[0]));
                               memset(&win[mwin-8], 0, 8*sizeof(win[0]));
                       }
                       win[nw].n = n;
                       win[nw].label = ctlstrdup(label);
                       win[nw].dirty = 1;
                       sprint(label, "%d", nw);
                       if (win[nw].button == nil){
                               win[nw].button = createtextbutton(cs, label);
                               chanprint(cs->ctl, "%q font keyfont", label);
                               chanprint(cs->ctl, "%q image keyback", label);
                               chanprint(cs->ctl, "%q pressedtextcolor red", label);
                               chanprint(cs->ctl, "%q mask transparent", label);
                               chanprint(cs->ctl, "%q border 1", label);
                               chanprint(cs->ctl, "%q bordercolor black", label);
                               chanprint(cs->ctl, "%q align centerleft", label);
                               chanprint(cs->ctl, "%q size 16 %d 512 %d", label, keyfont->height+2, keyfont->height+2);
                               controlwire(win[nw].button, "event", ec);
                       }
                       if (nw >= nwin){
                               activate(win[nw].button);
                               chanprint(cs->ctl, "cols add %q", label);
                       }
                       chanprint(cs->ctl, "%q text %q", win[nw].button->name, win[nw].label);
                       nw++;
               }
       }
       for(i = nw; i < nwin; i++){
               free(win[i].label);
               win[i].label = nil;
               deactivate(win[i].button);
               chanprint(cs->ctl, "cols remove %q", win[i].button->name);
       }
       nwin = nw;
       close(fd);
       if (rw.max.x)
               chanprint(cs->ctl, "cols rect %R\ncols show", rw);
}

void
resizecontrolset(Controlset*)
{
       if(getwindow(display, Refnone) < 0)
               ctlerror("resize failed: %r");

       if (hidden()) {
               if (debug) fprint(2, "resizecontrolset: hidden\n");
               return;
       }

       rk = screen->r;
       if (winshow){
               rw = rk;
               rw.min.x = (3*rk.max.x + rk.min.x)/4;
               rk.max.x = rw.min.x;
               if (debug) fprint(2, "rw: rect %R\n", rw);
               chanprint(cs->ctl, "cols rect %R\ncols show", rw);
       }
       if (kbdonly) {
               chanprint(cs->ctl, "keyboard rect %R\nkeyboard show", rk);
       } else {
               rs = rk;
               if (scribbleleft){
                       rk.min.x = (rk.max.x + 3*rk.min.x)/4;
                       rs.max.x = rk.min.x;
               }else{
                       rk.max.x = (3*rk.max.x + rk.min.x)/4;
                       rs.min.x = rk.max.x;
               }
               chanprint(cs->ctl, "keyboard rect %R\nkeyboard show", rk);
               if (debug) fprint(2, "rk: rect %R\nkeyboard show\n", rk);
               chanprint(cs->ctl, "scribble rect %R\nscribble show", rs);
               if (debug) fprint(2, "rs: rect %R\nscribble show\n", rs);
       }
}

void
usage(void)
{
       fprint(2, "usage: keyboard\n");
       threadexitsall("usage");
}

void
timerproc(void*v)
{
       Channel *c;

       c = v;
       for(;;){
               sleep(5000);
               sendul(c, 1);
       }
}

void
watchproc(void*)
{

       for(;;){}
}

void
threadmain(int argc, char *argv[])
{
       int i, n, kbdfd;
       char str[UTFmax+1];
       Rune r;
       Mousectl        *mousectl;
       Channel *mtok;
       char *e, buf[128], *args[8];
       int fd;

       ARGBEGIN{
       case 'w':
               winshow++;
               break;
       case 'l':
               scribbleleft++;
               break;
       case 'n':
               kbdonly++;
               break;
       case 'd':
               ScribbleDebug++;
               debug++;
               break;
       default:
               usage();
       }ARGEND

       if(argc != 0)
               usage();

       kbdfd = open("/dev/kbdin", OWRITE);
       if (kbdfd < 0 && (kbdfd = open("#r/kbdin", OWRITE)) < 0) {
               if (debug) fprint(2, "open %s: %r\n", "#r/kbdin");
               kbdfd = 1;
       }

       initdraw(0, 0, "keyboard");
       mousectl = initmouse(nil, screen);

       wctl = open("/dev/wctl", ORDWR);
       if (wctl < 0) {
               fprint(2, "open %s: %r\n", "/dev/wctl");
               wctl = 2;       /* for debugging */
       }

       mtok = chancreate(sizeof(Mouse), 0);

       initcontrols();

       cs = newcontrolset(screen, nil, mtok, mousectl->resizec);

       threadcreate(mousemux, mousectl->c, 4096);

       kc = chancreate(sizeof(char*), 0);
       tc = chancreate(sizeof(int), 1);
       ec = chancreate(sizeof(char*), 1);

       colors[Back] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, Keyback);
       namectlimage(colors[Back], "keyback");
       colors[Light] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, Keylight);
       namectlimage(colors[Light], "keylight");
       colors[Shade] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, Keyshade);
       namectlimage(colors[Shade], "keyshade");
       colors[Mask] = allocimage(display, Rect(0,0,1,1), RGBA32, 1, Keymask);
       namectlimage(colors[Shade], "keymask");
       keyfont = openfont(display, "/lib/font/bit/lucidasans/boldlatin1.6.font");
       namectlfont(keyfont, "keyfont");
       keyctlfont = openfont(display, "/lib/font/bit/lucidasans/unicode.6.font");
       namectlfont(keyctlfont, "keyctlfont");

       kbd = createkeyboard(cs, "keyboard");
       chanprint(cs->ctl, "keyboard font keyfont keyctlfont");
       chanprint(cs->ctl, "keyboard image keyback");
       chanprint(cs->ctl, "keyboard light keylight");
       chanprint(cs->ctl, "keyboard mask keymask");
       chanprint(cs->ctl, "keyboard border 1");
       chanprint(cs->ctl, "keyboard size %d %d %d %d", 246, 2 + 5 * (keyfont->height + 1), 512, 256);
       controlwire(kbd, "event", kc);

       if (kbdonly == 0){
               scrib = createscribble(cs, "scribble");
               if (scrib == nil)
                       sysfatal("createscribble");
               chanprint(cs->ctl, "scribble font keyfont");
               chanprint(cs->ctl, "scribble image keyback");
               chanprint(cs->ctl, "scribble border 1");
               controlwire(scrib, "event", kc);
       }

       if (winshow){
               boxbox = createboxbox(cs, "cols");

               chanprint(cs->ctl, "cols border 2");
               chanprint(cs->ctl, "cols bordercolor keyback");
       }

       resizecontrolset(nil);

       activate(kbd);
       if (kbdonly == 0)
               activate(scrib);
       if (winshow){
               refreshwin();
               proccreate(timerproc, tc, 2048);
       }

       for(;;){
               Alt a[] = {
                       { kc, &e, CHANRCV },
                       { ec, &e, CHANRCV },
                       { tc, &n, CHANRCV },
                       { nil, nil, CHANEND }
               };
               switch(alt(a)){
               case 0: /* Keyboard */
                       n = tokenize(e, args, nelem(args));
                       if(n == 3)
                       if(strcmp(args[0], "keyboard:")==0 || strcmp(args[0], "scribble:")==0)
                       if(strcmp(args[1], "value") == 0){
                               n = strtol(args[2], 0, 0);
                               if(n <= Runemax){
                                       r = n;
                                       i = runetochar(str, &r);
                                       write(kbdfd, str, i);
                               }
                       }
                       break;
               case 1: /* Button event */
                       n = tokenize(e, args, nelem(args));
                       if (n != 3 || strcmp(args[1], "value"))
                               sysfatal("event string");
                       i = atoi(args[0]);
                       if (i < 0 || i >= nwin)
                               sysfatal("win out of range: %d of %d", i, nwin);
                       n = atoi(args[2]);
                       if (n){
                               sprint(buf, "/dev/wsys/%d/wctl", win[i].n);
                               if((fd = open(buf, OWRITE)) >= 0){
                                       while (write(fd, "top\n", 4) < 0) {
                                               /* wait until mouse comes up */
                                               rerrstr(buf, sizeof buf);
                                               if (strncmp(buf, "action disallowed when mouse active", sizeof buf)){
                                                       fprint(2, "write top: %s\n", buf);
                                                       break;
                                               }
                                               sleep(100);
                                       }
                                       if (write(fd, "current\n", 8) < 0)
                                               fprint(2, "write current: %r\n");
                                       close(fd);
                               }
                               chanprint(cs->ctl, "%q value 0", win[i].button->name);
                       }
                       break;
               case 2:
                       refreshwin();
                       break;
               }
       }
}