/*
 *  Copyright (C) 2000 James Macnicol
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or 
 * implied warranty.
 */

#include <math.h>
#include <limits.h>
#include "screenhack.h"

#ifndef MIN
#define MIN(a, b) (((a) < (b))?(a):(b))
#endif

#ifndef MAX
#define MAX(a, b) (((a) > (b))?(a):(b))
#endif

#define MINX 0.0
#define MINY 0.0
/* This should be *way* slower than the spotlight hack was */
#define X_PERIOD (45000.0 * 3)
#define Y_PERIOD (36000.0 * 3)

struct state {
  Display *dpy;
  Window window;
  Screen *screen;

  int sizex, sizey;

  int delay;
  int duration;
  int pixwidth, pixheight, pixspacex, pixspacey, lensoffsetx, lensoffsety;
  Bool lenses;

  GC window_gc;

  XImage *orig_map;
  Pixmap pm;

  int tlx, tly, s;

  int sinusoid_offset;

  time_t start_time;
  async_load_state *img_loader;
};


static long currentTimeInMs(struct state *st)
{ 
  struct timeval curTime;
  unsigned long ret_unsigned;
#ifdef GETTIMEOFDAY_TWO_ARGS
  struct timezone tz = {0,0};
  gettimeofday(&curTime, &tz);
#else
  gettimeofday(&curTime);
#endif
  ret_unsigned = curTime.tv_sec *1000U + curTime.tv_usec / 1000;
  return (ret_unsigned <= LONG_MAX) ? ret_unsigned : -1 - (long)(ULONG_MAX - ret_unsigned);
}

static void *
zoom_init (Display *dpy, Window window)
{
  struct state *st = (struct state *) calloc (1, sizeof(*st));
  XGCValues gcv;
  XWindowAttributes xgwa;
  Colormap cmap;
  unsigned long bg;
  long gcflags;
  int nblocksx, nblocksy;

  st->dpy = dpy;
  st->window = window;
  XGetWindowAttributes(st->dpy, st->window, &xgwa);
  st->screen = xgwa.screen;
  st->sizex = xgwa.width;
  st->sizey = xgwa.height;
  cmap = xgwa.colormap;
  bg = get_pixel_resource(st->dpy, cmap, "background", "Background");

  st->delay = get_integer_resource(st->dpy, "delay", "Integer");
  if (st->delay < 1)
    st->delay = 1;
  st->duration = get_integer_resource (st->dpy, "duration", "Seconds");
  if (st->duration < 1)
    st->duration = 1;
  st->pixwidth = get_integer_resource(st->dpy, "pixwidth", "Integer");
  if (st->pixwidth < 1)
    st->pixwidth = 1;
  st->pixheight = get_integer_resource(st->dpy, "pixheight", "Integer");
  if (st->pixheight < 1)
    st->pixheight = 1;
  st->pixspacex = get_integer_resource(st->dpy, "pixspacex", "Integer");
  if (st->pixspacex < 0)
    st->pixspacex = 0;
  st->pixspacey = get_integer_resource(st->dpy, "pixspacey", "Integer");
  if (st->pixspacey < 0)
    st->pixspacey = 0;

  if (st->sizex < 50 || st->sizey < 50) {  /* tiny window */
    st->pixwidth = 10;
    st->pixheight = 10;
  }

  st->lenses = get_boolean_resource(st->dpy, "lenses", "Boolean");
  st->lensoffsetx = get_integer_resource(st->dpy, "lensoffsetx", "Integer");
  st->lensoffsetx = MAX(0, MIN(st->pixwidth, st->lensoffsetx));
  st->lensoffsety = get_integer_resource(st->dpy, "lensoffsety", "Integer");
  st->lensoffsety = MAX(0, MIN(st->pixwidth, st->lensoffsety));

  if (st->sizex > 2560 || st->sizey > 2560)  /* Retina displays */
    {
      int s = 3;
      st->pixwidth  *= s;
      st->pixheight *= s;
      st->pixspacex *= s;
      st->pixspacey *= s;
    }

  gcv.function = GXcopy;
  gcflags = GCForeground|GCFunction;
  gcv.foreground = bg;
  st->window_gc = XCreateGC(st->dpy, st->window, gcflags, &gcv);


  st->orig_map = NULL;
  st->pm = XCreatePixmap(st->dpy, st->window, st->sizex, st->sizey, xgwa.depth);

  XFillRectangle(st->dpy, st->window, st->window_gc, 0, 0, st->sizex, st->sizey);
  XSetWindowBackground(st->dpy, st->window, bg);

  st->start_time = time ((time_t *) 0);
  st->img_loader = load_image_async_simple (0, xgwa.screen, st->window,
                                            st->pm, 0, 0);

  nblocksx = (int)ceil((double)st->sizex / (double)(st->pixwidth + st->pixspacex));
  nblocksy = (int)ceil((double)st->sizey / (double)(st->pixheight + st->pixspacey));
  if (st->lenses)
    st->s = MAX((nblocksx - 1) * st->lensoffsetx + st->pixwidth, 
	    (nblocksy - 1) * st->lensoffsety + st->pixheight) * 2;
  else
    st->s = MAX(nblocksx, nblocksy) * 2;

  st->sinusoid_offset = random();

  return st;
}

