#include <u.h>
#include <libc.h>
#include <draw.h>

#include "cons.h"

#include <thread.h>
#include <fcall.h>
#include <9p.h>

#include <bio.h>
#include <mouse.h>
#include <keyboard.h>
#include <plumb.h>

enum menuact2{
       Mbackup,
       Mforward,
       Mreset,
       Mpaste,
       Msnarf,
       Mplumb,
       Mpage,
};

enum menuact3{
       M24x80,
       Mcrnl,
       Mnl,
       Mraw,
       Mblocksel,
       Mexit,
};

char    *menutext2[] = {
       [Mbackup]       "backup",
       [Mforward]      "forward",
       [Mreset]        "reset",
       [Mpaste]        "paste",
       [Msnarf]        "snarf",
       [Mplumb]        "plumb",
       [Mpage]         "page",
       nil
};

char    *menutext3[] = {
       [M24x80]        "24x80",
       [Mcrnl]         "crnl",
       [Mnl]           "nl",
       [Mraw]          "raw",
       [Mblocksel]     "blocksel",
       [Mexit]         "exit",
       nil
};

/* variables associated with the screen */

int     x, y;   /* character positions */
Rune    *backp;
int     backc;
int     nbacklines;
int     xmax, ymax;
int     blocked;
int     resize_flag = 1;
int     pagemode;
int     olines;
int     peekc;
int     blocksel = 0;
int     cursoron = 1;
int     hostclosed = 0;
Menu    menu2;
Menu    menu3;
Rune    *histp;
Rune    hist[HISTSIZ];
Rune    *onscreenrbuf;
uchar   *onscreenabuf;
uchar   *onscreencbuf;

#define onscreenr(x, y) &onscreenrbuf[((y)*(xmax+2) + (x))]
#define onscreena(x, y) &onscreenabuf[((y)*(xmax+2) + (x))]
#define onscreenc(x, y) &onscreencbuf[((y)*(xmax+2) + (x))]

uchar   *screenchangebuf;
uint    scrolloff;

#define screenchange(y) screenchangebuf[((y)+scrolloff) % (ymax+1)]

int     yscrmin, yscrmax;
int     attr, defattr;

Rectangle selrect;

Image   *cursorsave;
Image   *bordercol;
Image   *colors[8];
Image   *hicolors[8];
Image   *red;
Image   *green;
Image   *fgcolor;
Image   *bgcolor;
Image   *fgselected;
Image   *bgselected;
Image   *highlight;

uint rgbacolors[8] = {
       0x000000FF,     /* black */
       0xAA0000FF,     /* red */
       0x00AA00FF,     /* green */
       0xFF5500FF,     /* brown */
       0x0000FFFF,     /* blue */
       0xAA00AAFF,     /* purple */
       0x00AAAAFF,     /* cyan */
       0x7F7F7FFF,     /* white */
};

ulong rgbahicolors[8] = {
       0x555555FF,     /* light black aka grey */
       0xFF5555FF,     /* light red */
       0x55FF55FF,     /* light green */
       0xFFFF55FF,     /* light brown aka yellow */
       0x5555FFFF,     /* light blue */
       0xFF55FFFF,     /* light purple */
       0x55FFFFFF,     /* light cyan */
       0xFFFFFFFF,     /* light grey aka white */
};

/* terminal control */
struct ttystate ttystate[2] = { {0, 1}, {0, 0} };

Point   margin;
Point   ftsize;

Rune    kbdchar;

#define button1()       ((mc->buttons & 07)==1)
#define button2()       ((mc->buttons & 07)==2)
#define button3()       ((mc->buttons & 07)==4)

Mousectl        *mc;
Keyboardctl     *kc;
Channel         *hc[2];
Consstate       cs[1];

int     nocolor;
int     logfd = -1;
int     hostpid = -1;
Biobuf  *snarffp = 0;
Rune    *hostbuf, *hostbufp;
char    *hostin;
char    echo_input[BSIZE];
char    *echop = echo_input;            /* characters to echo, after canon */
char    sendbuf[BSIZE]; /* hope you can't type ahead more than BSIZE chars */
char    *sendbufp = sendbuf;

char *term;
struct funckey *fk, *appfk;

/* functions */
int     waitchar(void);
void    waitio(void);
int     rcvchar(void);
void    bigscroll(void);
void    readmenu(void);
void    selecting(void);
int     selected(int, int);
void    resized(void);
void    drawcursor(void);
void    send_interrupt(void);
int     alnum(int);
void    escapedump(int,uchar *,int);
void    paste(void);
void    snarfsel(void);
void    plumbsel(void);

