#include <u.h>
#include <libc.h>
#include <bio.h>
#include "plot.h"
#include <draw.h>
#include <thread.h>
#include <mouse.h>
#include <keyboard.h>

void    define(char*);
void    call(char*);
void    include(char*);
int     process(Biobuf*);
int     server(void);

enum{
       ARC,
       BOX,
       CALL,
       CFILL,
       CIRC,
       CLOSEPL,
       COLOR,
       CSPLINE,
       DEFINE,
       DISK,
       DSPLINE,
       ERASE,
       FILL,
       FRAME,
       FSPLINE,
       GRADE,
       IDLE,
       INCLUDE,
       LINE,
       LSPLINE,
       MOVE,
       OPENPL,
       PARABOLA,
       PEN,
       PAUSE,
       POINT,
       POLY,
       RANGE,
       RESTORE,
       RMOVE,
       RVEC,
       SAVE,
       SBOX,
       SPLINE,
       TEXT,
       VEC,
       LAST
};

struct pcall {
       char    *cc;
       int     numc;
} plots[] = {
       [ARC]           "a",    1,
       [BOX]           "bo",   2,
       [CALL]          "ca",   2,
       [CFILL]         "cf",   2,
       [CIRC]          "ci",   2,
       [CLOSEPL]       "cl",   2,
       [COLOR]         "co",   2,
       [CSPLINE]       "cs",   2,
       [DEFINE]        "de",   2,
       [DISK]          "di",   2,
       [DSPLINE]       "ds",   2,
       [ERASE]         "e",    1,
       [FILL]          "fi",   2,
       [FRAME]         "fr",   2,
       [FSPLINE]       "fs",   2,
       [GRADE]         "g",    1,
       [IDLE]          "id",   2,
       [INCLUDE]       "in",   2,
       [LINE]          "li",   2,
       [LSPLINE]       "ls",   2,
       [MOVE]          "m",    1,
       [OPENPL]        "o",    1,
       [PARABOLA]      "par",  3,
       [PEN]           "pe",   2,
       [PAUSE]         "pau",  3,
       [POINT]         "poi",  3,
       [POLY]          "pol",  3,
       [RANGE]         "ra",   2,
       [RESTORE]       "re",   2,
       [RMOVE]         "rm",   2,
       [RVEC]          "rv",   2,
       [SAVE]          "sa",   2,
       [SBOX]          "sb",   2,
       [SPLINE]        "sp",   2,
       [TEXT]          "t",    1,
       [VEC]           "v",    1,
       [LAST]          0,      0,
};

struct pcall *pplots;           /* last command read */

#define MAXL 16
struct fcall {
       char *name;
       char *stash;
} flibr[MAXL];                  /* define strings */

struct fcall *fptr = flibr;

#define NFSTACK 50
struct fstack{
       char name[128];
       int peekc;
       int lineno;
       char *corebuf;
       Biobuf *fd;
       double scale;
}fstack[NFSTACK];               /* stack of open input files & defines */
struct fstack *fsp=fstack;

#define NARGSTR 8192
char argstr[NARGSTR+1];         /* string arguments */

#define NX      8192
double x[NX];                   /* numeric arguments */

#define NPTS    256
int cnt[NPTS];                  /* control-polygon vertex counts */
double *pts[NPTS];              /* control-polygon vertex pointers */

extern void m_swapbuf(void);    /* reaching into implementation.  ick. */
extern Image *offscreen;

void
resize(Point p)
{
       int fd;

       fd = open("/dev/wctl", OWRITE);
       if(fd >= 0){
               fprint(fd, "resize -dx %d -dy %d", p.x+4*2, p.y+4*2);
               close(fd);
       }
}

void
resizeto(Point p)
{
       Point s;

       s = (Point){Dx(screen->r), Dy(screen->r)};
       if(eqpt(p, s))
               return;
       resize(p);
}

void
eresized(int new)
{
       if(new && getwindow(display, Refnone) < 0)
               sysfatal("plot: can't reattach to window: %r\n");
//      resizeto((Point){Dx(offscreen->r)+4, Dy(offscreen->r)+4});
       m_swapbuf();
}

