#include <stdio.h>

/* this is just for fork/exec maybe they should go? */
#include <sys/types.h>
#include <unistd.h>

#include "nawm.h"
#include "bindings.h"
#include "inform.h"
#ifdef USE_CACHE
#include "cache.h"
#endif 

#define XK_MISCELLANY
#include <X11/keysymdef.h>
#include <X11/Xutil.h>

/* I have no idea what i'm doing with this stuff, here goes nothing */
/*

  XTestStopInput        openwin,xfree86
  XTestFakeInput        openwin,xfree86
  XTestQueryInputSize   openwin,xfree86
  XTestPressButton      openwin,xfree86
  XTestMovePointer      openwin,xfree86
  XTestGetInput         openwin,xfree86
  XTestPressKey         openwin,xfree86
  XTestFlush            openwin,xfree86
  XTestReset            openwin,xfree86

  relevant docs:	
  /afs/athena/astaff/project/x11r5/src/mit/doc/extensions/xtest1.mm
  /afs/athena/astaff/project/x11r5/src/mit/extensions/include/xtestext1.h

  */
#if HAVE_XTEST_EXTENSION
#include <X11/Xmd.h>
#include <X11/extensions/XTest.h>
#include <X11/extensions/xtestext1.h>
#endif

extern int current_mode;
extern Window current_window;
extern Window focus_window;
extern BindInfo bind_list;
extern unsigned bind_cnt;

int screen_shift_x = 0;
int screen_shift_y = 0;

int marker_x = 0;
int marker_y = 0;

static void move_all_windows ();
#define check_x_coord(x) check_coord(x)
#define check_y_coord(x) check_coord(x)

int check_coord(def)
     int def;
{
  if(def == USE_DXMARKER || 
     def == USE_DYMARKER ||
     def == USE_X 	 || 
     def == USE_Y )  {
    Window w1, w2;
    int x, y;
    unsigned int k;
    int rx, ry;
    XQueryPointer(dpy, root, &w1, &w2, &rx, &ry, &x, &y, &k);
    return ((def == USE_X) ? rx :
	    (def == USE_Y) ? ry :
	    (def == USE_DXMARKER) ? rx - marker_x : 
	    (def == USE_DYMARKER) ? ry - marker_y : def);
  } else
    return((def == USE_MARKERX) ? marker_x :
	   (def == USE_MARKERY) ? marker_y : def );
}

void lower_cmd(bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  if (current_window)
    XLowerWindow(dpy, current_window);
}

void raise_cmd(bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  if (current_window)
    XRaiseWindow(dpy, current_window);
}

void warp_to_cmd(bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  
  XWarpPointer(dpy, None, root, 0,0,0,0, 
	       check_x_coord(bi->data.warpto.x), 
	       check_y_coord(bi->data.warpto.y));
}

void warp_cmd(bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  XWarpPointer(dpy, None, None, 0,0,0,0, 
	       check_x_coord(bi->data.warp.dx), 
	       check_y_coord(bi->data.warp.dy));
}

void map_cmd(bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  if(!current_window) return;
  XMapWindow(dpy, current_window);
}

void unmap_cmd(bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  if(!current_window) return;
  XUnmapWindow(dpy, current_window);
}

void find_cmd(bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  current_window = getwindowbyname(bi->data.find.win_name);
  if (!current_window) {
    XBell(dpy, 100);
    return;
  }
}


void warp_to_window_cmd(bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  XWindowAttributes attr;

  if (!current_window) return;
  deiconify_window(current_window);
  if (!XGetWindowAttributes(dpy, current_window, &attr)) return;	/* Oops, no such window */
  if (attr.x+attr.width < 0 || attr.x > DisplayWidth(dpy, screen) ||
      attr.y+attr.height < 0 || attr.y > DisplayHeight(dpy, screen)) {
    move_all_windows ((DisplayWidth(dpy, screen) - attr.width)/2 - attr.x,
		      (DisplayHeight(dpy, screen) - attr.height)/2 - attr.y,
		      0);
  }
  XWarpPointer(dpy, None, current_window, 0,0,0,0, attr.width/2, attr.height/2);
}     

