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

#define HSIZE   3       /* Type + short count */
Header  h;
uchar   indata[DATASIZE+1];     /* room for NUL */
uchar   outdata[DATASIZE];
short   outcount;
int     hversion;
int     exiting;

void    inmesg(Hmesg, int);
int     inshort(int);
long    inlong(int);
vlong   invlong(int);
void    hsetdot(int, long, long);
void    hmoveto(int, long);
void    hsetsnarf(int);
void    hplumb(int);
void    clrlock(void);
int     snarfswap(char*, int, char**);

void
rcv(void)
{
       int c;
       static state = 0;
       static count = 0;
       static i = 0;
       static int errs = 0;

       while((c=rcvchar()) != -1)
               switch(state){
               case 0:
                       h.type = c;
                       state++;
                       break;

               case 1:
                       h.count0 = c;
                       state++;
                       break;

               case 2:
                       h.count1 = c;
                       count = h.count0|(h.count1<<8);
                       i = 0;
                       if(count > DATASIZE){
                               if(++errs < 5){
                                       dumperrmsg(count, h.type, h.count0, c);
                                       state = 0;
                                       continue;
                               }
                               fprint(2, "type %d count %d\n", h.type, count);
                               panic("count>DATASIZE");
                       }
                       if(count == 0)
                               goto zerocount;
                       state++;
                       break;

               case 3:
                       indata[i++] = c;
                       if(i == count){
               zerocount:
                               indata[i] = 0;
                               inmesg(h.type, count);
                               state = count = 0;
                               continue;
                       }
                       break;
               }
}

Text *
whichtext(int tg)
{
       int i;

       for(i=0; i<nname; i++)
               if(tag[i] == tg)
                       return text[i];
       panic("whichtext");
       return 0;
}

void
inmesg(Hmesg type, int count)
{
       Text *t;
       int i, m;
       long l;
       Flayer *lp;

       m = inshort(0);
       l = inlong(2);
       switch(type){
       case -1:
               panic("rcv error");
       default:
               fprint(2, "type %d\n", type);
               panic("rcv unknown");

       case Hversion:
               hversion = m;
               break;

       case Hbindname:
               l = invlong(2);         /* for 64-bit pointers */
               if((i=whichmenu(m)) < 0)
                       break;
               /* in case of a race, a bindname may already have occurred */
               if((t=whichtext(m)) == 0)
                       t=(Text *)l;
               else    /* let the old one win; clean up the new one */
                       while(((Text *)l)->nwin>0)
                               closeup(&((Text *)l)->l[((Text *)l)->front]);
               text[i] = t;
               text[i]->tag = m;
               break;

       case Hcurrent:
               if(whichmenu(m)<0)
                       break;
               t = whichtext(m);
               i = which && ((Text *)which->user1)==&cmd && m!=cmd.tag;
               if(t==0 && (t = sweeptext(0, m))==0)
                       break;
               if(t->l[t->front].textfn==0)
                       panic("Hcurrent");
               lp = &t->l[t->front];
               if(i){
                       flupfront(lp);
                       flborder(lp, 0);
                       work = lp;
               }else
                       current(lp);
               break;

       case Hmovname:
               if((m=whichmenu(m)) < 0)
                       break;
               t = text[m];
               l = tag[m];
               i = name[m][0];
               text[m] = 0;    /* suppress panic in menudel */
               menudel(m);
               if(t == &cmd)
                       m = 0;
               else{
                       if (nname>0 && text[0]==&cmd)
                               m = 1;
                       else m = 0;
                       for(; m<nname; m++)
                               if(strcmp((char*)indata+2, (char*)name[m]+1)<0)
                                       break;
               }
               menuins(m, indata+2, t, i, (int)l);
               break;

       case Hgrow:
               if(whichmenu(m) >= 0)
                       hgrow(m, l, inlong(6), 1);
               break;

       case Hnewname:
               menuins(0, (uchar *)"", (Text *)0, ' ', m);
               break;

       case Hcheck0:
               i = whichmenu(m);
               if(i>=0) {
                       t = text[i];
                       if(t)
                               t->lock++;
                       outTs(Tcheck, m);
               }
               break;

       case Hcheck:
               i = whichmenu(m);
               if(i>=0) {
                       t = text[i];
                       if(t && t->lock)
                               t->lock--;
                       hcheck(m);
               }
               break;

       case Hunlock:
               clrlock();
               break;

       case Hdata:
               if(whichmenu(m) >= 0)
                       l += hdata(m, l, indata+6, count-6);
       Checkscroll:
               if(m == cmd.tag){
                       for(i=0; i<NL; i++){
                               lp = &cmd.l[i];
                               if(lp->textfn)
                                       center(lp, l>=0? l : lp->p1);
                       }
               }
               break;

       case Horigin:
               if(whichmenu(m) >= 0)
                       horigin(m, l);
               break;

       case Hunlockfile:
               if(whichmenu(m)>=0 && (t = whichtext(m))->lock){
                       --t->lock;
                       l = -1;
                       goto Checkscroll;
               }
               break;

       case Hsetdot:
               if(whichmenu(m) >= 0)
                       hsetdot(m, l, inlong(6));
               break;

       case Hgrowdata:
               if(whichmenu(m)<0)
                       break;
               hgrow(m, l, inlong(6), 0);
               whichtext(m)->lock++;   /* fake the request */
               l += hdata(m, l, indata+10, count-10);
               goto Checkscroll;

       case Hmoveto:
               if(whichmenu(m)>=0)
                       hmoveto(m, l);
               break;

       case Hclean:
               if((m = whichmenu(m)) >= 0)
                       name[m][0] = ' ';
               break;

       case Hdirty:
               if((m = whichmenu(m))>=0)
                       name[m][0] = '\'';
               break;

       case Hdelname:
               if((m=whichmenu(m)) >= 0)
                       menudel(m);
               break;

       case Hcut:
               if(whichmenu(m) >= 0)
                       hcut(m, l, inlong(6));
               break;

       case Hclose:
               if(whichmenu(m)<0 || (t = whichtext(m))==0)
                       break;
               l = t->nwin;
               for(i = 0,lp = t->l; l>0 && i<NL; i++,lp++)
                       if(lp->textfn){
                               closeup(lp);
                               --l;
                       }
               break;

       case Hsetpat:
               setpat((char *)indata);
               break;

       case Hsetsnarf:
               hsetsnarf(m);
               break;

       case Hsnarflen:
               snarflen = inlong(0);
               break;

       case Hack:
               outT0(Tack);
               break;

       case Hexit:
               exiting = 1;
               outT0(Texit);
               threadexitsall(nil);
               break;

       case Hplumb:
               hplumb(m);
               break;
       }
}

