#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include "flayer.h"
#include "samterm.h"

int     cursorfd;
int     plumbfd = -1;
int     input;
int     got;
int     block;
int     kbdc;
int     resized;
uchar   *hostp;
uchar   *hoststop;
uchar   *plumbbase;
uchar   *plumbp;
uchar   *plumbstop;
Channel *plumbc;
Channel *hostc;
Mousectl        *mousectl;
Mouse   *mousep;
Keyboardctl *keyboardctl;
void    panic(char*);

void
initio(void)
{
       threadsetname("main");
       mousectl = initmouse(nil, display->image);
       if(mousectl == nil){
               fprint(2, "samterm: mouse init failed: %r\n");
               threadexitsall("mouse");
       }
       mousep = mousectl;
       keyboardctl = initkeyboard(nil);
       if(keyboardctl == nil){
               fprint(2, "samterm: keyboard init failed: %r\n");
               threadexitsall("kbd");
       }
       hoststart();
       if(plumbstart() < 0)
               extstart();
}

void
getmouse(void)
{
       if(readmouse(mousectl) < 0)
               panic("mouse");
}

void
mouseunblock(void)
{
       got &= ~(1<<RMouse);
}

void
kbdblock(void)
{               /* ca suffit */
       block = (1<<RKeyboard)|(1<<RPlumb);
}

int
button(int but)
{
       getmouse();
       return mousep->buttons&(1<<(but-1));
}

void
externload(int i)
{
       plumbbase = malloc(plumbbuf[i].n);
       if(plumbbase == 0)
               return;
       memmove(plumbbase, plumbbuf[i].data, plumbbuf[i].n);
       plumbp = plumbbase;
       plumbstop = plumbbase + plumbbuf[i].n;
       got |= 1<<RPlumb;
}

int
waitforio(void)
{
       Alt alts[NRes+1];
       Rune r;
       int i;
       ulong type;

again:

       alts[RPlumb].c = plumbc;
       alts[RPlumb].v = &i;
       alts[RPlumb].op = CHANRCV;
       if((block & (1<<RPlumb)) || plumbc == nil)
               alts[RPlumb].op = CHANNOP;

       alts[RHost].c = hostc;
       alts[RHost].v = &i;
       alts[RHost].op = CHANRCV;
       if(block & (1<<RHost))
               alts[RHost].op = CHANNOP;

       alts[RKeyboard].c = keyboardctl->c;
       alts[RKeyboard].v = &r;
       alts[RKeyboard].op = CHANRCV;
       if(block & (1<<RKeyboard))
               alts[RKeyboard].op = CHANNOP;

       alts[RMouse].c = mousectl->c;
       alts[RMouse].v = &mousectl->Mouse;
       alts[RMouse].op = CHANRCV;
       if(block & (1<<RMouse))
               alts[RMouse].op = CHANNOP;

       alts[RResize].c = mousectl->resizec;
       alts[RResize].v = nil;
       alts[RResize].op = CHANRCV;
       if(block & (1<<RResize))
               alts[RResize].op = CHANNOP;

       alts[NRes].op = CHANEND;

       if(got & ~block)
               return got & ~block;
       if(display->bufp > display->buf)
               flushimage(display, 1);
       type = alt(alts);
       switch(type){
       case RHost:
               hostp = hostbuf[i].data;
               hoststop = hostbuf[i].data + hostbuf[i].n;
               block = 0;
               break;
       case RPlumb:
               externload(i);
               break;
       case RKeyboard:
               kbdc = r;
               break;
       case RMouse:
               break;
       case RResize:
               resized = 1;
               /* do the resize in line if we've finished initializing and we're not in a blocking state */
               if(hasunlocked && block==0 && RESIZED())
                       resize();
               goto again;
       }
       got |= 1<<type;
       return got;
}

int
rcvchar(void)
{
       int c;

       if(!(got & (1<<RHost)))
               return -1;
       c = *hostp++;
       if(hostp == hoststop)
               got &= ~(1<<RHost);
       return c;
}

char*
rcvstring(void)
{
       *hoststop = 0;
       got &= ~(1<<RHost);
       return (char*)hostp;
}

int
getch(void)
{
       int c;

       while((c = rcvchar()) == -1){
               block = ~(1<<RHost);
               waitforio();
               block = 0;
       }
       return c;
}

int
externchar(void)
{
       Rune r;

   loop:
       if(got & ((1<<RPlumb) & ~block)){
               plumbp += chartorune(&r, (char*)plumbp);
               if(plumbp >= plumbstop){
                       got &= ~(1<<RPlumb);
                       free(plumbbase);
               }
               if(r == 0)
                       goto loop;
               return r;
       }
       return -1;
}

int kpeekc = -1;
int
ecankbd(void)
{
       Rune r;

       if(kpeekc >= 0)
               return 1;
       if(nbrecv(keyboardctl->c, &r) > 0){
               kpeekc = r;
               return 1;
       }
       return 0;
}

int
ekbd(void)
{
       int c;
       Rune r;

       if(kpeekc >= 0){
               c = kpeekc;
               kpeekc = -1;
               return c;
       }
       if(recv(keyboardctl->c, &r) < 0){
               fprint(2, "samterm: keybard recv error: %r\n");
               panic("kbd");
       }
       return r;
}

int
kbdchar(void)
{
       int c, i;

       c = externchar();
       if(c > 0)
               return c;
       if(got & (1<<RKeyboard)){
               c = kbdc;
               kbdc = -1;
               got &= ~(1<<RKeyboard);
               return c;
       }
       while(plumbc!=nil && nbrecv(plumbc, &i)>0){
               externload(i);
               c = externchar();
               if(c > 0)
                       return c;
       }
       if(!ecankbd())
               return -1;
       return ekbd();
}

int
qpeekc(void)
{
       return kbdc;
}

int
RESIZED(void)
{
       if(resized){
               if(getwindow(display, Refnone) < 0)
                       panic("can't reattach to window");
               resized = 0;
               return 1;
       }
       return 0;
}