static Channel *pidchan;

static void
runcmd(void *args)
{
       char **argv = args;
       char *cmd;

       rfork(RFNAMEG);
       mountcons();

       rfork(RFFDG);
       close(0);
       open("/dev/cons", OREAD);
       close(1);
       open("/dev/cons", OWRITE);
       dup(1, 2);

       cmd = nil;
       while(*argv != nil){
               if(cmd == nil)
                       cmd = strdup(*argv);
               else
                       cmd = smprint("%s %q", cmd, *argv);
               argv++;
       }

       procexecl(pidchan, "/bin/rc", "rcX", cmd == nil ? nil : "-c", cmd, nil);
       sysfatal("%r");
}

void
send_interrupt(void)
{
       if(hostpid > 0)
               postnote(PNGROUP, hostpid, "interrupt");
}

void
sendnchars(int n, char *p)
{
       if((n = utfnlen(p, n)) < 1)
               return;
       hostin = smprint("%.*s", n, p);
       while(hostin != nil){
               if(nbsendp(hc[0], hostin)){
                       hostin = nil;
                       break;
               }
               drawcursor();
               waitio();
               if(resize_flag)
                       resized();
       }
}

static void
shutdown(void)
{
       send_interrupt();
       threadexitsall(nil);
}

static void
catch(void*, char*)
{
       shutdown();
}

void
usage(void)
{
       fprint(2, "usage: %s [-2abcrx] [-f font] [-l logfile] [cmd...]\n", argv0);
       exits("usage");
}

void
threadmain(int argc, char **argv)
{
       int rflag;
       int i, blkbg;
       char *fontname, *p;

       fontname = nil;
       fk = ansifk;
       term = "vt100";
       blkbg = 0;
       rflag = 0;
       attr = defattr;
       ARGBEGIN{
       case '2':
               fk = vt220fk;
               term = "vt220";
               break;
       case 'a':
               term = "ansi";
               break;
       case 'b':
               blkbg = 1;              /* e.g., for linux colored output */
               break;
       case 'c':
               nocolor = 1;
               break;
       case 'f':
               fontname = EARGF(usage());
               break;
       case 'l':
               p = EARGF(usage());
               logfd = create(p, OWRITE|OCEXEC, 0666);
               if(logfd < 0)
                       sysfatal("could not create log file: %s: %r", p);
               break;
       case 'x':
               fk = vt220fk;
               term = "xterm";
               break;
       case 'r':
               rflag = 1;
               break;
       default:
               usage();
               break;
       }ARGEND;

       quotefmtinstall();
       notify(catch);
       atexit(shutdown);

       if(initdraw(0, fontname, term) < 0)
               sysfatal("inidraw failed: %r");
       if((mc = initmouse("/dev/mouse", screen)) == nil)
               sysfatal("initmouse failed: %r");
       if((kc = initkeyboard("/dev/cons")) == nil)
               sysfatal("initkeyboard failed: %r");

       hc[0] = chancreate(sizeof(char*), 256); /* input to host */
       hc[1] = chancreate(sizeof(Rune*), 8);   /* output from host */

       cs->raw = rflag;

       histp = hist;
       menu2.item = menutext2;
       menu3.item = menutext3;
       pagemode = 0;
       blocked = 0;
       ftsize.y = font->height;
       ftsize.x = stringwidth(font, "m");

       red = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DRed);
       green = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DGreen);
       bordercol = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xCCCCCCCC);
       highlight = allocimage(display, Rect(0,0,1,1), CHAN1(CAlpha,8), 1, 0x80);

       for(i=0; i<8; i++){
               colors[i] = allocimage(display, Rect(0,0,1,1), screen->chan, 1,
                       rgbacolors[i]);
               hicolors[i] = allocimage(display, Rect(0,0,1,1), screen->chan, 1,
                       rgbahicolors[i]);
       }
       bgcolor = (blkbg? display->black: display->white);
       fgcolor = (blkbg? display->white: display->black);
       bgselected = allocimage(display, Rect(0,0,1,1), CMAP8, 1, blkbg ? 0x333333FF : 0xCCCCCCFF);
       fgselected = allocimage(display, Rect(0,0,1,1), CMAP8, 1, blkbg ? 0xCCCCCCFF : 0x333333FF);
       resized();

       pidchan = chancreate(sizeof(int), 0);
       proccreate(runcmd, argv, 16*1024);
       hostpid = recvul(pidchan);

       emulate();
}

