#include        <u.h>
#include        <libc.h>
#include        <draw.h>
#include        <cursor.h>
#include        <event.h>
#include        <bio.h>
#include        "proof.h"

int     res;
int     hpos;
int     vpos;
int     DIV = 11;

Point offset;
Point xyoffset = { 0,0 };

Rectangle       view[MAXVIEW];
Rectangle       bound[MAXVIEW];         /* extreme points */
int     nview = 1;

int     lastp;  /* last page number we were on */

#define NPAGENUMS       200
struct pagenum {
       int     num;
       long    adr;
} pagenums[NPAGENUMS];
int     npagenums;

int     curfont, cursize;

char    *getcmdstr(void);

static void     initpage(void);
static void     view_setup(int);
static Point    scale(Point);
static void     clearview(Rectangle);
static int      addpage(int);
static void     spline(Image *, int, Point *);
static int      skipto(int, int);
static void     wiggly(int);
static void     devcntrl(void);
static void     eatline(void);
static int      getn(void);
static int      botpage(int);
static void     getstr(char *);

#define Do screen->r.min
#define Dc screen->r.max

/* declarations and definitions of font stuff are in font.c and main.c */

static void
initpage(void)
{
       int i;

       view_setup(nview);
       for (i = 0; i < nview-1; i++)
               draw(screen, view[i], screen, nil, view[i+1].min);
       clearview(view[nview-1]);
       offset = view[nview-1].min;
       vpos = 0;
}

static void
view_setup(int n)
{
       int i, j, v, dx, dy, r, c;

       switch (n) {
       case 1: r = 1; c = 1; break;
       case 2: r = 1; c = 2; break;
       case 3: r = 1; c = 3; break;
       case 4: r = 2; c = 2; break;
       case 5: case 6: r = 2; c = 3; break;
       case 7: case 8: case 9: r = 3; c = 3; break;
       default: r = (n+2)/3; c = 3; break; /* finking out */
       }
       dx = (Dc.x - Do.x) / c;
       dy = (Dc.y - Do.y) / r;
       v = 0;
       for (i = 0; i < r && v < n; i++)
               for (j = 0; j < c && v < n; j++) {
                       view[v] = screen->r;
                       view[v].min.x = Do.x + j * dx;
                       view[v].max.x = Do.x + (j+1) * dx;
                       view[v].min.y = Do.y + i * dy;
                       view[v].max.y = Do.y + (i+1) * dy;
                       v++;
               }
}

static void
clearview(Rectangle r)
{
       draw(screen, r, display->white, nil, r.min);
}

int resized;
void eresized(int new)
{
       /* this is called if we are resized */
       if(new && getwindow(display, Refnone) < 0)
               drawerror(display, "can't reattach to window");
       initpage();
       resized = 1;
}

static Point
scale(Point p)
{
       p.x /= DIV;
       p.y /= DIV;
       return addpt(xyoffset, addpt(offset,p));
}

static int
addpage(int n)
{
       int i;

       for (i = 0; i < npagenums; i++)
               if (n == pagenums[i].num)
                       return i;
       if (npagenums < NPAGENUMS-1) {
               pagenums[npagenums].num = n;
               pagenums[npagenums].adr = offsetc();
               npagenums++;
       }
       return npagenums;
}