void goto_window_cmd(bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  error_mode = BEEP_ON_ERROR;
  current_window = bi->data.goto_window.window_id;
  XRaiseWindow(dpy, current_window);
  warp_to_window_cmd(bi, ev);
  XSync (dpy, False);
  error_mode = SPEW_ON_ERROR;
}

void move_cmd(bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  XWindowAttributes attr;

  inform_user(5, "move_cmd");
  if (!current_window) return;
  XGetWindowAttributes(dpy, current_window, &attr);  
  XMoveWindow(dpy, current_window,
	      attr.x + check_x_coord(bi->data.move.dx),
	      attr.y + check_y_coord(bi->data.move.dy));
}

void move_to_cmd(bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  if (!current_window) return;

  XMoveWindow(dpy, current_window,
	      check_x_coord(bi->data.moveto.x),
	      check_y_coord(bi->data.moveto.y));
  
}

typedef struct _move_window_stuff 
{
  int dx,dy;
  Window except;
} move_window_stuff;

static void move_one_window (k,d,stuff)
     cache_key   k;
     cache_data *d;
     move_window_stuff *stuff;
{
  
  if ((Window)k != stuff->except)
    XMoveWindow(dpy, k, d->x + stuff->dx, d->y + stuff->dy);
}

static void move_all_windows (dx, dy, except)
     int dx, dy;
     Window except;
{
  if (dx == 0 && dy == 0) return;
#ifdef USE_CACHE
  if (use_cache) 
    {
      move_window_stuff stuff;
      stuff.dx = dx;
      stuff.dy = dy;
      stuff.except = except;
      cache_iter(move_one_window,&stuff);
    }
  else
#endif USE_CACHE
    {
      Window *children;
      unsigned int numchildren, n;
      Window rootret;
      Window parent;
      XWindowAttributes attr;
      
      XQueryTree (dpy, root, &rootret, &parent, &children, &numchildren);
      for (n = 0; n < numchildren; n++)
	if (children[n] != except) 
	  {
	    XGetWindowAttributes(dpy, children[n], &attr);  
	    XMoveWindow(dpy, children[n],
			attr.x + dx,
			attr.y + dy);
	  }
      XFree ((char *) children);
    }
  screen_shift_x -= dx;
  screen_shift_y -= dy;
}

void screen_cmd (bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  move_all_windows (-bi->data.screen.dx, -bi->data.screen.dy, (Window) 0);
}

void screen_to_cmd (bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  move_all_windows (screen_shift_x - bi->data.screento.x,
		    screen_shift_y - bi->data.screento.y,
		    (Window) 0);
}

void carry_cmd (bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  move_all_windows (-bi->data.carry.dx, -bi->data.carry.dy, current_window);
}

void carry_to_cmd (bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  move_all_windows (screen_shift_x - bi->data.carryto.x,
		    screen_shift_y - bi->data.carryto.y,
		    current_window);
}

void size_cmd(bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  int w, h;
  int use_dx;
  int use_dy;
  XWindowAttributes attr;
  if (!current_window) return;
  
  use_dx = check_x_coord(bi->data.size.dx);
  use_dy = check_y_coord(bi->data.size.dy);

  XGetWindowAttributes(dpy, current_window, &attr);  

  if(use_dx < 0 && attr.width <= -use_dx )
    w = attr.width;
  else 
    w = attr.width + use_dx;
    
  if(use_dy < 0 && attr.height <= -use_dy )
    h = attr.height;
  else 
    h = attr.height + use_dy;

  XResizeWindow(dpy, current_window,
		w,h);
}

void size_to_cmd(bi, ev)  /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  if (!current_window) return;
  XResizeWindow(dpy, current_window,
	      bi->data.sizeto.x,
	      bi->data.sizeto.y);
}

