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

enum {
       Edge = 5,
       Maxmag = 16
};

enum {
       Mzoom,
       Munzoom,
       Mgrid,
       Mredraw,
       Mexit
};

char *menustr[] = {
       "zoom",
       "unzoom",
       "grid",
       "redraw",
       "exit",
       nil
};

Menu menu = {
       menustr,
       nil,
       -1
};

Point lastp;
Image *red;
Image *tmp;
Image *grid;
Image *chequer;
int     screenfd;
int     mag = 4;
int     showgrid = 0;
Rectangle       screenr;
uchar   *screenbuf;

void    magnify(void);
void makegrid(void);

void
drawit(void)
{
       Rectangle r;
       border(screen, screen->r, Edge, red, ZP);
       magnify();
       r = insetrect(screen->r, Edge);
       draw(screen, r, tmp, nil, tmp->r.min);
       flushimage(display, 1);
}

int bypp;

void
main(int argc, char *argv[])
{
       Event e;
       char buf[5*12];
       ulong chan;
       int d;

       USED(argc, argv);
       if(initdraw(nil, nil, "lens") < 0){
               fprint(2, "lens: initdraw failed: %r\n");
               exits("initdraw");
       }
       einit(Emouse|Ekeyboard);
       red = allocimage(display, Rect(0, 0, 1, 1), CMAP8, 1, DRed);
       chequer = allocimage(display, Rect(0, 0, 2, 2), GREY1, 1, DBlack);
       draw(chequer, Rect(0, 0, 1, 1), display->white, nil, ZP);
       draw(chequer, Rect(1, 1, 2, 2), display->white, nil, ZP);
       lastp = divpt(addpt(screen->r.min, screen->r.max), 2);
       screenfd = open("/dev/screen", OREAD);
       if(screenfd < 0){
               fprint(2, "lens: can't open /dev/screen: %r\n");
               exits("screen");
       }
       if(read(screenfd, buf, sizeof buf) != sizeof buf){
               fprint(2, "lens: can't read /dev/screen: %r\n");
               exits("screen");
       }
       chan = strtochan(buf);
       d = chantodepth(chan);
       if(d < 8){
               fprint(2, "lens: can't handle screen format %11.11s\n", buf);
               exits("screen");
       }
       bypp = d/8;
       screenr.min.x = atoi(buf+1*12);
       screenr.min.y = atoi(buf+2*12);
       screenr.max.x = atoi(buf+3*12);
       screenr.max.y = atoi(buf+4*12);
       screenbuf = malloc(bypp*Dx(screenr)*Dy(screenr));
       if(screenbuf == nil){
               fprint(2, "lens: buffer malloc failed: %r\n");
               exits("malloc");
       }
       eresized(0);

       for(;;)
               switch(event(&e)){
               case Ekeyboard:
                       switch(e.kbdc){
                       case 'q':
                       case Kdel:
                       case Keof:
                       caseexit:
                               exits(nil);
                       case '=':
                       case '+':
                       casezoom:
                               if(mag < Maxmag){
                                       mag++;
                                       makegrid();
                                       drawit();
                               }
                               break;
                       case 'g':
                       casegrid:
                               showgrid = !showgrid;
                               makegrid();
                               drawit();
                               break;
                       case '-':
                       case '_':
                       caseunzoom:
                               if(mag > 1){
                                       mag--;
                                       makegrid();
                                       drawit();
                               }
                               break;
                       case '.':
                       case ' ':
                       caseredraw:
                               drawit();
                               break;
                       case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case'0':
                               mag = e.kbdc-'0';
                               if(mag == 0)
                                       mag = 10;
                               makegrid();
                               drawit();
                               break;
                       }
                       break;
               case Emouse:
                       if(e.mouse.buttons & 1){
                               lastp = e.mouse.xy;
                               drawit();
                       }
                       if(e.mouse.buttons & 4)
                               switch(emenuhit(3, &e.mouse, &menu)){
                               case Mzoom:
                                       goto casezoom;
                               case Munzoom:
                                       goto caseunzoom;
                               case Mgrid:
                                       goto casegrid;
                               case Mredraw:
                                       goto caseredraw;
                               case Mexit:
                                       goto caseexit;
                               }
                       break;
               }
}

void
makegrid(void)
{
       int m;
       if (grid != nil) {
               freeimage(grid);
               grid = nil;
       }
       if (showgrid) {
               m = mag;
               if (m < 5)
                       m *= 10;
               grid = allocimage(display, Rect(0, 0, m, m),
                       CHAN2(CGrey, 8, CAlpha, 8), 1, DTransparent);
               if (grid != nil){
                       draw(grid, Rect(0, 0, m, 1), chequer, nil, ZP);
                       draw(grid, Rect(0, 1, 1, m), chequer, nil, ZP);
               }
       }
}

void
eresized(int new)
{
       if(new && getwindow(display, Refnone) < 0){
               fprint(2, "lens: can't reattach to window: %r\n");
               exits("attach");
       }
       freeimage(tmp);
       tmp = allocimage(display, Rect(0, 0, Dx(screen->r)-Edge, Dy(screen->r)-Edge+Maxmag), screen->chan, 0, DNofill);
       if(tmp == nil){
               fprint(2, "lens: allocimage failed: %r\n");
               exits("allocimage");
       }
       drawit();
}

void
magnify(void)
{
       int x, y, xx, yy, dd, i;
       int dx, dy;
       int xoff, yoff;
       uchar out[8192];
       uchar sp[4];

       dx = (Dx(tmp->r)+mag-1)/mag;
       dy = (Dy(tmp->r)+mag-1)/mag;
       xoff = lastp.x-Dx(tmp->r)/(mag*2);
       yoff  = lastp.y-Dy(tmp->r)/(mag*2);

       yy = yoff;
       dd = dy;
       if(yy < 0){
               dd += dy;
               yy = 0;
       }
       if(yy+dd > Dy(screenr))
               dd = Dy(screenr)-yy;
       seek(screenfd, 5*12+bypp*yy*Dx(screenr), 0);
       if(readn(screenfd, screenbuf+bypp*yy*Dx(screenr), bypp*Dx(screenr)*dd) != bypp*Dx(screenr)*dd){
               fprint(2, "lens: can't read screen: %r\n");
               return;
       }

       for(y=0; y<dy; y++){
               yy = yoff+y;
               if(yy>=0 && yy<Dy(screenr))
                       for(x=0; x<dx; x++){
                               xx = xoff+x;
                               if(xx>=0 && xx<Dx(screenr))     /* snarf pixel at xx, yy */
                                       for(i=0; i<bypp; i++)
                                               sp[i] = screenbuf[bypp*(yy*Dx(screenr)+xx)+i];
                               else
                                       sp[0] = sp[1] = sp[2] = sp[3] = 0;

                               for(xx=0; xx<mag; xx++)
                                       if(x*mag+xx < tmp->r.max.x)
                                               for(i=0; i<bypp; i++)
                                                       out[(x*mag+xx)*bypp+i] = sp[i];
                       }
               else
                       memset(out, 0, bypp*Dx(tmp->r));
               for(yy=0; yy<mag && y*mag+yy<Dy(tmp->r); yy++){
                       werrstr("no error");
                       if(loadimage(tmp, Rect(0, y*mag+yy, Dx(tmp->r), y*mag+yy+1), out, bypp*Dx(tmp->r)) != bypp*Dx(tmp->r)){
                               exits("load");
                       }
               }
       }
       if (showgrid && mag && grid)
               draw(tmp, tmp->r, grid, nil, mulpt(Pt(xoff, yoff), mag));
}