Image*
bgcol(int a, int c, int sel)
{
       if(sel)
               return bgselected;
       if(nocolor || (c & (1<<0)) == 0){
               if(a & TReverse)
                       return fgcolor;
               return bgcolor;
       }
       if((a & TReverse) != 0)
               c >>= 4;
       return colors[(c>>1)&7];
}

Image*
fgcol(int a, int c, int sel)
{
       if(sel)
               return fgselected;
       if(nocolor || (c & (1<<4)) == 0){
               if(a & TReverse)
                       return bgcolor;
               return fgcolor;
       }
       if((a & TReverse) == 0)
               c >>= 4;
       if(a & THighIntensity)
               return hicolors[(c>>1)&7];
       return colors[(c>>1)&7];
}

void
hidecursor(void)
{
       if(cursorsave == nil)
               return;
       draw(screen, cursorsave->r, cursorsave, nil, cursorsave->r.min);
       freeimage(cursorsave);
       cursorsave = nil;
}

void
drawscreen(void)
{
       int x, y, n;
       uchar *ap, *cp;
       Image *c;
       Rune *rp;
       Point p, q;

       hidecursor();

       if(scrolloff && scrolloff <= ymax)
               draw(screen, Rpt(pt(0,0), pt(xmax+2, ymax+1-scrolloff)),
                       screen, nil, pt(0, scrolloff));

       for(y = 0; y <= ymax; y++){
               if(!screenchange(y))
                       continue;
               screenchange(y) = 0;

               for(x = 0; x <= xmax; x += n){
                       cp = onscreenc(x, y);
                       ap = onscreena(x, y);
                       c = bgcol(*ap, *cp, selected(x, y));
                       for(n = 1; x+n <= xmax && bgcol(ap[n], cp[n], selected(x + n, y)) == c; n++)
                               ;
                       draw(screen, Rpt(pt(x, y), pt(x+n, y+1)), c, nil, ZP);
               }
               draw(screen, Rpt(pt(x, y), pt(x+1, y+1)), bgcolor, nil, ZP);

               for(x = 0; x <= xmax; x += n){
                       rp = onscreenr(x, y);
                       if(*rp == 0){
                               n = 1;
                               continue;
                       }
                       ap = onscreena(x, y);
                       cp = onscreenc(x, y);
                       c = fgcol(*ap, *cp, selected(x, y));
                       for(n = 1; x+n <= xmax && rp[n] != 0 && fgcol(ap[n], cp[n], selected(x + n, y)) == c
                       && ((ap[n] ^ *ap) & TUnderline) == 0; n++)
                               ;
                       p = pt(x, y);
                       q = runestringn(screen, p, c, ZP, font, rp, n);
                       if(*ap & TUnderline){
                               p.y += font->ascent+1;
                               q.y += font->ascent+2;
                               draw(screen, Rpt(p, q), c, nil, ZP);
                       }
               }
               if(*onscreenr(x, y) == 0)
                       runestringn(screen, pt(x, y),
                               bordercol,
                               ZP, font, L">", 1);
       }

       scrolloff = 0;
}

void
drawcursor(void)
{
       Image *col;
       Rectangle r;

       hidecursor();
       if(cursoron == 0)
               return;

       col = (hostin != nil || blocked || hostclosed) ? red : bordercol;
       r = Rpt(pt(x, y), pt(x+1, y+1));

       cursorsave = allocimage(display, r, screen->chan, 0, DNofill);
       draw(cursorsave, r, screen, nil, r.min);

       border(screen, r, 2, col, ZP);

}

void
clear(int x1, int y1, int x2, int y2)
{
       int c = (attr & 0x0F00)>>8; /* bgcolor */

       if(y1 < 0 || y1 > ymax || x1 < 0 || x1 > xmax || y2 <= y1 || x2 <= x1)
               return;

       while(y1 < y2){
               screenchange(y1) = 1;
               if(x1 < x2){
                       memset(onscreenr(x1, y1), 0, (x2-x1)*sizeof(Rune));
                       memset(onscreena(x1, y1), 0, x2-x1);
                       memset(onscreenc(x1, y1), c, x2-x1);
               }
               if(x2 > xmax)
                       *onscreenr(xmax+1, y1) = '\n';
               y1++;
       }
}

void
newline(void)
{
       if(x > xmax)
               *onscreenr(xmax+1, y) = 0;      /* wrap arround, remove hidden newline */
       nbacklines--;
       if(y >= yscrmax) {
               y = yscrmax;
               if(pagemode && olines >= yscrmax){
                       blocked = 1;
                       return;
               }
               scroll(yscrmin+1, yscrmax+1, yscrmin, yscrmax);
       } else
               y++;
       olines++;
}

