#include <xsimple.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/wait.h>
#include "imgview.h"
#include "reader/common.h"

VARS v;


static void sighandler(int sig)
{
   v.timeout++;
}

static void mycls(void)
{
   if (! v.curimg) {
       settitle(WINNAME);
       XFillRectangle(xv.d, xv.w, xv.gc, 0, 0, v.win_w, v.win_h);
       XSync(xv.d, False);
       return;
   }

   if (v.ulx > 0 && v.ulx > v.oulx)
       XFillRectangle(xv.d, xv.w, xv.gc, v.oulx, 0, v.ulx - v.oulx, v.win_h);
   if (v.uly > 0 && v.uly > v.ouly)
       XFillRectangle(xv.d, xv.w, xv.gc, 0, v.ouly, v.win_w, v.uly - v.ouly);
   if (v.lrx < (v.win_w - 1) && v.lrx < v.olrx)
       XFillRectangle(xv.d, xv.w, xv.gc, v.lrx + 1, 0, v.olrx - v.lrx,
               v.win_h);
   if (v.lry < (v.win_h - 1) && v.lry < v.olry)
       XFillRectangle(xv.d, xv.w, xv.gc, 0, v.lry + 1, v.win_w,
                v.olry - v.lry);
}

static void putimg(void)
{
   static int first = 1;

   if (! v.curimg) { if (first) first--; return; }

   v.ulx = v.img_x - v.curimg->width/2;
   v.uly = v.img_y - v.curimg->height/2;
   v.lrx = v.ulx + v.curimg->width - 1;
   v.lry = v.uly + v.curimg->height - 1;

   if (! xv.pmap) {
       GC putgc;
       xv.pmap = mkpixmap(v.curimg->width, v.curimg->height, v.curimg->depth);
       if (v.curimg->depth == 1) {
           static GC mgc = NULL;
           if (! mgc) mgc = XCreateGC(xv.d, xv.pmap, 0, NULL);
           if (v.black == 1) {
               XSetBackground(xv.d, mgc, 0); XSetForeground(xv.d, mgc, 1);
           } else {
               XSetBackground(xv.d, mgc, 1); XSetForeground(xv.d, mgc, 0);
           }
           putgc = mgc;
       } else
           putgc = xv.gc;
       XPutImage(xv.d, xv.pmap, putgc, v.curimg, 0, 0, 0, 0, v.curimg->width,
               v.curimg->height);
   }

   if (! first) {
       mycls();
       if (v.curimg->depth == 1)
           XCopyPlane(xv.d, xv.pmap, xv.w, v.monogc, 0, 0, v.curimg->width,
                   v.curimg->height, v.ulx, v.uly, 1);
       else
           XCopyArea(xv.d, xv.pmap, xv.w, xv.gc, 0, 0, v.curimg->width,
                   v.curimg->height, v.ulx, v.uly);
   } else
       first--;

   v.oulx = v.ulx; v.ouly = v.uly; v.olrx = v.lrx; v.olry = v.lry;
}

