#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
#include <bio.h>
#include <keyboard.h>
#include "cons.h"

enum{
       Ehost           = 4,
};

char    *menutext2[] = {
       "backup",
       "forward",
       "reset",
       "clear",
       "send",
       "page",
       0
};

char    *menutext3[] = {
       "24x80",
       "crnl",
       "nl",
       "raw",
       "exit",
       0
};

/* variables associated with the screen */

int     x, y;   /* character positions */
char    *backp;
int     backc;
int     atend;
int     nbacklines;
int     xmax, ymax;
int     blocked;
int     resize_flag;
int     pagemode;
int     olines;
int     peekc;
int     cursoron = 1;
Menu    menu2;
Menu    menu3;
char    *histp;
char    hist[HISTSIZ];
int     yscrmin, yscrmax;
int     bckcolor, frgcolor;
int     attribute;

Image   *bordercol;
Image   *cursback;
Image   *black;
Image   *white;
Image   *red;
Image   *green;
Image   *blue;
Image   *cyan;
Image   *magenta;
Image   *yellow;
Image   *grey;
Image   *colortab[8];

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

int     NS;
int     CW;
Consstate *cs;
Mouse   mouse;

int     outfd = -1;
Biobuf  *snarffp = 0;

char    *host_buf;
char    *hostp;                 /* input from host */
int     host_bsize = 2*BSIZE;
int     hostlength;                     /* amount of input from host */
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    *sendp = sendbuf;

/* functions */
void    initialize(int, char **);
void    ebegin(int);
int     waitchar(void);
int     rcvchar(void);
void    set_input(char *);
void    set_host(Event *);
void    bigscroll(void);
void    readmenu(void);
void    eresized(int);
void    resize(void);
void    send_interrupt(void);
int     alnum(int);
void    escapedump(int,uchar *,int);
char *term;
struct funckey *fk;

int     debug;
int     logfd = -1;
void
main(int argc, char **argv)
{
       initialize(argc, argv);
       emulate();
}

void
useage(void)
{
       fprint(2, "usage: %s [-2s] [-l logfile]\n", argv0);
       exits("usage");
}

void
initialize(int argc, char **argv)
{
       int dayglo = 1;
       char *p;

       rfork(RFENVG|RFNAMEG|RFNOTEG);

       term = "vt100";
       fk = vt100fk;
       ARGBEGIN{
       case 'a':
               term = "ansi";
               fk = ansifk;
               break;
       case '2':
               term = "vt220";
               fk = vt220fk;
               break;
       case 's': /* for sape */
               dayglo = 0;
               break;
       case 'l':
               p = ARGF();
               if(p == 0)
                       useage();
               logfd = create(p, OWRITE, 0666);
               if(logfd < 0)
                       sysfatal("could not create log file: %s: %r", p);
               break;
       }ARGEND;

       host_buf = malloc(host_bsize);
       hostp = host_buf;
       hostlength = 0;

       if(initdraw(0,0,term) < 0){
               fprint(2, "%s: initdraw failed: %r\n", term);
               exits("initdraw");
       }
       ebegin(Ehost);

       histp = hist;
       menu2.item = menutext2;
       menu3.item = menutext3;
       pagemode = 0;
       blocked = 0;
       NS = font->height ;
       CW = stringwidth(font, "m");
       bordercol = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xCCCCCCCC);
       cursback = allocimage(display, Rect(0, 0, CW+1, NS+1), screen->chan, 0, DNofill);
       black =  allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DBlack);
       white =  allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DWhite);
       red =  allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DRed);
       green =  allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DGreen);
       yellow =  allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DYellow);
       blue =  allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DBlue);
       magenta =  allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DMagenta);
       cyan =  allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DCyan);
       grey =  allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DPalegreygreen);

       colortab[0] = black;
       colortab[1] = red;
       colortab[2] = green;
       colortab[3] = yellow;
       colortab[4] = blue;
       colortab[5] = magenta;
       colortab[6] = cyan;
       colortab[7] = grey;

       if(dayglo) {
               bckcolor = 0;
               frgcolor = 7;
       } else {
               bckcolor = 7;
               frgcolor = 0;
               colortab[7] = white;
       }

       eresized(0);

       if(argc > 0) {
               sendnchars(strlen(argv[0]),argv[0]);
               sendnchars(1,"\n");
       }
}

void
clear(Rectangle r)
{
       draw(screen, r, colortab[bckcolor], nil, ZP);
}

void
newline(void)
{
       nbacklines--;
       if(y >= yscrmax) {
               y = yscrmax;
               if(pagemode && olines >= yscrmax) {
                       blocked = 1;
                       return;
               }
               scroll(yscrmin+1, yscrmax+1, yscrmin, yscrmax);
       } else
               y++;
       olines++;
}