void
setlock(void)
{
       hostlock++;
       setcursor(mousectl, cursor = &lockarrow);
}

void
clrlock(void)
{
       hasunlocked = 1;
       if(hostlock > 0)
               hostlock--;
       if(hostlock == 0)
               setcursor(mousectl, cursor=(Cursor *)0);
}

void
startfile(Text *t)
{
       outTsv(Tstartfile, t->tag, (vlong)t);   /* for 64-bit pointers */
       setlock();
}

void
startnewfile(int type, Text *t)
{
       t->tag = Untagged;
       outTv(type, (vlong)t);                  /* for 64-bit pointers */
}

int
inshort(int n)
{
       return indata[n]|(indata[n+1]<<8);
}

long
inlong(int n)
{
       return indata[n]|(indata[n+1]<<8)|
               ((long)indata[n+2]<<16)|((long)indata[n+3]<<24);
}

vlong
invlong(int n)
{
       vlong v;

       v = (indata[n+7]<<24) | (indata[n+6]<<16) | (indata[n+5]<<8) | indata[n+4];
       v = (v<<16) | (indata[n+3]<<8) | indata[n+2];
       v = (v<<16) | (indata[n+1]<<8) | indata[n];
       return v;
}

void
outT0(Tmesg type)
{
       outstart(type);
       outsend();
}

void
outTl(Tmesg type, long l)
{
       outstart(type);
       outlong(l);
       outsend();
}

void
outTs(Tmesg type, int s)
{
       outstart(type);
       outshort(s);
       outsend();
}

void
outTss(Tmesg type, int s1, int s2)
{
       outstart(type);
       outshort(s1);
       outshort(s2);
       outsend();
}

void
outTsll(Tmesg type, int s1, long l1, long l2)
{
       outstart(type);
       outshort(s1);
       outlong(l1);
       outlong(l2);
       outsend();
}

void
outTsl(Tmesg type, int s1, long l1)
{
       outstart(type);
       outshort(s1);
       outlong(l1);
       outsend();
}

void
outTsv(Tmesg type, int s1, vlong v1)
{
       outstart(type);
       outshort(s1);
       outvlong(v1);
       outsend();
}

void
outTv(Tmesg type, vlong v1)
{
       outstart(type);
       outvlong(v1);
       outsend();
}

void
outTslS(Tmesg type, int s1, long l1, Rune *s)
{
       char buf[DATASIZE*UTFmax+1];
       char *c;

       outstart(type);
       outshort(s1);
       outlong(l1);
       c = buf;
       while(*s)
               c += runetochar(c, s++);
       *c++ = 0;
       outcopy(c-buf, (uchar *)buf);
       outsend();
}