static void zoomimg(void)
{
   int w, h;
   unsigned char *dp1, *dp2;
   unsigned short **sdpp1 = (unsigned short **)&dp1,
           **sdpp2 = (unsigned short **)&dp2;
   unsigned long **ldpp1 = (unsigned long **)&dp1,
           **ldpp2 = (unsigned long **)&dp2;
   int x, y, y2, sbit, sbit2, dbit;
   int pad;
   int subsmp = 0;

   if (v.zimg) { rmximage(v.zimg); v.zimg = NULL; }
   if (xv.pmap) { rmpixmap(xv.pmap); xv.pmap = 0; }

   if (v.zoom == 1.) { v.curimg = v.img; putimg(); return; }

   if (v.zoom > 1.) {
       w = v.img->width * v.zoom; h = v.img->height * v.zoom;
   } else {
       subsmp = 1 / v.zoom;
       w = (v.img->width + subsmp - 1) / subsmp;
       h = (v.img->height + subsmp - 1) / subsmp;
   }

   pad = 0;
   v.zimg = mkximage(w, h, v.img->depth, &pad);

   if (v.img->depth == 1) memset(v.zimg->data, 0, v.zimg->bytes_per_line * h);

   dp1 = v.img->data; dp2 = v.zimg->data;

   if (v.zoom > 1.) {
       int i, j;

       for (y = 0; y < v.img->height; y++) {
           unsigned char *tp = dp1;
           for (i = 0; i < v.zoom; i++) {
               dp1 = tp;
               if (v.img->depth == 24) {
                   for (x = 0; x < v.img->width; x++) {
                       for (j = 0; j < v.zoom; j++)
                           *(*ldpp2)++ = *(*ldpp1);
                       (*ldpp1)++;
                   }
               } else if (v.img->depth == 16) {
                   for (x = 0; x < v.img->width; x++) {
                       for (j = 0; j < v.zoom; j++)
                           *(*sdpp2)++ = *(*sdpp1);
                       (*sdpp1)++;
                   }
               } else {
                   for (x = 0, sbit = dbit = 7; x < v.img->width; x++,
                           sbit--) {
                       if (sbit < 0) { dp1++; sbit = 7; }
                       sbit2 = (*dp1 >> sbit) & 1;
                       for (j = 0; j < v.zoom; j++, dbit--) {
                           if (dbit < 0) { dp2++; dbit = 7; }
                           *dp2 |= sbit2 << dbit;
                       }
                   }
                   dp1++; dp2++;
               }
               if (pad) dp2 += pad;
           }
           if (v.pad) dp1 += v.pad;
       }
   } else {
       for (y = 0, y2 = 0; y < v.img->height; y += subsmp, y2++) {
           dp1 = v.img->data + y * v.img->bytes_per_line;
           if (v.img->depth == 24) {
               for (x = 0; x < v.img->width; x += subsmp)
                   *(*ldpp2)++ = *(*ldpp1 + x);
           } else if (v.img->depth == 16) {
               for (x = 0; x < v.img->width; x += subsmp)
                   *(*sdpp2)++ = *(*sdpp1 + x);
           } else {
               for (x = 0, sbit = dbit = 7; x < v.img->width; x += subsmp,
                       sbit -= subsmp, dbit--) {
                   if (sbit < 0) { dp1 += (abs(sbit) + 7) / 8; sbit = 7; }
                   if (dbit < 0) { dp2++; dbit = 7; }
                   *dp2 |= ((*dp1 >> sbit) & 1) << dbit;
               }
               dp2++;
           }
           if (pad) dp2 += pad;
       }
   }

   v.curimg = v.zimg;
   putimg();
}

static int getfmt(int fd)
{
   unsigned char magic[4];

   if (read(fd, magic, 4) < 4)
       return -1;
   lseek(fd, 0, SEEK_SET);

   if (magic[0] == 'P' && (magic[1] >= '1' && magic[1] <= '6'))
       return FMT_PPM;
   else if (*(unsigned int *)magic == 0x38464947)
       return FMT_GIF;
   else if (*(unsigned short *)magic == 0xd8ff)
       return FMT_JPEG;
   else if (*(unsigned int *)magic == 0x2a004d4d ||
           *(unsigned int *)magic == 0x002a4949)
       return FMT_TIFF;
   else if (*(unsigned int *)magic == 0x474e5089)
       return FMT_PNG;
   else if (magic[0] == 0x0a)
       return FMT_PCX;
   else if (magic[2] >= 1 && magic[2] <= 8)
       return FMT_JBIG;
   else
       return -1;
}