static unsigned long
zoom_draw (Display *dpy, Window window, void *closure)
{
  struct state *st = (struct state *) closure;
  unsigned x, y, i, j;

  long now;
  unsigned long now_unsigned;

  if (st->img_loader)   /* still loading */
    {
      st->img_loader = load_image_async_simple (st->img_loader, 0, 0, 0, 0, 0);
      if (! st->img_loader) {  /* just finished */
        XClearWindow (st->dpy, st->window);
        st->start_time = time ((time_t *) 0);
        if (!st->lenses) {
          st->orig_map = XGetImage(st->dpy, st->pm, 0, 0, st->sizex, st->sizey, ~0L, ZPixmap);
/*          XFreePixmap(st->dpy, st->pm);
          st->pm = 0;*/
        }
      }
      return st->delay;
    }

  if (!st->img_loader &&
      st->start_time + st->duration < time ((time_t *) 0)) {
    st->img_loader = load_image_async_simple (0, st->screen, st->window,
                                              st->pm, 0, 0);
    return st->delay;
  }

#define nrnd(x) (random() % (x))

  now = currentTimeInMs(st);
  now_unsigned = (unsigned long) now + st->sinusoid_offset;  /* don't run multiple screens in lock-step */
  now = (now_unsigned <= LONG_MAX) ? now_unsigned : -1 - (long)(ULONG_MAX - now_unsigned);

  /* find new x,y */
  st->tlx = ((1. + sin(((double)now) / X_PERIOD * 2. * M_PI))/2.0)
    * (st->sizex - st->s/2) /* -s/4 */ + MINX;
  st->tly = ((1. + sin(((double)now) / Y_PERIOD * 2. * M_PI))/2.0)
    * (st->sizey - st->s/2) /* -s/4 */ + MINY;

  if (st->lenses) {
    for (x = i = 0; x < st->sizex; x += (st->pixwidth + st->pixspacex), ++i)
      for (y = j = 0; y < st->sizey; y += (st->pixheight + st->pixspacey), ++j) {
	XCopyArea(st->dpy, st->pm /* src */, st->window /* dest */, st->window_gc,
		  st->tlx + i * st->lensoffsetx /* src_x */, 
		  st->tly + j * st->lensoffsety /* src_y */,
		  st->pixwidth, st->pixheight,
		  x /* dest_x */, y /* dest_y */);
      }
  } else {
    for (x = i = 0; x < st->sizex; x += (st->pixwidth + st->pixspacex), ++i)
      for (y = j = 0; y < st->sizey; y += (st->pixheight + st->pixspacey), ++j) {
	XSetForeground(st->dpy, st->window_gc, XGetPixel(st->orig_map, st->tlx+i, st->tly+j));
	XFillRectangle(st->dpy, st->window, st->window_gc, 
		       i * (st->pixwidth + st->pixspacex),
		       j * (st->pixheight + st->pixspacey), st->pixwidth, st->pixheight);
      }
  }

  return st->delay;
}

static void
zoom_reshape (Display *dpy, Window window, void *closure, 
                 unsigned int w, unsigned int h)
{
}

static Bool
zoom_event (Display *dpy, Window window, void *closure, XEvent *event)
{
  struct state *st = (struct state *) closure;
  if (screenhack_event_helper (dpy, window, event))
    {
      st->start_time = 0;
      return True;
    }
  return False;
}

static void
zoom_free (Display *dpy, Window window, void *closure)
{
  struct state *st = (struct state *) closure;
  XFreeGC (st->dpy, st->window_gc);
  if (st->orig_map) XDestroyImage (st->orig_map);
  if (st->pm) XFreePixmap (st->dpy, st->pm);
  free (st);
}


static const char *zoom_defaults[] = {
  "*dontClearRoot: True",
  ".foreground:    white",
  ".background:    #111111",
/* ".lowrez:        true", */
  "*fpsSolid:	   true",
  "*lenses:        true",
  "*delay:         10000",
  "*duration:      120",
  "*pixwidth:      40",
  "*pixheight:     40",
  "*pixspacex:     2",
  "*pixspacey:     2",
  "*lensoffsetx:   5",
  "*lensoffsety:   5",
#ifdef HAVE_MOBILE
  "*ignoreRotation: True",
  "*rotateImages:   True",
#endif
  0
};

static XrmOptionDescRec zoom_options[] = {
  { "-lenses", ".lenses", XrmoptionNoArg, "true" },
  { "-no-lenses", ".lenses", XrmoptionNoArg, "false" },
  { "-delay", ".delay", XrmoptionSepArg, 0 },
  { "-duration",  ".duration", XrmoptionSepArg, 0 },
  { "-pixwidth", ".pixwidth", XrmoptionSepArg, 0 },
  { "-pixheight", ".pixheight", XrmoptionSepArg, 0 },
  { "-pixspacex", ".pixspacex", XrmoptionSepArg, 0 },
  { "-pixspacey", ".pixspacey", XrmoptionSepArg, 0 },
  { "-lensoffsetx", ".lensoffsetx", XrmoptionSepArg, 0 },
  { "-lensoffsety", ".lensoffsety", XrmoptionSepArg, 0 },
  { 0, 0, 0, 0 }
};

XSCREENSAVER_MODULE ("Zoom", zoom)
