#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ctype.h>

typedef struct Symbol Symbol;
typedef struct Event Event;
typedef struct Signal Signal;

struct Symbol {
       char *name;
       int t;
       double e;
       Symbol *next;
};
struct Event {
       double t;
       int val;
       Event *next;
       char *data;
       int line;
};
struct Signal {
       char *name;
       Event *ev;
       Signal *next;
};

int lineno, lastc, peeked;
char sname[512];
double sval;
double width, rheight;
double mint, maxt;
double left, right;
int nsig;
Biobuf *bp;
Symbol *stab[256];
Signal *sigfirst, **siglast = &sigfirst;

enum {
       SYMFREE,
       SYMNUM,
};

enum {
       VZ,
       VX,
       VL,
       VH,
       VMULT,
};

enum {
       CMD = -1,
       SYM = -2,
       NUM = -3,
       EOF = -4,
       STR = -5,
};

static int
Tfmt(Fmt *f)
{
       int n;

       n = va_arg(f->args, int);
       if(n >= 0 && n < 0x80 && isprint(n))
               return fmtprint(f, "'%c'", n);
       else
               switch(n){
               case CMD: return fmtprint(f, ".%s", sname);
               case SYM: return fmtprint(f, "'%s'", sname);
               case NUM: return fmtprint(f, "%g", sval);
               case EOF: return fmtprint(f, "EOF");
               case STR: return fmtprint(f, "%#q", sname);
               default: return fmtprint(f, "%d", n);
               }
}

static void *
emalloc(int n)
{
       void *v;

       v = malloc(n);
       if(v == nil)
               sysfatal("malloc: %r");
       memset(v, 0, n);
       setmalloctag(v, getcallerpc(&n));
       return v;
}

static void
error(int l, char *fmt, ...)
{
       va_list va;
       char *s;

       va_start(va, fmt);
       s = vsmprint(fmt, va);
       fprint(2, "%d %s\n", l, s);
       free(s);
       va_end(va);
}

static int
hash(char *s)
{
       int c;

       for(c = 0; *s != 0; s++)
               c += *s;
       return c;
}

static Symbol *
getsym(char *st)
{
       Symbol *s, **p;

       for(p = &stab[hash(st)%nelem(stab)]; s = *p, s != nil; p = &s->next){
               if(strcmp(s->name, st) == 0)
                       return s;
       }
       s = emalloc(sizeof(Symbol));
       s->name = strdup(st);
       *p = s;
       return s;
}

static int
issym(int c)
{
       return isalnum(c) || c == '_' || c >= 0x80;
}

static int
numfsm(int c, int *st)
{
       enum {
               PREDOT,
               POSTDOT,
               ESIGN,
               EDIG
       };

       switch(*st){
       case PREDOT:
               if(c == '.')
                       *st = POSTDOT;
               if(c == 'e')
                       *st = ESIGN;
               return isdigit(c) || c == '.' || c == 'e';
       case POSTDOT:
               if(c == 'e')
                       *st = ESIGN;
               return isdigit(c) || c == 'e';
       case ESIGN:
               *st = EDIG;
               return isdigit(c) || c == '+' || c == '-';
       case EDIG:
               return isdigit(c);
       }
       return 0;
}

static int
lex(void)
{
       int c;
       char *p;
       int st;

       do{
               c = Bgetc(bp);
               if(c < 0)
                       return EOF;
               if(!isspace(c))
                       break;
               if(c == '\n')
                       lineno++;
               lastc = c;
       }while(1);

       if(lastc == 10 && c == '.'){
               for(p = sname; c = Bgetc(bp), issym(c); )
                       if(p < sname + sizeof(sname) - 1)
                               *p++ = c;
               Bungetc(bp);
               *p = 0;
               return CMD;
       }
       if(isdigit(c) || c == '.'){
               st = 0;
               for(p = sname; numfsm(c, &st); c = Bgetc(bp))
                       if(p < sname + sizeof(sname) - 1)
                               *p++ = c;
               Bungetc(bp);
               *p = 0;
               sval = strtol(sname, &p, 0);
               if(*p != 0)
                       sval = strtod(sname, &p);
               if(*p != 0)
                       error(lineno, "invalid number %s", sname);
               return NUM;
       }
       if(issym(c)){
               for(p = sname; issym(c); c = Bgetc(bp))
                       if(p < sname + sizeof(sname) - 1)
                               *p++ = c;
               Bungetc(bp);
               *p = 0;
               return SYM;
       }
       if(c == '\''){
               for(p = sname; c = Bgetc(bp), c != '\'' || Bgetc(bp) == '\''; )
                       if(p < sname + sizeof(sname) - 1)
                               *p++ = c;
               Bungetc(bp);
               *p = 0;
               return STR;
       }
       return c;
}