int
get_next_char(void)
{
       int c = peekc;

       peekc = 0;
       if(c > 0)
               return(c);
       while(c <= 0) {
               if(backp) {
                       c = *backp;
                       if(c && nbacklines >= 0){
                               backp++;
                               if(backp >= &hist[HISTSIZ])
                                       backp = hist;
                               return(c);
                       }
                       backp = 0;
               }
               c = waitchar();
               if(c > 0 && logfd >= 0)
                       fprint(logfd, "%C", (Rune)c);
       }
       *histp++ = c;
       if(histp >= &hist[HISTSIZ])
               histp = hist;
       *histp = '\0';
       return(c);
}

char*
backrune(char *start, char *cp)
{
       char *ep;

       ep = cp;
       cp -= UTFmax;
       if(cp < start)
               cp = start;
       while(cp < ep){
               Rune r;
               int n;

               n = chartorune(&r, cp);
               if(cp + n >= ep)
                       break;
               cp += n;
       }
       return cp;
}

int
canon(char *ep, Rune c)
{
       switch(c) {
       case Kdown:
       case Kpgdown:
               return SCROLL;
       case '\b':
               if(sendbufp > sendbuf){
                       sendbufp = backrune(sendbuf, sendbufp);
                       *ep++ = '\b';
                       *ep++ = ' ';
                       *ep++ = '\b';
               }
               break;
       case 0x15:      /* ^U line kill */
               sendbufp = sendbuf;
               *ep++ = '^';
               *ep++ = 'U';
               *ep++ = '\n';
               break;
       case 0x17:      /* ^W word kill */
               while(sendbufp > sendbuf && !alnum(*sendbufp)) {
                       sendbufp = backrune(sendbuf, sendbufp);
                       *ep++ = '\b';
                       *ep++ = ' ';
                       *ep++ = '\b';
               }
               while(sendbufp > sendbuf && alnum(*sendbufp)) {
                       sendbufp = backrune(sendbuf, sendbufp);
                       *ep++ = '\b';
                       *ep++ = ' ';
                       *ep++ = '\b';
               }
               break;
       case '\177':    /* interrupt */
               sendbufp = sendbuf;
               send_interrupt();
               return(NEWLINE);
       case '\021':    /* quit */
       case '\r':
       case '\n':
               if(sendbufp < &sendbuf[BSIZE])
                       *sendbufp++ = '\n';
               sendnchars((int)(sendbufp-sendbuf), sendbuf);
               sendbufp = sendbuf;
               if(c == '\n' || c == '\r')
                       *ep++ = '\n';
               *ep = 0;
               return(NEWLINE);
       case '\004':    /* EOT */
               if(sendbufp == sendbuf) {
                       sendnchars(0,sendbuf);
                       *ep = 0;
                       return(NEWLINE);
               }
               /* fall through */
       default:
               if(sendbufp < &sendbuf[BSIZE-UTFmax])
                       sendbufp += runetochar(sendbufp, &c);
               ep += runetochar(ep, &c);
               break;
       }
       *ep = 0;
       return(OTHER);
}

char*
lookfk(struct funckey *fk, char *name)
{
       int i;

       for(i=0; fk[i].name; i++){
               if(strcmp(name, fk[i].name)==0)
                       return fk[i].sequence;
       }
       return nil;
}

int
sendfk(char *name)
{
       char *s = lookfk(appfk != nil ? appfk : fk, name);
       if(s == nil && appfk != nil)
               s = lookfk(fk, name);
       if(s != nil){
               sendnchars(strlen(s), s);
               return 1;
       }
       return 0;
}