void set_mode_cmd (bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  int new;

  inform_user(5, "set_mode_cmd");
  new =  (current_mode == bi->data.setmode.mode) ? DEFAULT_MODE : bi->data.setmode.mode;
  set_mode (new);
}


void iconify_cmd(bi, ev)  /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  if (current_window) {
    XUnmapWindow(dpy, current_window);
    make_icon(current_window);
  }
}

void deiconify_cmd(bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  if (current_window) {
    deiconify_icon(current_window);
  }
}

static void prev_next_cmd(dir)
     int dir;
{
  Window rootret;
  Window parent;
  Window *children;
  int numchildren;
  XWindowAttributes attr;
  int me, found, n;
  
  XQueryTree (dpy, root, &rootret, &parent, &children, &numchildren);
  for (n = 0; n < numchildren && children[n] != current_window; n++);
  me = n;
  found = (n < numchildren);
  do {
    n += dir;
    if (n >= numchildren) n = 0;
    if (n < 0) n = numchildren - 1;
    if (found) {
      if (n == me) break;
    } else {
      found = 1;
      me = n;
    }
    XGetWindowAttributes(dpy, children[n], &attr);
    if (attr.map_state != IsUnmapped) {
      current_window = children[n];
      break;
    }
  } while (1);
  XFree ((char *) children);
}



void prev_cmd (bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  prev_next_cmd(-1);
}

void next_cmd (bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  prev_next_cmd(1);
}

void bell_cmd(bi, ev)
     BindInfo bi;
     XEvent *ev;
{
  XBell(dpy, 100);
}

void set_marker_cmd (bi, ev)
     BindInfo bi;
     XEvent *ev;
{
  Window w1, w2;
  int x, y;
  unsigned int k;
  XQueryPointer(dpy, root, &w1, &w2, &marker_x, &marker_y, &x, &y, &k);
}

void exch_mark_cmd(bi, ev)
     BindInfo bi;
     XEvent *ev;
{
  Window w1, w2;
  int x, y;
  int oldx, oldy;
  unsigned int k;
  
  oldx = marker_x;
  oldy = marker_y;
  XQueryPointer(dpy, root, &w1, &w2, &marker_x, &marker_y, &x, &y, &k);
  XWarpPointer(dpy, None, root, 0,0,0,0, 
	       oldx, oldy);  
}

void set_x_cmd( bi, ev)
     BindInfo bi;
     XEvent *ev;
{
  marker_x = check_x_coord(bi->data.setx.x);
}     

void set_y_cmd( bi, ev)
     BindInfo bi;
     XEvent *ev;
{
  marker_y = check_y_coord(bi->data.sety.y);
}

void kill_cmd(bi, ev)
     BindInfo bi;
     XEvent *ev;
{
  XKillClient(dpy, current_window);
}

void event_window_cmd (bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  current_window = focus_window;
}

void bind_cmd (bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  BindInfo new, bnd;
  BindInfo_S key;
  unsigned int i;
  
  if (!current_window) {
    XBell(dpy, 100);
    return;
  }
  ungrab_things();
  get_key_combo (&key);
  new = NULL;
  for(i = 0, bnd = bind_list; i < bind_cnt; bnd++, i++) 
    if ((bnd->type == key.type) &&
	(bnd->whatmode == ANY_MODE || bnd->whatmode == current_mode) &&
	(bnd->modifiers == key.modifiers) &&
	((bnd->type == KeyPress) ? (bnd->keycode == key.keycode) : (bnd->button == key.button)) &&
	(bnd->func == goto_window_cmd)) {
      new = bnd;
      break;
    }
  if (!new) {
    new = realloc_binding();
    new->func = goto_window_cmd;
    new->whatmode = bi->whatmode;
    new->type = key.type;
    new->keycode = key.keycode;
    new->modifiers = key.modifiers;
    new->button = key.button;
  }
  new->data.goto_window.window_id = current_window;
  grab_things();
}