void
cursoff(void)
{
       draw(screen, Rpt(pt(x, y), addpt(pt(x, y), Pt(CW,NS))),
               cursback, nil, cursback->r.min);
}

void
curson(int bl)
{
       Image *col;

       if(!cursoron){
               cursoff();
               return;
       }

       draw(cursback, cursback->r, screen, nil, pt(x, y));
       if(bl)
               col = red;
       else
               col = bordercol;
       border(screen, Rpt(pt(x, y), addpt(pt(x, y), Pt(CW,NS))), 2, col, ZP);
}

int
get_next_char(void)
{
       int c = peekc;
       uchar buf[1];
       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 = (uchar)waitchar();
               if(c > 0 && logfd >= 0) {
                       buf[0] = c;
                       write(logfd, buf, 1);
               }
       }
       *histp++ = c;
       if(histp >= &hist[HISTSIZ])
               histp = hist;
       *histp = '\0';
       return(c);
}

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

       }
       *ep = 0;
       return(OTHER);
}

void
sendfk(char *name)
{
       int i;
       static int fd;

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

int
waitchar(void)
{
       Event e;
       int c;
       char c2;
       int newmouse;
       int wasblocked;
       int kbdchar = -1;
       char echobuf[3*BSIZE];
       static int lastc = -1;


       for(;;) {
               if(resize_flag)
                       resize();
               wasblocked = blocked;
               if(backp)
                       return(0);
               if(ecanmouse() && (button2() || button3()))
                       readmenu();
               if(snarffp) {
                       if((c = Bgetc(snarffp)) < 0) {
                               if(lastc != '\n')
                                       write(outfd,"\n",1);
                               Bterm(snarffp);
                               snarffp = 0;
                               if(lastc != '\n') {
                                       lastc = -1;
                                       return('\n');
                               }
                               lastc = -1;
                               continue;
                       }
                       lastc = c;
                       c2 = c;
                       write(outfd, &c2, 1);
                       return(c);
               }
               if(!blocked && host_avail())
                       return(rcvchar());
               if(kbdchar > 0) {
                       if(blocked)
                               resize();
                       if(cs->raw) {
                               switch(kbdchar){
                               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:
                                       echobuf[0] = kbdchar;
                                       sendnchars(1, echobuf);
                                       break;
                               }
                       } else if(canon(echobuf,kbdchar) == SCROLL) {
                               if(!blocked)
                                       bigscroll();
                       } else
                               strcat(echo_input,echobuf);
                       blocked = 0;
                       kbdchar = -1;
                       continue;
               }
               curson(wasblocked);     /* turn on cursor while we're waiting */
               do {
                       newmouse = 0;
                       switch(eread(blocked ? Emouse|Ekeyboard :
                                              Emouse|Ekeyboard|Ehost, &e)) {
                       case Emouse:
                               mouse = e.mouse;
                               if(button2() || button3())
                                       readmenu();
                               else if(resize_flag == 0) {
                                       /* eresized() is triggered by special mouse event */
                                       newmouse = 1;
                               }
                               break;
                       case Ekeyboard:
                               kbdchar = e.kbdc;
                               break;
                       case Ehost:
                               set_host(&e);
                               break;
                       default:
                               perror("protocol violation");
                               exits("protocol violation");
                       }
               } while(newmouse == 1);
               cursoff();      /* turn cursor back off */
       }
       return 1;               /* to shut up compiler */
}

void
eresized(int new)
{
       resize_flag = 1+new;
}

void
exportsize(void)
{
       int     fd;
       char    buf[10];

       if((fd = create("/env/LINES", OWRITE, 0644)) > 0) {
               sprint(buf,"%d",ymax+1);
               write(fd,buf,strlen(buf));
               close(fd);
       }
       if((fd = create("/env/COLS", OWRITE, 0644)) > 0) {
               sprint(buf,"%d",xmax+1);
               write(fd,buf,strlen(buf));
               close(fd);
       }
       if((fd = create("/env/TERM", OWRITE, 0644)) > 0) {
               fprint(fd, "%s", term);
               close(fd);
       }
}

void
resize(void)
{
       if(resize_flag > 1 && getwindow(display, Refnone) < 0){
               fprint(2, "can't reattach to window: %r\n");
               exits("can't reattach to window");
       }
       xmax = (Dx(screen->r)-2*XMARGIN)/CW-1;
       ymax = (Dy(screen->r)-2*YMARGIN)/NS-1;
       if(xmax == 0 || ymax == 0)
               exits("window gone");
       x = 0;
       y = 0;
       yscrmin = 0;
       yscrmax = ymax;
       olines = 0;
       exportsize();
       clear(screen->r);
       resize_flag = 0;
}

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

       if(ht != -1)
               ymax = ht-1;
       if(wid != -1)
               xmax = wid-1;

       r.min = screen->r.min;
       r.max = addpt(screen->r.min,
                       Pt((xmax+1)*CW+2*XMARGIN+2*INSET,
                               (ymax+1)*NS+2*YMARGIN+2*INSET));
       fd = open("/dev/wctl", OWRITE);
       if(fd < 0 || fprint(fd, "resize -dx %d -dy %d\n", Dx(r)+2*Borderwidth, Dy(r)+2*Borderwidth) < 0){
               border(screen, r, INSET, bordercol, ZP);
               exportsize();
       }
       if(fd >= 0)
               close(fd);
}