int
waitchar(void)
{
       static char echobuf[4*BSIZE];

       for(;;) {
               if(resize_flag)
                       resized();
               if(backp)
                       return(0);
               if(snarffp) {
                       int c;

                       if((c = Bgetrune(snarffp)) < 0) {
                               Bterm(snarffp);
                               snarffp = nil;
                               continue;
                       }
                       kbdchar = c;
               }
               if(kbdchar) {
                       if(backc){
                               backc = 0;
                               backup(backc);
                       }
                       if(blocked)
                               resize_flag = 1;
                       if(cs->raw) {
                               switch(kbdchar){
                               case Kins:
                                       if(!sendfk("insert"))
                                               goto Send;
                                       break;
                               case Kdel:
                                       if(!sendfk("delete"))
                                               goto Send;
                                       break;
                               case Khome:
                                       if(!sendfk("home"))
                                               goto Send;
                                       break;
                               case Kend:
                                       if(!sendfk("end"))
                                               goto Send;
                                       break;

                               case Kpgup:
                                       sendfk("page up");
                                       break;
                               case Kpgdown:
                                       sendfk("page down");
                                       break;

                               case Kup:
                                       sendfk("up key");
                                       break;
                               case Kdown:
                                       sendfk("down key");
                                       break;
                               case Kleft:
                                       sendfk("left key");
                                       break;
                               case Kright:
                                       sendfk("right key");
                                       break;

                               case KF|1:
                                       sendfk("F1");
                                       break;
                               case KF|2:
                                       sendfk("F2");
                                       break;
                               case KF|3:
                                       sendfk("F3");
                                       break;
                               case KF|4:
                                       sendfk("F4");
                                       break;
                               case KF|5:
                                       sendfk("F5");
                                       break;
                               case KF|6:
                                       sendfk("F6");
                                       break;
                               case KF|7:
                                       sendfk("F7");
                                       break;
                               case KF|8:
                                       sendfk("F8");
                                       break;
                               case KF|9:
                                       sendfk("F9");
                                       break;
                               case KF|10:
                                       sendfk("F10");
                                       break;
                               case KF|11:
                                       sendfk("F11");
                                       break;
                               case KF|12:
                                       sendfk("F12");
                                       break;

                               case '\n':
                                       echobuf[0] = '\r';
                                       sendnchars(1, echobuf);
                                       break;
                               case '\r':
                                       echobuf[0] = '\n';
                                       sendnchars(1, echobuf);
                                       break;
                               default:
                               Send:
                                       sendnchars(runetochar(echobuf, &kbdchar), echobuf);
                                       break;
                               }
                       } else {
                               switch(canon(echobuf, kbdchar)){
                               case SCROLL:
                                       if(!blocked)
                                               bigscroll();
                                       break;
                               default:
                                       strcat(echo_input,echobuf);
                               }
                       }
                       blocked = 0;
                       kbdchar = 0;
                       continue;
               } else if(nbrecv(kc->c, &kbdchar))
                       continue;
               if(!blocked){
                       if(host_avail())
                               return(rcvchar());
                       free(hostbuf);
                       hostbufp = hostbuf = nbrecvp(hc[1]);
                       if(host_avail() && nrand(32))
                               return(rcvchar());
               }
               drawscreen();
               drawcursor();
               waitio();
       }
}

void
waitio(void)
{
       enum { AMOUSE, ARESIZE, AKBD, AHOSTIN, AHOSTOUT, AEND, };
       Alt a[AEND+1] = {
               { mc->c, &mc->Mouse, CHANRCV },
               { mc->resizec, nil, CHANRCV },
               { kc->c, &kbdchar, CHANRCV },
               { hc[0], &hostin, CHANSND },
               { hc[1], &hostbuf, CHANRCV },
               { nil, nil, CHANEND },
       };
       if(kbdchar != 0)
               a[AKBD].op = CHANNOP;
       if(hostin == nil)
               a[AHOSTIN].op = CHANNOP;
       if(blocked)
               a[AHOSTOUT].op = CHANNOP;
       else if(hostbuf != nil)
               a[AHOSTOUT].op = CHANNOBLK;
Next:
       if(display->bufp > display->buf)
               flushimage(display, 1);
       switch(alt(a)){
       case AMOUSE:
               if(button1())
                       selecting();
               else if(button2() || button3())
                       readmenu();
               else if(resize_flag == 0)
                       goto Next;
               break;
       case ARESIZE:
               resize_flag = 2;
               break;
       case AHOSTIN:
               hostin = nil;
               break;
       case AHOSTOUT:
               hostbufp = hostbuf;
               if(hostbuf == nil)
                       hostclosed = 1;
               break;
       }
}

void
putenvint(char *name, int x)
{
       char buf[20];

       snprint(buf, sizeof buf, "%d", x);
       putenv(name, buf);
}

void
exportsize(void)
{
       putenvint("XPIXELS", (xmax+1)*ftsize.x);
       putenvint("YPIXELS", (ymax+1)*ftsize.y);
       putenvint("LINES", ymax+1);
       putenvint("COLS", xmax+1);
       putenv("TERM", term);
       if(cs->winch)
               send_interrupt();
}