unbind_cmd (bi, ev) /*ARGSUSED*/
     BindInfo bi;
     XEvent *ev;
{
  XBell(dpy, 100);
}

get_key_combo (bi)
     BindInfo bi;
{
  XEvent ev;
  KeySym ksym;
  int done = 0;
  Window win;
  int x, y;
  int ig_int;
  unsigned int ig_uint;
  Window ig_w;
  XSizeHints hints;

  XQueryPointer (dpy, root, &ig_w, &ig_w, &x, &y, &ig_int, &ig_int, &ig_uint);
  hints.x = x-5;
  hints.y = y-5;
  hints.width = 10;
  hints.height = 10;
  hints.flags = USPosition | USSize;
  win = XCreateSimpleWindow (dpy, root, hints.x, hints.y, hints.width, hints.height, 0, 0, 0);
  XSelectInput (dpy, win, KeyPressMask | ButtonPressMask | ExposureMask | ButtonReleaseMask);
  XSetNormalHints(dpy, win, &hints);
  XMapWindow(dpy, win);
  while (!done) {
    XNextEvent (dpy, &ev);
    switch (ev.type) {
    case Expose:
      XGrabPointer (dpy, win, False, ButtonPressMask | ButtonReleaseMask,
		    GrabModeAsync, GrabModeAsync, win, None, CurrentTime);
      break;
    case KeyPress:
      ksym = keycode_to_keysym(ev.xkey.keycode);
      if ((ksym < XK_Shift_L ||		/* Trust me, they're all in here. */
	  ksym > XK_Hyper_R)		/* Hey, this is *sample* code from O'Reilly.. */
	  && (ksym != XK_Multi_key)) {	/* ...Well, except for this one... */
	bi->type = KeyPress;
	bi->keycode = ev.xkey.keycode;
	bi->modifiers = ev.xkey.state;
	done = 1;
      }
      break;
      
    case ButtonPress:
      bi->type = ButtonPress;
      bi->button = ev.xbutton.button;
      bi->modifiers = ev.xbutton.state;
      done = 1;
      break;
    }
  }
  XUngrabPointer (dpy, CurrentTime);
  XDestroyWindow (dpy, win);
}

    