static void readfile(char *fname)
{
   char *tail = NULL, tmp[512];
   char *tmpname = NULL;

   if (v.img) { rmximage(v.img); v.img = NULL; }
   if (v.zimg) { rmximage(v.zimg); v.zimg = NULL; }
   if (xv.pmap) { rmpixmap(xv.pmap); xv.pmap = 0; }
   v.curimg = NULL;

   v.black = 1; v.white = 0;

   mycls();

   if (fname) {
       int fd;

       if ((tail = strrchr(fname, '/'))) tail++; else tail = fname;

       if (! strncmp(fname, "http://", 7)) {
           int r;
           tmpname = (char *)malloc(128);
           sprintf(tmpname, "%s/.imgview.XXXXXX", getenv("HOME"));
           if ((fd = mkstemp(tmpname)) < 0) {
               perror("mkstemp");
               goto err;
           } else
               close(fd);
           sprintf(tmp, "wget -q -O %s %s", tmpname, fname);
           if ((r = system(tmp))) {
               if (r < 0)
                   perror("system");
               else
                   fprintf(stderr, "wget failed with exit status %d\n",
                           WEXITSTATUS(r));
               goto err;
           }
           if ((fd = open(tmpname, O_RDONLY)) < 0) {
               perror("open");
               goto err;
           }
       } else {
           if ((fd = open(fname, O_RDONLY)) < 0) {
               perror("open");
               goto err;
           }
       }
       switch (getfmt(fd)) {
           case FMT_PPM:
               v.img = readppm(fd); break;
           case FMT_GIF:
               v.img = readgif(fd); break;
           case FMT_JPEG:
               v.img = readjpeg(fd); break;
           case FMT_TIFF:
               v.img = readtiff(fd, fname); break;
           case FMT_PNG:
               v.img = readpng(fd); break;
           case FMT_PCX:
               v.img = readpcx(fd); break;
           case FMT_JBIG:
               v.img = readjbig(fd); break;
           default:
               close(fd); v.img = readxbm(fname); break;
       }
   } else
       v.img = readppm(0);

   if (! v.img) goto err;

   if (fname) {
       sprintf(tmp, "%s - %s [%dx%d]", WINNAME, tail, v.img->width,
               v.img->height);
   } else {
       sprintf(tmp, "%s - stdin [%dx%d]", WINNAME, v.img->width,
               v.img->height);
   }
   settitle(tmp);

   v.curimg = v.img;
   v.img_x = v.win_w/2; v.img_y = v.win_h/2;
   if (v.keepzoom)
       zoomimg();
   else {
       v.zoom = 1.0;
       putimg();
   }
   if (tmpname) { unlink(tmpname); free(tmpname); }
   return;

err:
   if (fname)
       sprintf(tmp, "%s - %s [ERROR]", WINNAME, tail);
   else
       sprintf(tmp, "%s - stdin [ERROR]", WINNAME);
   settitle(tmp);
   putimg();
   if (fname)
       fprintf(stderr, "%s: Error while reading `%s'\n", APPNAME, tail);
   else
       fprintf(stderr, "%s: Error while reading stdin\n", APPNAME);
   if (tmpname) { unlink(tmpname); free(tmpname); }
}

static void usage(void)
{
   fprintf(stderr, "usage: iv [-s] [-v] [file ...]\n");
   exit(1);
}

static void init(int argc, char **argv)
{
   int c, s_flag = 0;
   char rcname[512];
   FILE *f;

   v.win_x = WIN_X; v.win_y = WIN_Y; v.win_w = WIN_W; v.win_h = WIN_H;
   strcpy(v.bgcolor_s, BGCOLOR); strcpy(v.tbgcolor_s, TBGCOLOR);
   strcpy(v.bwbgcolor_s, BWBGCOLOR); strcpy(v.bwfgcolor_s, BWFGCOLOR);
   v.xymove = XYMOVE;
   v.delay = DELAY;
   v.jbigw = JBIGW;
   v.jbigh = JBIGH;
   v.zoom = 1.0;
   v.keepzoom = v.slide = v.timeout = 0;
   v.verbose = 0;

   while ((c = getopt(argc, argv, ":sv")) != -1) {
       switch (c) {
           case 's':
               s_flag++;
               break;
           case 'v':
               v.verbose++;
               break;
           case '?':
           case ':':
           default:
               usage();
       }
   }

   if ((argc - optind) && s_flag) {
       signal(SIGALRM, sighandler);
       v.slide++;
   }

   atexit(writeconfig);

   sprintf(rcname, "%s/.imgviewrc", getenv("HOME"));
   if ((f = fopen(rcname, "r")))
       readconfig(f);

   xinit(APPNAME, CLASSNAME, WINNAME, v.win_x, v.win_y, v.win_w, v.win_h, 1,
           v.bgcolor_s, v.bgcolor_s, 0);

   v.win_w = xv.width; v.win_h = xv.height;
   v.bgcolor = getnamedcolor(v.bgcolor_s, &v.bgcolor_rgb);
   v.tbgcolor = getnamedcolor(v.tbgcolor_s, &v.tbgcolor_rgb);
   v.bwbgcolor = getnamedcolor(v.bwbgcolor_s, NULL);
   v.bwfgcolor = getnamedcolor(v.bwfgcolor_s, NULL);

   XSetForeground(xv.d, xv.gc, v.bgcolor);

   v.monogc = XCreateGC(xv.d, xv.w, 0, NULL);
   XSetBackground(xv.d, v.monogc, v.bwbgcolor);
   XSetForeground(xv.d, v.monogc, v.bwfgcolor);
}