char *items[]={
       "exit",
       0
};
Menu menu={items};

void
mouseproc(void*)
{
       void *v;
       Rune r;
       Alt alts[4];
       Keyboardctl *k;
       Mousectl *m;
       Mouse mc;
       enum{Amouse, Akbd, Aresize, Aend};

       m = initmouse(nil, screen);
       k = initkeyboard(nil);

       memset(alts, 0, sizeof alts);
       alts[Amouse].c = m->c;
       alts[Amouse].v = &mc;
       alts[Amouse].op = CHANRCV;

       alts[Akbd].c = k->c;
       alts[Akbd].v = &r;
       alts[Akbd].op = CHANRCV;

       alts[Aresize].c = m->resizec;
       alts[Aresize].v = &v;
       alts[Aresize].op = CHANRCV;

       alts[Aend].op = CHANEND;

       for(;;)
               switch(alt(alts)){
               default:
                       sysfatal("mouse!");
               case Amouse:
                       if(mc.buttons & 4) {
                               if(menuhit(3, m, &menu, nil) == 0)
                                       threadexitsall("");
                       }
                       break;
               case Akbd:
                       switch(r){
                       case 'q':
                       case Kdel:
                       case Keof:
                               threadexitsall("");
                       }
                       break;
               case Aresize:
                       eresized(1);
                       ;
               }
}