void grab_pointer_cmd (bi, ev)
     BindInfo bi;
     XEvent *ev;
{
  inform_user(5, "grab_pointer_cmd");
  if(current_window)
    XGrabPointer (dpy, root, False, ButtonPressMask | ButtonReleaseMask |
		  PointerMotionMask,
		  GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
}

void ungrab_pointer_cmd (bi, ev)
     BindInfo bi;
     XEvent *ev;
{
  inform_user(5, "ungrab_pointer_cmd");
  XUngrabPointer(dpy, CurrentTime);
}

#if HAVE_XTEST_EXTENSION
static void new_mouseclick_cmd(bi, ev)
    BindInfo bi;
    XEvent *ev;
{
  int destx = check_x_coord(bi->data.mouseclick.x);
  int desty = check_y_coord(bi->data.mouseclick.y);
  int delay = 10;
  Window w1, w2;
  int x, y;
  unsigned int k;
  int rx, ry;
  XQueryPointer(dpy, root, &w1, &w2, &rx, &ry, &x, &y, &k);
  /* set_modifier_state(ev->state, bi->data.mouseclick.state); */
  XTestMovePointer(dpy, 0, 
		   &delay,
		   &destx,&desty,
		   1);
  XTestPressButton(dpy, 0, 10,
		   bi->data.mouseclick.button,
		   XTestSTROKE);
  XTestMovePointer(dpy, 0, 
		   &delay,
		   &x,&y,
		   1);
  /* set_modifier_stat(bi->data.mouseclick.state, ev->state); */
  XTestFlush(dpy);
}

static void set_modifier_state(old,new)
     unsigned int old,new;
{
  /* this function can't be properly implemented */
}
#endif

static void old_mouseclick_cmd(bi, ev)
    BindInfo bi;
    XEvent *ev;
{
  XEvent send;
  Window junk, cwin, *list;
  XWindowAttributes attr;
  unsigned int length;
  int i, x, y, buttonMask=0;

  XQueryTree(dpy, RootWindow(dpy, screen), &junk, &junk, 
	     &list, &length);

  x = check_x_coord(bi->data.mouseclick.x);
  y = check_y_coord(bi->data.mouseclick.y);
  
  for (i = length - 1; i >= 0; i--) {
    if (!XGetWindowAttributes(dpy, list[i], &attr)) continue ;
    if (x > attr.x && x < (attr.x + attr.width) &&
	y > attr.y && y < (attr.y + attr.height) &&
	attr.map_state == IsViewable) {
      break;
    }
  }

  if (i >= 0) {
    cwin = nawmClientWindow(dpy, list[i]);

    memcpy(&send, ev, sizeof(XEvent));
    send.type = ButtonPress;
    send.xbutton.window = cwin;
    send.xbutton.subwindow = None;
    send.xbutton.x_root = x;
    send.xbutton.y_root = y;
    send.xbutton.x = x - attr.x;
    send.xbutton.y = y - attr.y;
    switch(bi->data.mouseclick.button) {
    case 1:
      send.xbutton.button = Button1;
      buttonMask = Button1Mask;
      break;
    case 2:
      send.xbutton.button = Button2;
      buttonMask = Button2Mask;
      break;
    case 3:
      send.xbutton.button = Button3;
      buttonMask = Button3Mask;
      break;
    case 4:
      send.xbutton.button = Button4;
      buttonMask = Button4Mask;
      break;
    case 5:
      send.xbutton.button = Button5;
      buttonMask = Button5Mask;
      break;
    }
    send.xbutton.state = bi->data.mouseclick.modifiers;
    XSendEvent(dpy, cwin, True, ButtonPressMask, &send);
    send.type = ButtonRelease;
    send.xbutton.time += 10;
    send.xbutton.state |= buttonMask; /* button being released is down right? */
    XSendEvent(dpy, cwin, True, ButtonReleaseMask, &send);
  }

  XFree((char *)list);
}

void mouseclick_cmd(bi, ev)
    BindInfo bi;
    XEvent *ev;
{
#if HAVE_XTEST_EXTENSION
  if (use_xtest_extension) 
    new_mouseclick_cmd(bi,ev);
  else
#endif
    old_mouseclick_cmd(bi,ev);
} 


void break_cmd(bi, env)
    BindInfo bi;
    XEvent *env;
{
  inform_user(5, "break_cmd");
  XSetInputFocus(dpy, PointerRoot, None, CurrentTime);
}


/* XXX This doesn't avoid zombie's yet,
       I'm leaning towards giving up and using system() for simplicity 

   XXX well now if you #define DOUBLE_FORK it avoids zombies (?)
       but that means it will background itself when it does an exec
       argh, I'm really leaning towards using system(),
       but then the user has to remember to use & in his commands 
       or else he'll lock up his nawm.
*/
#define DOUBLE_FORK 1 /* This seems to work, and it's half-way reasonable */

void system_cmd(bi,env)
     BindInfo bi;
     XEvent *env;
{
  inform_user(5, "system_cmd");
  switch (fork()) {
  case -1: /* error */
       perror("nawm: fork");
       return;
  case 0: /* child */
       execl("/bin/sh", "/bin/sh", "-c", bi->data.system.cmd, (char*)0);
       perror("nawm: exec");
       exit(2);
  default:  /* parent */
#if DOUBLE_FORK
    switch (fork()) {
    case -1: /* error */
      perror("nawm: second fork (will cause zombie)");
      return;
    case 0: /* child (new nawm) */
      return;
    default: /* parent (old nawm) */
      exit(0);
    }
#endif
  }
}

      
void exit_cmd(bi,env)
     BindInfo bi;
     XEvent *env;
{
  inform_user(5, "exit_cmd");
  exit(0);
  abort();
  return;
}