int main(int argc, char **argv)
{
   struct timeval t;
   int fnum;

   init(argc, argv);

   fnum = optind;
   readfile(argc - fnum ? argv[fnum] : NULL);
   if (v.slide) alarm(v.delay);

   for (;;) {
       XEvent e;

       if (v.slide && v.timeout) {
           if (++fnum == argc) exit(0);
           readfile(argv[fnum]);
           v.timeout = 0; alarm(v.delay);
       }

       while (getevent(&e)) {
           static int shift = 0, ctrl = 0;
           int k;

           switch (e.type) {
               case ConfigureNotify:
                   {
                       v.win_x = xv.x; v.win_y = xv.y;
                       if (xv.width != v.win_w || xv.height != v.win_h) {
                           v.win_w = xv.width; v.win_h = xv.height;
                           v.img_x = v.win_w/2; v.img_y = v.win_h/2;
                           putimg();
                       }
                   }
                   break;
               case Expose:
                   if (v.curimg) {
                       for (;;) {
                           XExposeEvent *ee = (XExposeEvent *)&e;
                           if (v.curimg->depth == 1)
                               XCopyPlane(xv.d, xv.pmap, xv.w, v.monogc,
                                       ee->x - v.ulx, ee->y - v.uly,
                                       ee->width, ee->height, ee->x, ee->y, 1);
                           else
                               XCopyArea(xv.d, xv.pmap, xv.w, xv.gc,
                                       ee->x - v.ulx, ee->y - v.uly,
                                       ee->width, ee->height, ee->x, ee->y);
                           if (! ee->count)
                               break;
                           XNextEvent(xv.d, &e);
                       }
                   }
                   break;
               case KeyPress:
                   k = XLookupKeysym((XKeyEvent *)&e, 0);
                   switch (k) {
                       case XK_Shift_L:
                       case XK_Shift_R:
                           shift++; break;
                       case XK_Control_L:
                       case XK_Control_R:
                           ctrl++; break;
                       case XK_Left:
                           v.img_x += v.xymove * (ctrl ? 5 : 1);
                           putimg();
                           break;
                       case XK_Right:
                           v.img_x -= v.xymove * (ctrl ? 5 : 1);
                           putimg();
                           break;
                       case XK_Up:
                           v.img_y += v.xymove * (ctrl ? 5 : 1);
                           putimg();
                           break;
                       case XK_Down:
                           v.img_y -= v.xymove * (ctrl ? 5 : 1);
                           putimg();
                           break;
                       case XK_Prior:
                           v.zoom *= 2;
                           zoomimg();
                           break;
                       case XK_Next:
                           v.zoom /= 2;
                           zoomimg();
                           break;
                       case XK_Home:
                           v.img_x = v.win_w/2;
                           v.img_y = v.win_h/2;
                           if (ctrl) {
                               v.zoom = 1.0; zoomimg();
                           } else {
                               putimg();
                           }
                           break;
                       case XK_p:
                           if (argc > 1) {
                               int ofnum = fnum;
                               fnum = (shift ? 1 :
                                       (fnum > 1 ? fnum - 1 : fnum));
                               if (fnum != ofnum) {
                                   readfile(argv[fnum]);
                                   if (v.slide) gettimeofday(&t, NULL);
                               }
                           }
                           break;
                       case XK_n:
                           if (argc > 1) {
                               int ofnum = fnum;
                               fnum = (shift ? argc - 1 :
                                       (fnum < argc - 1 ? fnum + 1 : fnum));
                               if (fnum != ofnum) {
                                   readfile(argv[fnum]);
                                   if (v.slide) gettimeofday(&t, NULL);
                               }
                           }
                           break;
                       case XK_z:
                           v.keepzoom ^= 1; break;
                       case XK_q:
                           exit(0);
                   }
                   if (v.slide && k != XK_Shift_L && k != XK_Shift_R &&
                           k != XK_Control_L && k != XK_Control_R) {
                       v.timeout = 0; alarm(v.delay);
                   }
                   break;
               case KeyRelease:
                   switch (XLookupKeysym((XKeyEvent *)&e, 0)) {
                       case XK_Shift_L:
                       case XK_Shift_R:
                           shift--; break;
                       case XK_Control_L:
                       case XK_Control_R:
                           ctrl--; break;
                   }
                   break;
           }
       }
       usleep(1000);
   }
}