void
setdim(int ht, int wid)
{
       int fd;

       if(wid > 0) xmax = wid-1;
       if(ht > 0) ymax = ht-1;

       x = 0;
       y = 0;
       yscrmin = 0;
       yscrmax = ymax;
       olines = 0;

       margin.x = (Dx(screen->r) - (xmax+1)*ftsize.x) / 2;
       margin.y = (Dy(screen->r) - (ymax+1)*ftsize.y) / 2;

       free(screenchangebuf);
       screenchangebuf = emalloc9p(ymax+1);
       scrolloff = 0;
       selrect = ZR;

       free(onscreenrbuf);
       onscreenrbuf = emalloc9p((ymax+1)*(xmax+2)*sizeof(Rune));
       free(onscreenabuf);
       onscreenabuf = emalloc9p((ymax+1)*(xmax+2));
       free(onscreencbuf);
       onscreencbuf = emalloc9p((ymax+1)*(xmax+2));
       clear(0,0,xmax+1,ymax+1);

       draw(screen, screen->r, bgcolor, nil, ZP);

       if(resize_flag || backc)
               return;

       exportsize();

       fd = open("/dev/wctl", OWRITE);
       if(fd >= 0){
               ht = (ymax+1) * ftsize.y + 2*INSET + 2*Borderwidth;
               wid = (xmax+1) * ftsize.x + ftsize.x + 2*INSET + 2*Borderwidth;
               fprint(fd, "resize -dx %d -dy %d\n", wid, ht);
               close(fd);
       }
}

void
resized(void)
{
       if(resize_flag > 1 && getwindow(display, Refnone) < 0){
               fprint(2, "can't reattach to window: %r\n");
               exits("can't reattach to window");
       }
       setdim((Dy(screen->r) - 2*INSET)/ftsize.y, (Dx(screen->r) - 2*INSET - ftsize.x)/ftsize.x);
       exportsize();
       if(resize_flag > 1)
               backup(backc);
       resize_flag = 0;
       werrstr("");            /* clear spurious error messages */
}

char*
selrange(char *d, int x0, int y0, int x1, int y1)
{
       Rune *s, *e;
       int z, p;

       s = onscreenr(x0, y0);
       e = onscreenr(x1, y1);
       for(z = p = 0; s < e; s++){
               if(*s){
                       if(*s == '\n')
                               z = p = 0;
                       else if(p++ == 0){
                               while(z-- > 0) *d++ = ' ';
                       }
                       d += runetochar(d, s);
               } else {
                       z++;
               }
       }
       return d;
}

char*
selection(void)
{
       char *s, *p;
       int y;

       /* generous, but we can spare a few bytes for a few microseconds */
       s = p = malloc(UTFmax*(xmax+1)*(Dy(selrect)+1)+1);
       if(s == nil)
               return nil;
       if(blocksel){
               for(y = selrect.min.y; y <= selrect.max.y; y++){
                       p = selrange(p, selrect.min.x, y, selrect.max.x, y);
                       *p++ = '\n';
               }
       } else {
               p = selrange(p, selrect.min.x, selrect.min.y, selrect.max.x, selrect.max.y);
       }
       *p = 0;
       return s;
}

void
snarfsel(void)
{
       Biobuf *b;
       char *s;

       if((s = selection()) == nil)
               return;
       if((b = Bopen("/dev/snarf", OWRITE|OTRUNC)) == nil){
               free(s);
               return;
       }
       Bprint(b, "%s", s);
       Bterm(b);
       free(s);
}

void
plumbsel(void)
{
       char *s, wdir[1024];
       int plumb;

       s = selection();
       if(s == nil || *s == 0)
               return;
       if(getwd(wdir, sizeof wdir) == nil){
               free(s);
               return;
       }
       if((plumb = plumbopen("send", OWRITE)) < 0){
               free(s);
               return;
       }
       plumbsendtext(plumb, "vt", nil, wdir, s);
       close(plumb);
       free(s);
}

void
paste(void)
{
       if(snarffp == nil)
               snarffp = Bopen("/dev/snarf",OREAD);
}

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 0;
       if(0x7F<=c && c<=0xA0)
               return 0;
       if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
               return 0;
       return 1;
}

int
isspace(Rune c)
{
       return c == 0 || c == ' ' || c == '\t' ||
               c == '\n' || c == '\r' || c == '\v';
}

void
unselect(void)
{
       int y;

       for(y = selrect.min.y; y <= selrect.max.y; y++)
               screenchange(y) = 1;
       selrect = ZR;
}

int
inmode(Rune r, int mode)
{
       return (mode == 1) ? isalnum(r) : r && !isspace(r);
}