void
readmenu(void)
{
       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";

               switch(emenuhit(3, &mouse, &menu3)) {
               case 0:         /* 24x80 */
                       setdim(24, 80);
                       return;
               case 1:         /* newline after cr? */
                       ttystate[cs->raw].crnl = !ttystate[cs->raw].crnl;
                       return;
               case 2:         /* cr after newline? */
                       ttystate[cs->raw].nlcr = !ttystate[cs->raw].nlcr;
                       return;
               case 3:         /* switch raw mode */
                       cs->raw = !cs->raw;
                       return;
               case 4:
                       exits(0);
               }
               return;
       }

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

       switch(emenuhit(2, &mouse, &menu2)) {

       case 0:         /* back up */
               if(atend == 0) {
                       backc++;
                       backup(backc);
               }
               return;

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

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

       case 3:         /* clear screen */
               eresized(0);
               return;

       case 4:         /* send the snarf buffer */
               snarffp = Bopen("/dev/snarf",OREAD);
               return;

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

void
backup(int count)
{
       register n;
       register char *cp;

       eresized(0);
       n = 3*(count+1)*ymax/4;
       cp = histp;
       atend = 0;
       while (n >= 0) {
               cp--;
               if(cp < hist)
                       cp = &hist[HISTSIZ-1];
               if(*cp == '\0') {
                       atend = 1;
                       break;
               }
               if(*cp == '\n')
                       n--;
       }
       cp++;
       if(cp >= &hist[HISTSIZ])
               cp = hist;
       backp = cp;
       nbacklines = ymax-2;
}

Point
pt(int x, int y)
{
       return addpt(screen->r.min, Pt(x*CW+XMARGIN,y*NS+YMARGIN));
}

void
scroll(int sy, int ly, int dy, int cy)  /* source, limit, dest, which line to clear */
{
       draw(screen, Rpt(pt(0, dy), pt(xmax+1, dy+ly-sy)), screen, nil, pt(0, sy));
       clear(Rpt(pt(0, cy), pt(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(Rpt(pt(0,0),pt(xmax+1,ymax+1)));
               x = y = 0;
               return;
       }
       draw(screen, Rpt(pt(0, 0), pt(xmax+1, ymax+1)), screen, nil, pt(0, half));
       clear(Rpt(pt(0,y-half+1),pt(xmax+1,ymax+1)));
       y -= half;
       if(olines)
               olines -= half;
}

int
number(char *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 */

void
sendnchars(int n,char *p)
{
       sendnchars2(n, p);
       p[n+1] = 0;
}

void
sendnchars2(int n,char *p)
{
       if(write(outfd,p,n) < 0) {
               close(outfd);
               close(0);
               close(1);
               close(2);
               exits("write");
       }
}

int
host_avail(void)
{
       return(*echop || ((hostp - host_buf) < hostlength));
}

int
rcvchar(void)
{
       int c;
       if(*echop) {
               c = *echop++;
               if(!*echop) {
                       echop = echo_input;
                       *echop = 0;
               }
               return c;
       }
       return *hostp++;
}

void
set_host(Event *e)
{
       hostlength = e->n;
       if(hostlength > host_bsize) {
               host_bsize *= 2;
               host_buf = realloc(host_buf,host_bsize);
       }
       hostp = host_buf;
       memmove(host_buf,e->data,hostlength);
       host_buf[hostlength]=0;
}

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
funckey(int key)
{
       if(key >= NKEYS)
               return;
       if(fk[key].name == 0)
               return;
       sendnchars2(strlen(fk[key].sequence), fk[key].sequence);
}


void
drawstring(Point p, char *str, int attribute)
{
       Image *txt, *bg;

       if(!(attribute & TReverse)) {
               txt = colortab[frgcolor];
               bg = colortab[bckcolor];
       } else {
               txt = colortab[bckcolor];
               bg = colortab[frgcolor];
       }
       if(attribute & THighIntensity)
               txt = white;

       draw(screen, Rpt(p, addpt(p, stringsize(font, str))), bg, nil, p);
       string(screen, p, txt, ZP, font, str);
}