void
readpage(void)
{
       int c, i, a, alpha, phi;
       static int first = 0;
       int m, n, gonow = 1;
       Rune r[32], t;
       Point p,q,qq;

       offset = screen->clipr.min;
       esetcursor(&deadmouse);
       while (gonow)
       {
               c = getc();
               switch (c)
               {
               case -1:
                       esetcursor(0);
                       if (botpage(lastp+1)) {
                               initpage();
                               break;
                       }
                       exits(0);
               case 'p':       /* new page */
                       lastp = getn();
                       addpage(lastp);
                       if (first++ > 0) {
                               esetcursor(0);
                               botpage(lastp);
                               esetcursor(&deadmouse);
                       }
                       initpage();
                       break;
               case '\n':      /* when input is text */
               case ' ':
               case 0:         /* occasional noise creeps in */
                       break;
               case '0': case '1': case '2': case '3': case '4':
               case '5': case '6': case '7': case '8': case '9':
                       /* two motion digits plus a character */
                       hpos += (c-'0')*10 + getc()-'0';

               /* FALLS THROUGH */
               case 'c':       /* single ascii character */
                       r[0] = getrune();
                       r[1] = 0;
                       dochar(r);
                       break;

               case 'C':
                       for(i=0; ; i++){
                               t = getrune();
                               if(isspace(t))
                                       break;
                               r[i] = t;
                       }
                       r[i] = 0;
                       dochar(r);
                       break;

               case 'N':
                       r[0] = getn();
                       r[1] = 0;
                       dochar(r);
                       break;

               case 'D':       /* draw function */
                       switch (getc())
                       {
                       case 'l':       /* draw a line */
                               n = getn();
                               m = getn();
                               p = Pt(hpos,vpos);
                               q = addpt(p, Pt(n,m));
                               hpos += n;
                               vpos += m;
                               line(screen, scale(p), scale(q), 0, 0, 0, display->black, ZP);
                               break;
                       case 'c':       /* circle */
                               /*nop*/
                               m = getn()/2;
                               p = Pt(hpos+m,vpos);
                               hpos += 2*m;
                               ellipse(screen, scale(p), m/DIV, m/DIV, 0, display->black, ZP);
                               /* p=currentpt; p.x+=dmap(m/2);circle bp,p,a,ONES,Mode*/
                               break;
                       case 'e':       /* ellipse */
                               /*nop*/
                               m = getn()/2;
                               n = getn()/2;
                               p = Pt(hpos+m,vpos);
                               hpos += 2*m;
                               ellipse(screen, scale(p), m/DIV, n/DIV, 0, display->black, ZP);
                               break;
                       case 'a':       /* arc */
                               p = scale(Pt(hpos,vpos));
                               n = getn();
                               m = getn();
                               hpos += n;
                               vpos += m;
                               q = scale(Pt(hpos,vpos));
                               n = getn();
                               m = getn();
                               hpos += n;
                               vpos += m;
                               qq = scale(Pt(hpos,vpos));
                               /*
                                 * tricky: convert from 3-point clockwise to
                                 * center, angle1, delta-angle counterclockwise.
                                */
                               a = hypot(qq.x-q.x, qq.y-q.y);
                               phi = atan2(q.y-p.y, p.x-q.x)*180./PI;
                               alpha = atan2(q.y-qq.y, qq.x-q.x)*180./PI - phi;
                               if(alpha < 0)
                                       alpha += 360;
                               arc(screen, q, a, a, 0, display->black, ZP, phi, alpha);
                               break;
                       case '~':       /* wiggly line */
                               wiggly(0);
                               break;
                       default:
                               break;
                       }
                       eatline();
                       break;
               case 's':
                       n = getn();     /* ignore fractional sizes */
                       if (cursize == n)
                               break;
                       cursize = n;
                       if (cursize >= NFONT)
                               cursize = NFONT-1;
                       break;
               case 'f':
                       curfont = getn();
                       break;
               case 'H':       /* absolute horizontal motion */
                       hpos = getn();
                       break;
               case 'h':       /* relative horizontal motion */
                       hpos += getn();
                       break;
               case 'w':       /* word space */
                       break;
               case 'V':
                       vpos = getn();
                       break;
               case 'v':
                       vpos += getn();
                       break;
               case '#':       /* comment */
               case 'n':       /* end of line */
                       eatline();
                       break;
               case 'x':       /* device control */
                       devcntrl();
                       break;
               default:
                       fprint(2, "unknown input character %o %c at offset %lud\n", c, c, offsetc());
                       exits("bad char");
               }
       }
       esetcursor(0);
}

static void
spline(Image *b, int n, Point *pp)
{
       long w, t1, t2, t3, fac=1000;
       int i, j, steps=10;
       Point p, q;

       for (i = n; i > 0; i--)
               pp[i] = pp[i-1];
       pp[n+1] = pp[n];
       n += 2;
       p = pp[0];
       for(i = 0; i < n-2; i++)
       {
               for(j = 0; j < steps; j++)
               {
                       w = fac * j / steps;
                       t1 = w * w / (2 * fac);
                       w = w - fac/2;
                       t2 = 3*fac/4 - w * w / fac;
                       w = w - fac/2;
                       t3 = w * w / (2*fac);
                       q.x = (t1*pp[i+2].x + t2*pp[i+1].x +
                               t3*pp[i].x + fac/2) / fac;
                       q.y = (t1*pp[i+2].y + t2*pp[i+1].y +
                               t3*pp[i].y + fac/2) / fac;
                       line(b, p, q, 0, 0, 0, display->black, ZP);
                       p = q;
               }
       }
}

/* Have to parse skipped pages, to find out what fonts are loaded. */
static int
skipto(int gotop, int curp)
{
       char *p;
       int i;

       if (gotop == curp)
               return 1;
       for (i = 0; i < npagenums; i++)
               if (pagenums[i].num == gotop) {
                       if (seekc(pagenums[i].adr) == Beof) {
                               fprint(2, "can't rewind input\n");
                               return 0;
                       }
                       return 1;
               }
       if (gotop <= curp) {
           restart:
               if (seekc(0) == Beof) {
                       fprint(2, "can't rewind input\n");
                       return 0;
               }
       }
       for(;;){
               p = rdlinec();
               if (p == 0) {
                       if(gotop>curp){
                               gotop = curp;
                               goto restart;
                       }
                       return 0;
               } else if (*p == 'p') {
                       lastp = curp = atoi(p+1);
                       addpage(lastp); /* maybe 1 too high */
                       if (curp>=gotop)
                               return 1;
               }
       }
}