static int
next(void)
{
       int rc;

       if(peeked != 0){
               rc = peeked;
               peeked = 0;
               return rc;
       }
       return lex();
}

static int
peek(void)
{
       if(peeked == 0)
               peeked = lex();
       return peeked;
}

static void
expect(int n)
{
       int s;

       if((s = peek()) != n)
               error(lineno, "expected %T, got %T", n, s);
       else
               next();
}

static double expr(void);

static double
factor(void)
{
       int t;
       double g;
       Symbol *s;

       switch(t = next()){
       case NUM:
               return sval;
       case SYM:
               s = getsym(sname);
               if(s->t != SYMNUM)
                       error(lineno, "not a number: %s", s->name);
               return s->e;
       case '(':
               g = expr();
               expect(')');
               return g;
       default:
               error(lineno, "factor: unexpected %T", t);
               return 0;
       }
}

static double
term(void)
{
       double g;

       g = factor();
       while(peek() == '*' || peek() == '/'){
               switch(next()){
               case '*': g *= factor(); break;
               case '/': g /= factor(); break;
               }
       }
       return g;
}

static double
expr(void)
{
       int s;
       double g;

       s = 1;
       if(peek() == '+' || peek() == '-')
               s = next() == '-' ? -1 : 1;
       g = s * term();
       while(peek() == '+' || peek() == '-'){
               s = next() == '-' ? -1 : 1;
               g += s * term();
       }
       return g;
}

static void
assign(Symbol *s)
{
       next();
       if(s->t != SYMFREE){
               error(lineno, "symbol already exists: %s", s->name);
               return;
       }
       s->t = SYMNUM;
       s->e = expr();
       expect(';');
}

static int
parseval(Event *e)
{
       int t;

       switch(t = next()){
       case SYM:
               if(strcmp(sname, "x") == 0)
                       return VX;
               if(strcmp(sname, "z") == 0)
                       return VZ;
               e->data = strdup(sname);
               return VMULT;
       case NUM:
               if(sval == 0)
                       return VL;
               if(sval == 1)
                       return VH;
               e->data = smprint("%g", sval);
               return VMULT;
       case STR:
               e->data = strdup(sname);
               return VMULT;
       default:
               error(lineno, "unexpected %T", t);
               return VZ;
       }
}

static void
append(double off, Event ***ep, Event *e)
{
       Event *f;

       for(; e != nil; e = e->next){
               f = emalloc(sizeof(Event));
               f->t = e->t + off;
               f->val = e->val;
               f->line = e->line;
               if(e->data != nil)
                       f->data = strdup(e->data);
               **ep = f;
               *ep = &f->next;
       }
}

static void
freeev(Event *e)
{
       Event *en;

       for(; e != nil; e = en){
               en = e->next;
               free(e->data);
               free(e);
       }
}