void
threadmain(int arc, char *arv[]){
       char *ap;
       Biobuf *bp;
       int fd;
       int i;
       int dflag;
       char *oflag;

       bp = 0;
       fd = dup(0, -1);                /* because openpl will close 0! */
       dflag=0;
       oflag="";
       argv0 = arv[0];
       for(i=1;i!=arc;i++) if(arv[i][0]=='-') switch(arv[i][1]){
       case 'd': dflag=1; break;
       case 'o': oflag=arv[i]+2; break;
       case 's': fd=server(); break;
       }
       openpl(oflag);
       proccreate(mouseproc, nil, 32*1024);
       if(dflag)
               doublebuffer();
       for (; arc > 1; arc--, arv++) {
               if (arv[1][0] == '-') {
                       ap = arv[1];
                       ap++;
                       switch (*ap) {
                       default:
                               fprint(2, "%s not allowed as argument\n", ap);
                               exits("usage");
                       case 'T': break;
                       case 'D': break;
                       case 'd': break;
                       case 'o': break;
                       case 'W': break;
                       case 's': break;
                       case 'e': erase(); break;
                       case 'C': closepl(); break;
                       case 'w': ppause(); break;
                       case 'c': color(ap+1); break;
                       case 'f': cfill(ap+1); break;
                       case 'p': pen(ap+1); break;
                       case 'g': grade(atof(ap+1)); break;
                       }
               }
               else if ((bp = Bopen(arv[1], OREAD)) == 0) {
                       perror(arv[1]);
                       fprint(2, "Cannot find file %s\n", arv[1]);
               }
               else if(process(bp)) Bterm(fsp->fd);
               else break;
       }
       if (bp == 0){
               bp = malloc(sizeof *bp);
               Binit(bp, fd, OREAD);
               process(bp);
       }
       closepl();
       flushimage(display, 1);
       for(;;)
               sleep(1000);
}
int isalpha(int c)
{
       return ('a'<=c && c<='z') || ('A'<=c && c<='Z');
}
int isupper(int c)
{
       return 'A'<=c && c<='Z';
}
int isdigit(int c)
{
       return '0'<=c && c<='9';
}
int ispunct(int c)
{
       return strchr("!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~", c)!=0;
}
int isspace(int c)
{
       return strchr(" \t\n\v\f\r", c)!=0;
}
int nextc(void){
       int c;
       Rune r;
       for(;;){
               if(fsp->peekc!=Beof){
                       c=fsp->peekc;
                       fsp->peekc=Beof;
                       return c;
               }
               if(fsp->fd)
                       c=Bgetrune(fsp->fd);
               else if(*fsp->corebuf){
                       fsp->corebuf+=chartorune(&r, fsp->corebuf);
                       c=r;
               }else
                       c=Beof;
               if(c!=Beof || fsp==fstack) break;
               if(fsp->fd) Bterm(fsp->fd);
               --fsp;
       }
       if(c=='\n') fsp->lineno++;
       return c;
}
/*
* Read a string into argstr -- ignores leading spaces
* and an optional leading quote-mark
*/
void
strarg(void){
       int c;
       Rune r;
       int quote=0;
       char *s=argstr;
       do
               c=nextc();
       while(c==' ' || c=='\t');
       if(c=='\'' || c=='"'){
               quote=c;
               c=nextc();
       }
       r = 0;
       while(c!='\n' && c!=Beof){
               r=c;
               s+=runetochar(s, &r);
               c=nextc();
       }
       if(quote && s!=argstr && r==quote) --s;
       *s='\0';
}
/*
* Read a floating point number into argstr
*/
numstring(void){
       int ndp=0;
       int ndig=0;
       char *s=argstr;
       int c=nextc();
       if(c=='+' || c=='-'){
               *s++=c;
               c=nextc();
       }
       while(isdigit(c) || c=='.'){
               if(s!=&argstr[NARGSTR]) *s++=c;
               if(c=='.') ndp++;
               else ndig++;
               c=nextc();
       }
       if(ndp>1 || ndig==0){
               fsp->peekc=c;
               return 0;
       }
       if(c=='e' || c=='E'){
               if(s!=&argstr[NARGSTR]) *s++=c;
               c=nextc();
               if(c=='+' || c=='-'){
                       if(s!=&argstr[NARGSTR]) *s++=c;
                       c=nextc();
               }
               if(!isdigit(c)){
                       fsp->peekc=c;
                       return 0;
               }
               while(isdigit(c)){
                       if(s!=&argstr[NARGSTR]) *s++=c;
                       c=nextc();
               }
       }
       fsp->peekc=c;
       *s='\0';
       return 1;
}
/*
* Read n numeric arguments, storing them in
* x[0], ..., x[n-1]
*/
void
numargs(int n){
       int i, c;
       for(i=0;i!=n;i++){
               do{
                       c=nextc();
               }while(strchr(" \t\n", c) || c!='.' && c!='+' && c!='-' && ispunct(c));
               fsp->peekc=c;
               if(!numstring())
                       sysfatal("%s:%d: number expected\n", fsp->name, fsp->lineno);
               x[i]=atof(argstr)*fsp->scale;
       }
}
/*
* Read a list of lists of control vertices, storing points in x[.],
* pointers in pts[.] and counts in cnt[.]
*/
void
polyarg(void){
       int nleft, l, r, c;
       double **ptsp=pts, *xp=x;
       int *cntp=cnt;
       do{
               c=nextc();
       }while(c==' ' || c=='\t');
       if(c=='{'){
               l='{';
               r='}';
       }
       else{
               l=r='\n';
               fsp->peekc=c;
       }
       nleft=1;
       *cntp=0;
       *ptsp=xp;
       for(;;){
               c=nextc();
               if(c==r){
                       if(*cntp){
                               if(*cntp&1)
                                       sysfatal("%s:%d: phase error", fsp->name, fsp->lineno);
                               *cntp/=2;
                               if(ptsp==&pts[NPTS])
                                       sysfatal("%s:%d: out of polygons", fsp->name, fsp->lineno);
                               *++ptsp=xp;
                               *++cntp=0;
                       }
                       if(--nleft==0) return;
               }
               else switch(c){
               case Beof:  return;
               case ' ':  break;
               case '\t': break;
               case '\n': break;
               case '.': case '+': case '-':
               case '0': case '1': case '2': case '3': case '4':
               case '5': case '6': case '7': case '8': case '9':
                       fsp->peekc=c;
                       if(!numstring())
                               sysfatal("%s:%d: expected number", fsp->name, fsp->lineno);
                       if(xp==&x[NX])
                               sysfatal("%s:%d: out of space", fsp->name, fsp->lineno);
                       *xp++=atof(argstr);
                       ++*cntp;
                       break;
               default:
                       if(c==l) nleft++;
                       else if(!ispunct(c)){
                               fsp->peekc=c;
                               return;
                       }
               }
       }
}