void
outTsls(Tmesg type, int s1, long l1, int s2)
{
       outstart(type);
       outshort(s1);
       outlong(l1);
       outshort(s2);
       outsend();
}

void
outstart(Tmesg type)
{
       outdata[0] = type;
       outcount = 0;
}

void
outcopy(int count, uchar *data)
{
       while(count--)
               outdata[HSIZE+outcount++] = *data++;
}

void
outshort(int s)
{
       uchar buf[2];

       buf[0]=s;
       buf[1]=s>>8;
       outcopy(2, buf);
}

void
outlong(long l)
{
       uchar buf[4];

       buf[0]=l;
       buf[1]=l>>8;
       buf[2]=l>>16;
       buf[3]=l>>24;
       outcopy(4, buf);
}

void
outvlong(vlong v)
{
       int i;
       uchar buf[8];

       for(i = 0; i < sizeof(buf); i++){
               buf[i] = v;
               v >>= 8;
       }

       outcopy(8, buf);
}

void
outsend(void)
{
       if(outcount>DATASIZE-HSIZE)
               panic("outcount>sizeof outdata");
       outdata[1]=outcount;
       outdata[2]=outcount>>8;
       if(write(1, (char *)outdata, outcount+HSIZE)!=outcount+HSIZE)
               panic("write error");
}


void
hsetdot(int m, long p0, long p1)
{
       Text *t = whichtext(m);
       Flayer *l = &t->l[t->front];

       flushtyping(1);
       flsetselect(l, p0, p1);
}

void
horigin(int m, long p0)
{
       Text *t = whichtext(m);
       Flayer *l = &t->l[t->front];
       long a;
       ulong n;
       Rune *r;

       if(!flprepare(l)){
               l->origin = p0;
               return;
       }
       a = p0-l->origin;
       if(a>=0 && a<l->f.nchars)
               frdelete(&l->f, 0, a);
       else if(a<0 && -a<l->f.nchars){
               r = rload(&t->rasp, p0, l->origin, &n);
               frinsert(&l->f, r, r+n, 0);
       }else
               frdelete(&l->f, 0, l->f.nchars);
       l->origin = p0;
       scrdraw(l, t->rasp.nrunes);
       if(l->visible==Some)
               flrefresh(l, l->entire, 0);
       hcheck(m);
}

void
hmoveto(int m, long p0)
{
       Text *t = whichtext(m);
       Flayer *l = &t->l[t->front];

       if(p0<l->origin || p0-l->origin>l->f.nchars*9/10)
               outTsll(Torigin, m, p0, 2L);
}

void
hcheck(int m)
{
       Flayer *l;
       Text *t;
       int reqd = 0, i;
       long n, nl, a;
       Rune *r;

       if(m == Untagged)
               return;
       t = whichtext(m);
       if(t == 0)              /* possible in a half-built window */
               return;
       for(l = &t->l[0], i = 0; i<NL; i++, l++){
               if(l->textfn==0 || !flprepare(l))       /* BUG: don't
                                                          need this if BUG below
                                                          is fixed */
                       continue;
               a = t->l[i].origin;
               n = rcontig(&t->rasp, a, a+l->f.nchars, 1);
               if(n<l->f.nchars)       /* text missing in middle of screen */
                       a+=n;
               else{                   /* text missing at end of screen? */
       Again:
                       if(l->f.lastlinefull)
                               goto Checksel;  /* all's well */
                       a = t->l[i].origin+l->f.nchars;
                       n = t->rasp.nrunes-a;
                       if(n==0)
                               goto Checksel;
                       if(n>TBLOCKSIZE)
                               n = TBLOCKSIZE;
                       n = rcontig(&t->rasp, a, a+n, 1);
                       if(n>0){
                               rload(&t->rasp, a, a+n, 0);
                               nl = l->f.nchars;
                               r = scratch;
                               flinsert(l, r, r+n, l->origin+nl);
                               if(nl == l->f.nchars)   /* made no progress */
                                       goto Checksel;
                               goto Again;
                       }
               }
               if(!reqd){
                       n = rcontig(&t->rasp, a, a+TBLOCKSIZE, 0);
                       if(n <= 0)
                               panic("hcheck request==0");
                       outTsls(Trequest, m, a, (int)n);
                       outTs(Tcheck, m);
                       t->lock++;      /* for the Trequest */
                       t->lock++;      /* for the Tcheck */
                       reqd++;
               }
           Checksel:
               flsetselect(l, l->p0, l->p1);
       }
}