static Event *
events(double *off, int ronly)
{
       int rela;
       Event *e, *ev, **ep;
       int i, n;
       double f;
       Symbol *sy;
       double len;
       int line;

       rela = 0;
       line = 0;
       ev = nil;
       ep = &ev;
       for(;;)
               switch(peek()){
               case '+':
                       rela = 1;
                       next();
                       break;
               case '}':
               case ';':
                       return ev;
               case ':':
                       next();
                       e = emalloc(sizeof(Event));
                       e->t = *off;
                       e->val = parseval(e);
                       e->line = line;
                       line = 0;
                       *ep = e;
                       ep = &e->next;
                       break;
               case '|':
                       line = 1;
                       next();
                       break;
               default:
                       f = expr();
                       if(peek() == '{'){
                               next();
                               if(f < 0 || isNaN(f) || f >= 0x7fffffff){
                                       error(lineno, "invalid repeat count");
                                       f = 0;
                               }
                               len = 0;
                               e = events(&len, 1);
                               expect('}');
                               n = f + 0.5;
                               for(i = 0; i < n; i++){
                                       append(*off, &ep, e);
                                       *off += len;
                               }
                               if(*off > maxt) maxt = *off;
                               freeev(e);
                               break;
                       }
                       if(ronly && !rela){
                               error(lineno, "only relative addressing allowed");
                               rela = 1;
                       }
                       if(peek() == SYM){
                               next();
                               sy = getsym(sname);
                               if(sy->t == SYMFREE)
                                       error(lineno, "undefined %s", sy->name);
                               else
                                       f *= sy->e;
                       }
                       if(rela)
                               *off += f;
                       else
                               *off = f;
                       if(peek() == '['){
                               next();
                               sy = getsym(sname);
                               if(sy->t != SYMFREE && sy->t != SYMNUM)
                                       error(lineno, "already defined %s", sy->name);
                               else{
                                       sy->t = SYMNUM;
                                       sy->e = *off;
                               }
                               expect(']');
                       }
                       if(*off < mint) mint = *off;
                       if(*off > maxt) maxt = *off;
                       rela = 0;
                       break;
               }

}

static void
signal(char *st)
{
       Signal *s;
       double off;

       s = emalloc(sizeof(Signal));
       s->name = strdup(st);
       s->ev = events(&off, 0);
       expect(';');
       *siglast = s;
       siglast = &s->next;
       nsig++;
}

static void
slantfill(double x1, double x2, double t, double b, double tw)
{
       double x;
       double sw = 0.05;
       double soff = 0.05;

       for(x = x1; x < x2 - sw; x += soff)
               print("line from %g,%g to %g,%g\n", x, t, x+sw, b);
       if(x < x2)
               print("line from %g,%g to %g,%g\n", x, t, (2*sw*tw+2*tw*x+sw*x2)/(sw+2*tw), (sw*t+t*(x-x2)+b*(2*tw-x+x2))/(sw+2*tw));
}

static void
sigout(Signal *s, double top)
{
       Event *e, *n;
       double l, w, t, b, x1, x2, tw, m;

       for(e = s->ev; e != nil && e->next != nil && e->next->t < left; e = e->next)
               ;
       if(e == nil)
               return;
       b = top - rheight * 0.75;
       t = top - rheight * 0.25;
       tw = width * 0.003;
       m = (t+b)/2;
       l = width * 0.2;
       w = width * 0.8 / (right - left);
       x1 = l;
       print("\"%s\" ljust at %g,%g\n", s->name, width * 0.1, m);
       while(n = e->next, n != nil && n->t < right){
               x2 = (n->t - left) * w + l;
               if(n->line)
                       print("line from %g,%g to %g,%g dashed\n", x2, 0.0, x2, nsig*rheight);
               switch(e->val){
               case VZ:
                       print("line from %g,%g to %g,%g\n", x1, m, x2, m);
                       break;
               case VL:
                       print("line from %g,%g to %g,%g\n", x1, b, x2-tw, b);
                       print("line from %g,%g to %g,%g\n", x2-tw, b, x2, m);
                       break;
               case VH:
                       print("line from %g,%g to %g,%g\n", x1, t, x2-tw, t);
                       print("line from %g,%g to %g,%g\n", x2-tw, t, x2, m);
                       break;
               case VMULT:
                       print("\"%s\" at %g,%g\n", e->data, (x1+x2)/2, m);
                       if(0){
               case VX:
                               slantfill(x1, x2-tw, t, b, tw);
                       }
                       print("line from %g,%g to %g,%g\n", x1, b, x2-tw, b);
                       print("line from %g,%g to %g,%g\n", x2-tw, b, x2, m);
                       print("line from %g,%g to %g,%g\n", x1, t, x2-tw, t);
                       print("line from %g,%g to %g,%g\n", x2-tw, t, x2, m);
                       break;
               default:
                       fprint(2, "unknown event type %d\n", e->val);
               }
               switch(n->val){
               case VL:
                       print("line from %g,%g to %g,%g\n", x2, m, x2+tw, b);
                       break;
               case VH:
                       print("line from %g,%g to %g,%g\n", x2, m, x2+tw, t);
                       break;
               case VMULT:
               case VX:
                       print("line from %g,%g to %g,%g\n", x2, m, x2+tw, b);
                       print("line from %g,%g to %g,%g\n", x2, m, x2+tw, t);
                       break;
               }
               e = e->next;
               if(e->val == VZ)
                       x1 = x2;
               else
                       x1 = x2 + tw;
       }
       x2 = (right - left) * w + l;
       switch(e->val){
       case VZ:
               print("line from %g,%g to %g,%g\n", x1, m, x2, m);
               break;
       case VL:
               print("line from %g,%g to %g,%g\n", x1, b, x2, b);
               break;
       case VH:
               print("line from %g,%g to %g,%g\n", x1, t, x2, t);
               break;
       case VMULT:
               print("\"%s\" at %g,%g\n", e->data, (x1+x2)/2, m);
               if(0){
       case VX:
                       slantfill(x1, x2, t, b, tw);
               }
               print("line from %g,%g to %g,%g\n", x1, b, x2, b);
               print("line from %g,%g to %g,%g\n", x1, t, x2, t);
               break;
       default:
               fprint(2, "unknown event type %d\n", e->val);
       }
}