process(Biobuf *fd){
       char *s;
       int c;
       fsp=fstack;
       fsp->fd=fd;
       fsp->corebuf=0;
       fsp->peekc=Beof;
       fsp->lineno=1;
       fsp->scale=1.;
       for(;;){
               do
                       c=nextc();
               while(c==' ' || c=='\t');
               if(c==':'){
                       do
                               c=nextc();
                       while(c!='\n' && c!=Beof);
                       if(c==Beof) break;
                       continue;
               }
               while(c=='.'){
                       c=nextc();
                       if(isdigit(c)){
                               if(fsp->fd) Bungetc(fsp->fd);
                               else --fsp->corebuf;
                               c='.';
                               break;
                       }
               }
               if(c==Beof) break;
               if(c=='\n') continue;
               if(isalpha(c)){
                       s=argstr;
                       do{
                               if(isupper(c)) c=tolower(c);
                               if(s!=&argstr[NARGSTR]) *s++=c;
                               c=nextc();
                       }while(isalpha(c));
                       fsp->peekc=c;
                       *s='\0';
                       for(pplots=plots;pplots->cc;pplots++)
                               if(strncmp(argstr, pplots->cc, pplots->numc)==0)
                                       break;
                       if(pplots->cc==0)
                               sysfatal("%s:%d: %s unknown", fsp->name, fsp->lineno, argstr);
               }
               else{
                       fsp->peekc=c;
               }
               if(!pplots)
                       sysfatal("%s:%d: no command\n", fsp->name, fsp->lineno);
               switch(pplots-plots){
               case ARC:       numargs(7); rarc(x[0],x[1],x[2],x[3],x[4],x[5],x[6]); break;
               case BOX:       numargs(4); box(x[0], x[1], x[2], x[3]); break;
               case CALL:      strarg();   call(argstr); pplots=0; break;
               case CFILL:     strarg();   cfill(argstr); pplots=0; break;
               case CIRC:      numargs(3); circ(x[0], x[1], x[2]); break;
               case CLOSEPL:   strarg();   closepl(); pplots=0; break;
               case COLOR:     strarg();   color(argstr); pplots=0; break;
               case CSPLINE:   polyarg();  splin(4, cnt, pts); break;
               case DEFINE:    strarg();   define(argstr); pplots=0; break;
               case DISK:      numargs(3); plotdisc(x[0], x[1], x[2]); break;
               case DSPLINE:   polyarg();  splin(3, cnt, pts); break;
               case ERASE:     strarg();   erase(); pplots=0; break;
               case FILL:      polyarg();  fill(cnt, pts); break;
               case FRAME:     numargs(4); frame(x[0], x[1], x[2], x[3]); break;
               case FSPLINE:   polyarg();  splin(1, cnt, pts); break;
               case GRADE:     numargs(1); grade(x[0]); break;
               case IDLE:      strarg();   idle(); pplots=0; break;
               case INCLUDE:   strarg();   include(argstr); pplots=0; break;
               case LINE:      numargs(4); plotline(x[0], x[1], x[2], x[3]); break;
               case LSPLINE:   polyarg();  splin(2, cnt, pts); break;
               case MOVE:      numargs(2); move(x[0], x[1]); break;
               case OPENPL:    strarg();   openpl(argstr); pplots=0; break;
               case PARABOLA:  numargs(6); parabola(x[0],x[1],x[2],x[3],x[4],x[5]); break;
               case PAUSE:     strarg();   ppause(); pplots=0; break;
               case PEN:       strarg();   pen(argstr); pplots=0; break;
               case POINT:     numargs(2); dpoint(x[0], x[1]); break;
               case POLY:      polyarg();  plotpoly(cnt, pts); break;
               case RANGE:     numargs(4); range(x[0], x[1], x[2], x[3]); break;
               case RESTORE:   strarg();   restore(); pplots=0; break;
               case RMOVE:     numargs(2); rmove(x[0], x[1]); break;
               case RVEC:      numargs(2); rvec(x[0], x[1]); break;
               case SAVE:      strarg();   save(); pplots=0; break;
               case SBOX:      numargs(4); sbox(x[0], x[1], x[2], x[3]); break;
               case SPLINE:    polyarg();  splin(0, cnt, pts); break;
               case TEXT:      strarg();   text(argstr); pplots=0; break;
               case VEC:       numargs(2); vec(x[0], x[1]); break;
               default:
                       sysfatal("%s:%d: plot: missing case %zd\n", fsp->name, fsp->lineno, pplots-plots);
               }
       }
       return 1;
}
char *names = 0;
char *enames = 0;
char *bstash = 0;
char *estash = 0;
unsigned size = 1024;
char *nstash = 0;
void define(char *a){
       char    *ap;
       short   i, j;
       int curly = 0;
       ap = a;
       while(isalpha(*ap))ap++;
       if(ap == a)
               sysfatal("plot: no name with define\n");
       i = ap - a;
       if(names+i+1 > enames){
               names = malloc((unsigned)512);
               enames = names + 512;
       }
       fptr->name = names;
       strncpy(names, a,i);
       names += i;
       *names++ = '\0';
       if(!bstash){
               bstash = nstash = malloc(size);
               estash = bstash + size;
       }
       fptr->stash = nstash;
       while(*ap != '{')
               if(*ap == '\n'){
                       if((ap=Brdline(fsp->fd, '\n'))==0)
                               sysfatal("plot: unexpected eof");
               }
               else ap++;
       while((j=Bgetc(fsp->fd))!= Beof){
               if(j == '{')curly++;
               else if(j == '}'){
                       if(curly == 0)break;
                       else curly--;
               }
               *nstash++ = j;
               if(nstash == estash){
                       free(bstash);
                       size += 1024;
                       bstash = realloc(bstash,size);
                       if(bstash == nil)
                               sysfatal("plot: realloc: %r");
                       estash = bstash+size;
               }
       }
       *nstash++ = '\0';
       if(fptr++ >= &flibr[MAXL])
               sysfatal("too many objects");
}
void call(char *a){
       char *ap;
       struct fcall *f;
       char sav;
       double SC;
       ap = a;
       while(isalpha(*ap))ap++;
       sav = *ap;
       *ap = '\0';
       for(f=flibr;f<fptr;f++){
               if (!(strcmp(a, f->name)))
                       break;
       }
       if(f == fptr)
               sysfatal("plot: object %s not defined",a);
       *ap = sav;
       while (isspace(*ap) || *ap == ',')
               ap++;
       if (*ap != '\0')
               SC = atof(ap);
       else SC = 1.;
       if(++fsp==&fstack[NFSTACK])
               sysfatal("plot: input stack overflow");
       snprint(fsp->name, sizeof fsp->name, "call %s", f->name);
       fsp->peekc=Beof;
       fsp->lineno=1;
       fsp->corebuf=f->stash;
       fsp->fd=0;
       fsp->scale=fsp[-1].scale*SC;
}
void include(char *a){
       Biobuf *fd;
       fd=Bopen(a, OREAD);
       if(fd==0)
               sysfatal("plot: cant include %s: %r", a);
       if(++fsp==&fstack[NFSTACK])
               sysfatal("plot: input stack overflow");
       snprint(fsp->name, sizeof fsp->name, "%s", a);
       fsp->peekc=Beof;
       fsp->lineno=1;
       fsp->corebuf=0;
       fsp->fd=fd;
}
/*
* Doesn't work.  Why?
*/
int server(void){
       int fd, p[2];
       char buf[32];
       pipe(p);
       if((fd = create("/srv/plot", OWRITE, 0666)) < 0)
               sysfatal("open: %r");
       sprint(buf, "%d", p[1]);
       write(fd, buf, strlen(buf));
       close(fd);
       close(p[1]);
       return p[0];
}