void
flnewlyvisible(Flayer *l)
{
       hcheck(((Text *)l->user1)->tag);
}

void
hsetsnarf(int nc)
{
       char *s2;
       char *s1;
       int i;
       int n;

       setcursor(mousectl, &deadmouse);
       s2 = alloc(nc+1);
       for(i=0; i<nc; i++)
               s2[i] = getch();
       s2[nc] = 0;
       n = snarfswap(s2, nc, &s1);
       if(n >= 0){
               if(!s1)
                       n = 0;
               s1 = realloc(s1, n+1);
               if (!s1)
                       panic("realloc");
               s1[n] = 0;
               snarflen = n;
               outTs(Tsetsnarf, n);
               if(n>0 && write(1, s1, n)!=n)
                       panic("snarf write error");
               free(s1);
       }else
               outTs(Tsetsnarf, 0);
       free(s2);
       setcursor(mousectl, cursor);
}

void
hplumb(int nc)
{
       int i;
       char *s;
       Plumbmsg *m;

       s = alloc(nc);
       for(i=0; i<nc; i++)
               s[i] = getch();
       if(plumbfd >= 0){
               m = plumbunpack(s, nc);
               if(m != 0)
                       plumbsend(plumbfd, m);
               plumbfree(m);
       }
       free(s);
}

void
hgrow(int m, long a, long new, int req)
{
       int i;
       Flayer *l;
       Text *t = whichtext(m);
       long o, b;

       if(new <= 0)
               panic("hgrow");
       rresize(&t->rasp, a, 0L, new);
       for(l = &t->l[0], i = 0; i<NL; i++, l++){
               if(l->textfn == 0)
                       continue;
               o = l->origin;
               b = a-o-rmissing(&t->rasp, o, a);
               if(a < o)
                       l->origin+=new;
               if(a < l->p0)
                       l->p0+=new;
               if(a < l->p1)
                       l->p1+=new;
               /* must prevent b temporarily becoming unsigned */
               if(!req || a<o || (b>0 && b>l->f.nchars) ||
                   (l->f.nchars==0 && a-o>0))
                       continue;
               if(new>TBLOCKSIZE)
                       new = TBLOCKSIZE;
               outTsls(Trequest, m, a, (int)new);
               t->lock++;
               req = 0;
       }
}

int
hdata1(Text *t, long a, Rune *r, int len)
{
       int i;
       Flayer *l;
       long o, b;

       for(l = &t->l[0], i=0; i<NL; i++, l++){
               if(l->textfn==0)
                       continue;
               o = l->origin;
               b = a-o-rmissing(&t->rasp, o, a);
               /* must prevent b temporarily becoming unsigned */
               if(a<o || (b>0 && b>l->f.nchars))
                       continue;
               flinsert(l, r, r+len, o+b);
       }
       rdata(&t->rasp, a, a+len, r);
       rclean(&t->rasp);
       return len;
}

int
hdata(int m, long a, uchar *s, int len)
{
       int i, w;
       Text *t = whichtext(m);
       Rune buf[DATASIZE], *r;

       if(t->lock)
               --t->lock;
       if(len == 0)
               return 0;
       r = buf;
       for(i=0; i<len; i+=w,s+=w)
               w = chartorune(r++, (char*)s);
       return hdata1(t, a, buf, r-buf);
}

int
hdatarune(int m, long a, Rune *r, int len)
{
       Text *t = whichtext(m);

       if(t->lock)
               --t->lock;
       if(len == 0)
               return 0;
       return hdata1(t, a, r, len);
}

void
hcut(int m, long a, long old)
{
       Flayer *l;
       Text *t = whichtext(m);
       int i;
       long o, b;

       if(t->lock)
               --t->lock;
       for(l = &t->l[0], i = 0; i<NL; i++, l++){
               if(l->textfn == 0)
                       continue;
               o = l->origin;
               b = a-o-rmissing(&t->rasp, o, a);
               /* must prevent b temporarily becoming unsigned */
               if((b<0 || b<l->f.nchars) && a+old>=o){
                       fldelete(l, b<0? o : o+b,
                           a+old-rmissing(&t->rasp, o, a+old));
               }
               if(a+old<o)
                       l->origin-=old;
               else if(a<=o)
                       l->origin = a;
               if(a+old<l->p0)
                       l->p0-=old;
               else if(a<=l->p0)
                       l->p0 = a;
               if(a+old<l->p1)
                       l->p1-=old;
               else if(a<=l->p1)
                       l->p1 = a;
       }
       rresize(&t->rasp, a, old, 0L);
       rclean(&t->rasp);
}