static void
wiggly(int skip)
{
       Point p[300];
       int c,i,n;
       for (n = 1; (c = getc()) != '\n' && c>=0; n++) {
               ungetc();
               p[n].x = getn();
               p[n].y = getn();
       }
       p[0] = Pt(hpos, vpos);
       for (i = 1; i < n; i++)
               p[i] = addpt(p[i],p[i-1]);
       hpos = p[n-1].x;
       vpos = p[n-1].y;
       for (i = 0; i < n; i++)
               p[i] = scale(p[i]);
       if (!skip)
               spline(screen,n,p);
}

static void
devcntrl(void)  /* interpret device control functions */
{
       char str[80];
       int n;

       getstr(str);
       switch (str[0]) {       /* crude for now */
       case 'i':       /* initialize */
               break;
       case 'T':       /* device name */
               getstr(devname);
               break;
       case 't':       /* trailer */
               break;
       case 'p':       /* pause -- can restart */
               break;
       case 's':       /* stop */
               break;
       case 'r':       /* resolution assumed when prepared */
               res=getn();
               DIV = floor(.5 + res/(100.0*mag));
               if (DIV < 1)
                       DIV = 1;
               mag = res/(100.0*DIV); /* adjust mag according to DIV coarseness */
               break;
       case 'f':       /* font used */
               n = getn();
               getstr(str);
               loadfontname(n, str);
               break;
       /* these don't belong here... */
       case 'H':       /* char height */
               break;
       case 'S':       /* slant */
               break;
       case 'X':
               break;
       }
       eatline();
}

int
isspace(int c)
{
       return c==' ' || c=='\t' || c=='\n';
}

static void
getstr(char *is)
{
       uchar *s = (uchar *) is;

       for (*s = getc(); isspace(*s); *s = getc())
               ;
       for (; !isspace(*s); *++s = getc())
               ;
       ungetc();
       *s = 0;
}

static void
eatline(void)
{
       int c;

       while ((c=getc()) != '\n' && c >= 0)
               ;
}

static int
getn(void)
{
       int n, c, sign;

       while (c = getc())
               if (!isspace(c))
                       break;
       if(c == '-'){
               sign = -1;
               c = getc();
       }else
               sign = 1;
       for (n = 0; '0'<=c && c<='9'; c = getc())
               n = n*10 + c - '0';
       while (c == ' ')
               c = getc();
       ungetc();
       return(n*sign);
}

static int
botpage(int np) /* called at bottom of page np-1 == top of page np */
{
       char *p;
       int n;

       while (p = getcmdstr()) {
               if (*p == '\0')
                       return 0;
               if (*p == 'q')
                       exits(p);
               if (*p == 'c')          /* nop */
                       continue;
               if (*p == 'm') {
                       mag = atof(p+1);
                       if (mag <= .1 || mag >= 10)
                               mag = DEFMAG;
                       allfree();      /* zap fonts */
                       DIV = floor(.5 + res/(100.0*mag));
                       if (DIV < 1)
                               DIV = 1;
                       mag = res/(100.0*DIV);
                       return skipto(np-1, np);        /* reprint the page */
               }
               if (*p == 'x') {
                       xyoffset.x += atoi(p+1)*100;
                       skipto(np-1, np);
                       return 1;
               }
               if (*p == 'y') {
                       xyoffset.y += atoi(p+1)*100;
                       skipto(np-1, np);
                       return 1;
               }
               if (*p == '/') {        /* divide into n pieces */
                       nview = atoi(p+1);
                       if (nview < 1)
                               nview = 1;
                       else if (nview > MAXVIEW)
                               nview = MAXVIEW;
                       return skipto(np-1, np);
               }
               if (*p == 'p') {
                       if (p[1] == '\0'){      /* bare 'p' */
                               if(skipto(np-1, np))
                                       return 1;
                               continue;
                       }
                       p++;
               }
               if ('0'<=*p && *p<='9') {
                       n = atoi(p);
                       if(skipto(n, np))
                               return 1;
                       continue;
               }
               if (*p == '-' || *p == '+') {
                       n = atoi(p);
                       if (n == 0)
                               n = *p == '-' ? -1 : 1;
                       if(skipto(np - 1 + n, np))
                               return 1;
                       continue;
               }
               if (*p == 'd') {
                       dbg = 1 - dbg;
                       continue;
               }

               fprint(2, "illegal;  try q, 17, +2, -1, p, m.7, /2, x1, y-.5 or return\n");
       }
       return 0;
}