/*
* Selects different things based on mode.
* 0: selects swept-over text.
* 1: selects alphanumeric segment
* 2: selects non-whitespace segment.
*/
void
select(Point p, Point q, int mode)
{
       if(onscreenr(p.x, p.y) > onscreenr(q.x, q.y)){
               select(q, p, mode);
               return;
       }
       unselect();
       if(p.y < 0 || p.y > ymax)
               return;
       if(p.y < 0){
               p.y = 0;
               if(!blocksel) p.x = 0;
       }
       if(q.y > ymax){
               q.y = ymax;
               if(!blocksel) q.x = xmax+1;
       }
       if(mode != 0 && eqpt(p, q)){
               while(p.x > 0 && inmode(*onscreenr(p.x-1, p.y), mode))
                       p.x--;
               while(q.x <= xmax && inmode(*onscreenr(q.x, q.y), mode))
                       q.x++;
               if(p.x != q.x)
                       mode = 0;
       }
       if(p.x < 0 || mode)
               p.x = 0;
       if(q.x > xmax+1 || mode)
               q.x = xmax+1;
       selrect = Rpt(p, q);
       for(; p.y <= q.y; p.y++)
               screenchange(p.y) = 1;
}

void
selecting(void)
{
       Point p, q;
       static ulong t, mode;

       p = pos(mc->xy);
       t += mc->msec;
       mode++;
       do{
               q = pos(mc->xy);
               if(t > 200)
                       mode = 0;
               if(mode > 2)
                       mode = 2;
               select(p, q, mode);
               drawscreen();
               readmouse(mc);
       } while(button1());
       switch(mc->buttons & 0x7){
       case 3: snarfsel();     break;
       case 5: paste();        break;
       }
       while(mc->buttons&7) readmouse(mc);
       t = -mc->msec;
}

int
selected(int x, int y)
{
       int s;

       s = y >= selrect.min.y && y <= selrect.max.y;
       if (blocksel)
               s = s && x >= selrect.min.x && x < selrect.max.x;
       else{
               if(y == selrect.min.y)
                       s = s && x >= selrect.min.x;
               if(y == selrect.max.y)
                       s = s && x < selrect.max.x;
               if(y > selrect.min.y && y < selrect.max.y)
                       s = 1;
       }
       return s;
}

void
readmenu(void)
{
       Point p;

       p = pos(mc->xy);
       if(button3()) {
               menu3.item[1] = ttystate[cs->raw].crnl ? "cr" : "crnl";
               menu3.item[2] = ttystate[cs->raw].nlcr ? "nl" : "nlcr";
               menu3.item[3] = cs->raw ? "cooked" : "raw";
               menu3.item[4] = blocksel ? "linesel" : "blocksel";

               switch(menuhit(3, mc, &menu3, nil)) {
               case M24x80:            /* 24x80 */
                       setdim(24, 80);
                       backup(backc);
                       return;
               case Mcrnl:             /* newline after cr? */
                       ttystate[cs->raw].crnl = !ttystate[cs->raw].crnl;
                       return;
               case Mnl:               /* cr after newline? */
                       ttystate[cs->raw].nlcr = !ttystate[cs->raw].nlcr;
                       return;
               case Mraw:              /* switch raw mode */
                       cs->raw = !cs->raw;
                       return;
               case Mblocksel:
                       unselect();
                       blocksel = !blocksel;
                       return;
               case Mexit:
                       exits(0);
               }
               return;
       }

       menu2.item[Mpage] = pagemode? "scroll": "page";

       switch(menuhit(2, mc, &menu2, nil)) {
       case Mbackup:           /* back up */
               if(backup(backc+1))
                       backc++;
               return;

       case Mforward:          /* move forward */
               if(backc > 0)
                       backup(--backc);
               return;

       case Mreset:            /* reset */
               backc = 0;
               backup(0);
               return;

       case Mpaste:            /* paste the snarf buffer */
               paste();
               return;

       case Msnarf:            /* send the snarf buffer */
               snarfsel();
               return;

       case Mplumb:
               plumbsel();
               return;

       case Mpage:             /* pause and clear at end of screen */
               pagemode = 1-pagemode;
               if(blocked && !pagemode) {
                       resize_flag = 1;
                       blocked = 0;
               }
               return;
       }
}

int
backup(int count)
{
       Rune *cp;
       int left, n;

       unselect();

       resize_flag = 1;
       if(count == 0 && !pagemode) {
               n = ymax;
               nbacklines = HISTSIZ;   /* make sure we scroll to the very end */
       } else{
               n = 3*(count+1)*ymax/4;
               nbacklines = ymax-1;
       }
       cp = histp;
       left = 1;
       while (n >= 0) {
               cp--;
               if(cp < hist)
                       cp = &hist[HISTSIZ-1];
               if(*cp == '\0') {
                       left = 0;
                       break;
               }
               if(*cp == '\n')
                       n--;
       }
       cp++;
       if(cp >= &hist[HISTSIZ])
               cp = hist;
       backp = cp;
       return left;
}