static void
parseopts(char *l)
{
       char *f[3];
       int rc;

       rc = tokenize(l, f, nelem(f));
       if(rc != 3){
               error(lineno, ".TPS wrong syntax");
               return;
       }
       width = strtod(f[1], 0);
       rheight = strtod(f[2], 0);
}

static void
cleansym(void)
{
       Symbol **p, *s, *sn;
       Signal *si, *sin;
       Event *e, *en;

       for(p = stab; p < stab + nelem(stab); p++)
               for(s = *p, *p = nil; s != nil; s = sn){

                       free(s->name);
                       sn = s->next;
                       free(s);
               }
       memset(stab, 0, sizeof(stab));

       for(si = sigfirst; si != nil; si = sin){
               for(e = si->ev; e != nil; e = en){
                       en = e->next;
                       free(e);
               }
               free(si->name);
               sin = si->next;
               free(si);
       }
       siglast = &sigfirst;
}

static void
diagram(char *l)
{
       Symbol *s;
       Signal *si;
       int t;
       double top;

       lastc = 10;
       mint = 0;
       maxt = 0;
       nsig = 0;
       parseopts(l);

       for(;;){
               switch(t = next()){
               case SYM:
                       s = getsym(sname);
                       if(peek() == '=')
                               assign(s);
                       else
                               signal(s->name);
                       break;
               case STR:
                       signal(sname);
                       break;
               case CMD:
                       if(strcmp(sname, "TPE") == 0)
                               goto end;
                       error(lineno, "unknown command %s", sname);
                       break;
               default:
                       error(lineno, "unexpected %T", t);
               }
       }
end:

       print(".PS %g %g\n", width, rheight * nsig);
       left = mint;
       right = maxt;
       top = rheight * nsig;
       for(si = sigfirst; si != nil; si = si->next, top -= rheight)
               sigout(si, top);
       print(".PE\n");

       cleansym();
}

static void
run(char *f)
{
       char *l;

       if(f == nil)
               bp = Bfdopen(0, OREAD);
       else
               bp = Bopen(f, OREAD);
       if(bp == nil)
               sysfatal("open: %r");
       Blethal(bp, nil);
       lineno = 1;

       for(;;){
               l = Brdstr(bp, '\n', 1);
               if(l == nil)
                       break;
               lineno++;
               if(strncmp(l, ".TPS", 4) == 0 && (!l[4] || isspace(l[4])))
                       diagram(l);
               else
                       print("%s\n", l);
               free(l);
       }
       Bterm(bp);
}

static void
usage(void)
{
       fprint(2, "usage: %s [ files ]\n", argv0);
       exits("usage");
}

void
main(int argc, char **argv)
{
       int i;

       fmtinstall('T', Tfmt);
       quotefmtinstall();

       ARGBEGIN {
       default: usage();
       } ARGEND;

       if(argc == 0)
               run(nil);
       else
               for(i = 0; i < argc; i++)
                       run(argv[i]);

       exits(nil);
}