Point
pt(int x, int y)
{
       return addpt(screen->r.min, Pt(x*ftsize.x+margin.x,y*ftsize.y+margin.y));
}

Point
pos(Point pt)
{
       pt.x -= screen->r.min.x + margin.x;
       pt.y -= screen->r.min.y + margin.y;
       pt.x /= ftsize.x;
       pt.y /= ftsize.y;
       if(pt.x < 0)
               pt.x = 0;
       else if(pt.x > xmax+1)
               pt.x = xmax+1;
       if(pt.y < 0)
               pt.y = 0;
       else if(pt.y > ymax+1)
               pt.y = ymax+1;
       return pt;
}

void
shift(int x1, int y, int x2, int w)
{
       if(y < 0 || y > ymax || x1 < 0 || x2 < 0 || w <= 0)
               return;

       if(x1+w > xmax+1)
               w = xmax+1 - x1;
       if(x2+w > xmax+1)
               w = xmax+1 - x2;

       screenchange(y) = 1;
       memmove(onscreenr(x1, y), onscreenr(x2, y), w*sizeof(Rune));
       memmove(onscreena(x1, y), onscreena(x2, y), w);
       memmove(onscreenc(x1, y), onscreenc(x2, y), w);
}

void
scroll(int sy, int ly, int dy, int cy)  /* source, limit, dest, which line to clear */
{
       int n, d, i;

       if(sy < 0 || sy > ymax || dy < 0 || dy > ymax)
               return;

       n = ly - sy;
       if(sy + n > ymax+1)
               n = ymax+1 - sy;
       if(dy + n > ymax+1)
               n = ymax+1 - dy;

       d = sy - dy;
       if(n > 0 && d != 0){
               if(d > 0 && dy == 0 && n >= ymax){
                       scrolloff += d;
               } else {
                       for(i = 0; i < n; i++)
                               screenchange(dy+i) = 1;
               }
               memmove(onscreenr(0, dy), onscreenr(0, sy), n*(xmax+2)*sizeof(Rune));
               memmove(onscreena(0, dy), onscreena(0, sy), n*(xmax+2));
               memmove(onscreenc(0, dy), onscreenc(0, sy), n*(xmax+2));
       }

       /* move selection */
       selrect.min.y -= d;
       selrect.max.y -= d;
       select(selrect.min, selrect.max, 0);

       clear(0, cy, xmax+1, cy+1);
}

void
bigscroll(void)                 /* scroll up half a page */
{
       int half = ymax/3;

       if(x == 0 && y == 0)
               return;
       if(y < half) {
               clear(0, 0, xmax+1, ymax+1);
               scrolloff = 0;
               x = y = 0;
               return;
       }
       scroll(half, ymax+1, 0, ymax);
       clear(0, y-half+1, xmax+1, ymax+1);

       y -= half;
       if(olines)
               olines -= half;
}

int
number(Rune *p, int *got)
{
       int c, n = 0;

       if(got)
               *got = 0;
       while ((c = get_next_char()) >= '0' && c <= '9'){
               if(got)
                       *got = 1;
               n = n*10 + c - '0';
       }
       *p = c;
       return(n);
}

/* stubs */

int
host_avail(void)
{
       if(*echop != 0 && fullrune(echop, strlen(echop)))
               return 1;
       if(hostbuf == nil)
               return 0;
       return *hostbufp != 0;
}

int
rcvchar(void)
{
       Rune r;

       if(*echop != 0) {
               echop += chartorune(&r, echop);
               if(*echop == 0) {
                       echop = echo_input;
                       *echop = 0;
               }
               return r;
       }
       return *hostbufp++;
}

void
ringbell(void){
}

int
alnum(int c)
{
       if(c >= 'a' && c <= 'z')
               return 1;
       if(c >= 'A' && c <= 'Z')
               return 1;
       if(c >= '0' && c <= '9')
               return 1;
       return 0;
}

void
escapedump(int fd,uchar *str,int len)
{
       int i;

       for(i = 0; i < len; i++) {
               if((str[i] < ' ' || str[i] > '\177') &&
                       str[i] != '\n' && str[i] != '\t') fprint(fd,"^%c",str[i]+64);
               else if(str[i] == '\177') fprint(fd,"^$");
               else if(str[i] == '\n') fprint(fd,"^J\n");
               else fprint(fd,"%c",str[i]);
       }
}

void
drawstring(Rune *str, int n)
{
       screenchange(y) = 1;
       memmove(onscreenr(x, y), str, n*sizeof(Rune));
       memset(onscreena(x, y), attr & 0xFF, n);
       memset(onscreenc(x, y), attr >> 8, n);
}