#include <stdio.h>
#include <ctype.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <sys/param.h>
#include "macros.h"

#define LINELEN 2048

char *pname;

char *TYPES[5] = { "int", "string", "var", "func", "undef" };

Arg success, failure;
#define FAILURE &failure
#define SUCCESS &success

/*
 * For functions that want a solid value to work with,
 * and don't care what type it is. get_arg_real evaluates
 * variables and functions until it gets down to a real
 * value: int, string, or undef.
 */
Arg *get_arg_real(e, name, a, dest)
     XEvent *e;
     char *name;
     Arg **a;
     Arg **dest;
{
  Arg *work;

  work = *a;

  if (work == NULL)
    {
      fprintf(stderr, "%s: too few arguments to %s\n",
	      pname, name);
      return FAILURE;
    }

  while (work->type == ARG_VAR || work->type == ARG_FUNC)
    {
      if (work->type == ARG_VAR)
	work = work->data.v;
      else
	work = (Arg *)work->data.ProcCall.p(e, work->data.ProcCall.args);
    }

  *dest = work;
  *a = (*a)->next;
  return SUCCESS;
}

Arg *get_arg_int(e, name, a, dest)
     XEvent *e;
     char *name;
     Arg **a;
     int *dest;
{
  Arg *work, *b;

  b = *a;

  if (get_arg_real(e, name, &b, &work) == FAILURE)
    return FAILURE;

  if (work->type != ARG_INT)
    {
      fprintf(stderr, "%s: %s expected an int, got %s\n",
	      pname, name, TYPES[work->type]);
      return FAILURE;
    }

  *dest = work->data.i;
  *a = (*a)->next;
  return SUCCESS;
}

Arg *get_arg_var(e, name, a, dest)
     XEvent *e;
     char *name;
     Arg **a;
     Arg **dest;
{
  if (*a == NULL)
    {
      fprintf(stderr, "%s: too few arguments to %s\n",
	      pname, name);
      return FAILURE;
    }

  if ((*a)->type != ARG_VAR)
    {
      fprintf(stderr, "%s: %s expected a var, got %s\n",
	      pname, name, TYPES[(*a)->type]);
      return FAILURE;
    }

  *dest = (*a)->data.v;
  *a = (*a)->next;
  return SUCCESS;
}

Arg *set(e, a)
     XEvent *e;
     Arg *a;
{
  Arg *v;

  if (get_arg_var(e, "set", &a, &v) == FAILURE)
    return FAILURE;

  if (a == NULL)
    {
      fprintf(stderr, "%s: too few arguments to set\n", pname);
      return FAILURE;
    }

  v->type = a->type;
  v->data = a->data;
  return v;
}

/*
 * setv returns a var, with type {int, string, undef}
 */
Arg *setv(e, a)
     XEvent *e;
     Arg *a;
{
  Arg *v, *w;

  if (get_arg_var(e, "set", &a, &v) == FAILURE)
    return FAILURE;

  if (get_arg_real(e, "set", &a, &w) == FAILURE)
    return FAILURE;

  v->type = w->type;
  v->data = w->data;
  return v;
}

Arg *led(e, a)
     XEvent *e;
     Arg *a;
{
  XKeyboardControl k;

  if (get_arg_int(e, "led", &a, &k.led) == FAILURE)
    return FAILURE;

  if (get_arg_int(e, "led", &a, &k.led_mode) == FAILURE)
    return FAILURE;

  k.led_mode = k.led_mode ? LedModeOn : LedModeOff;

  XChangeKeyboardControl(e->xkey.display,
			 KBLed | KBLedMode,
			 &k);

  return SUCCESS;
}

/*
 * minus returns an int arg
 */
Arg *minus(e, a)
     XEvent *e;
     Arg *a;
{
  static Arg ret;
  int x, y;

  if (get_arg_int(e, "minus", &a, &x) == FAILURE)
    return FAILURE;

  if (get_arg_int(e, "minus", &a, &y) == FAILURE)
    return FAILURE;

  ret.type = ARG_INT;
  ret.data.i = x - y;
  return &ret;
}

char *parseNum(s, a, b)
     char *s;
     int *a, b;
{
  int sign;

  sign = (*s == '+') ? 1 : -1;
  s++;
  if (*s == 'c')
    {
      *a = b / 2;
      return s+1;
    }

  if (sign == 1)
    *a = atoi(s);
  else
    *a = b - atoi(s);

  while (isdigit(*s))
    s++;

  return s;
}

void parseGeom(s, x, y, width, height)
     char *s;
     int *x, *y, width, height;
{
  s = parseNum(s, x, width);
  s = parseNum(s, y, height);
}

void printArgs(args)
     Arg *args;
{
  while (args)
    {
      switch(args->type)
	{
	case ARG_INT:
	  fprintf(stdout, "int");
	  break;
	case ARG_STRING:
	  fprintf(stdout, "string");
	  break;
	case ARG_VAR:
	case ARG_UNDEF:
	  fprintf(stdout, "var");
	  break;
	case ARG_FUNC:
	  fprintf(stdout, "func(");
	  printArgs(args->data.ProcCall.args);
	  fprintf(stdout, ")");
	  break;
	default:
	  break;
	}
      args = args->next;
      if (args)
	fprintf(stdout, ",");
    }
}

Arg *swv(e, a)
     XEvent *e;
     Arg *a;
{
  int s, i;
  Arg *ret;

  if (get_arg_int(e, "switch", &a, &s) == FAILURE)
    return FAILURE;

  i = s;

  while(i && a && a->next)
    {
      i--;
      a = a->next;
    }

  if (i != 0)
    {
      fprintf(stderr, "%s: switchv(%d,,,...) has too few arguments\n",
	      pname, s);
      return(FAILURE);
    }

  if (get_arg_real(e, "switchv", &a, &ret) == FAILURE)
    return FAILURE;

  return ret;
}

click(event, button, x, y)
     XEvent *event;
     int button, x, y;
{
  Display *dpy;
  XWindowAttributes attr;
  XEvent send;
  Window junk, *list, cwin;
  int i, screen;
  unsigned int length;

  dpy = event->xkey.display;
  screen = DefaultScreen(dpy),

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

  for (i = length-1; i >= 0; i--)
    {
      XGetWindowAttributes(dpy, list[i], &attr);
      if (x > attr.x && x < (attr.x + attr.width) &&
	  y > attr.y && y < (attr.y + attr.height))
	break;
    }

  if (i != -1)
    {
      cwin = XmuClientWindow(dpy, list[i]);

      bcopy(event, &send, sizeof(XEvent));
      send.type = ButtonPress;
      send.xbutton.window = cwin;
      send.xbutton.subwindow = None;
      send.xbutton.x = x - attr.x;
      send.xbutton.y = y - attr.y;
      send.xbutton.x_root = x;
      send.xbutton.y_root = y;
      switch(button)
	{
	case 1:
	  send.xbutton.button = Button1;
	  break;
	case 2:
	  send.xbutton.button = Button2;
	  break;
	case 3:
	  send.xbutton.button = Button3;
	  break;
	default:
	  send.xbutton.button = Button1;
	  break;
	}

      XSendEvent(dpy, cwin, True, ButtonPressMask, &send);
      send.type = ButtonRelease;
      send.xbutton.time += 10;
      XSendEvent(dpy, cwin, True, ButtonReleaseMask, &send);
    }

  XFree((char *)list);
}

Arg *sendclick(event, args)
     XEvent *event;
     Arg *args;
{
  Display *dpy;
  int width, height;
  int screen, button, x, y;

  dpy = event->xkey.display;
  screen = DefaultScreen(dpy),

  width = DisplayWidth(dpy, screen);
  height = DisplayHeight(dpy, screen);

  if (!args)
    {
      fprintf(stderr, "%s: sendclick passed no args\n", pname);
      return FAILURE;
    }

  if (args->type != ARG_INT)
    {
      fprintf(stderr, "%s: sendclick takes int as first arg\n", pname);
      return FAILURE;
    }

  button = args->data.i;
  args = args->next;

  if (args->type != ARG_STRING)
    {
      fprintf(stderr, "%s: sendclick takes string as second arg\n", pname);
      return FAILURE;
    }

  parseGeom(args->data.s, &x, &y, width, height);

  click(event, button, x, y);

  return SUCCESS;
}

Arg *sendclickmasked(event, args)
     XEvent *event;
     Arg *args;
{
  event->xkey.state = 0;
  return sendclick(event, args);
}

Arg *mouseclick(e, a)
     XEvent *e;
     Arg *a;
{
  int button;

  if (get_arg_int(e, "mouseclick", &a, &button) == FAILURE)
    return FAILURE;

  click(e, button, e->xkey.x_root, e->xkey.y_root);

  return SUCCESS;
}

Arg *warpdelta(e, a)
     XEvent *e;
     Arg *a;
{
  int delta, dx, dy;

  if (get_arg_int(e, "warpdelta", &a, &delta) == FAILURE)
    return FAILURE;

  if (get_arg_int(e, "warpdelta", &a, &dx) == FAILURE)
    return FAILURE;

  if (get_arg_int(e, "warpdelta", &a, &dy) == FAILURE)
    return FAILURE;

  XWarpPointer(e->xkey.display, None, None, 0, 0, 0, 0,
	       delta * dx, delta * dy);

  return SUCCESS;
}

Arg *findmouse(e, a)
     XEvent *e;
     Arg *a;
{
  int min, max, delta, delay;
  static GC findgc;
  static int gcinit = 0;
  Display *dpy;
  Window root;
  int dummyint, x, y, i;
  Window dummywin;

  dpy = e->xkey.display;
  root = DefaultRootWindow(dpy);

  if (get_arg_int(e, "warpdelta", &a, &min) == FAILURE)
    return FAILURE;

  if (get_arg_int(e, "warpdelta", &a, &max) == FAILURE)
    return FAILURE;

  if (get_arg_int(e, "warpdelta", &a, &delta) == FAILURE)
    return FAILURE;

  if (!gcinit)
    {
      XGCValues values;
      XColor x_color;

      if (a->type != ARG_STRING)
	{
	  fprintf(stderr, "%s: findmouse takes string as fourth arg\n", pname);
	  return FAILURE;
	}

      if (!XParseColor(dpy, DefaultColormap(dpy, DefaultScreen(dpy)),
		       a->data.s, &x_color))
	{
	  fprintf(stderr, "%s: findmouse couldn't figure out color %s\n",
		  pname, a->data.s);
	  return FAILURE;
	}

      if (!XAllocColor(dpy, DefaultColormap(dpy, DefaultScreen(dpy)),
		       &x_color))
	{
	  fprintf(stderr, "%s: findmouse couldn't allocate color %s\n",
		  pname, a->data.s);
	  return FAILURE;
	}

      values.foreground = x_color.pixel;

      a = a->next;

      if (a->type != ARG_STRING)
	{
	  fprintf(stderr, "%s: findmouse takes string as fifth arg\n", pname);
	  return FAILURE;
	}

      if (!XParseColor(dpy, DefaultColormap(dpy, DefaultScreen(dpy)),
		       a->data.s, &x_color))
	{
	  fprintf(stderr, "%s: findmouse couldn't figure out color %s\n",
		  pname, a->data.s);
	  return FAILURE;
	}

      if (!XAllocColor(dpy, DefaultColormap(dpy, DefaultScreen(dpy)),
		       &x_color))
	{
	  fprintf(stderr, "%s: findmouse couldn't allocate color %s\n",
		  pname, a->data.s);
	  return FAILURE;
	}

      values.foreground ^= x_color.pixel;

      gcinit = 1;
      values.function = GXxor;
      values.line_width = 0;
      values.subwindow_mode = IncludeInferiors;
      findgc = XCreateGC(dpy, root, GCFunction | GCForeground |
			 GCLineWidth | GCSubwindowMode, &values);
    }

  XQueryPointer(dpy, root, &dummywin, &dummywin, &x, &y,
		&dummyint, &dummyint, &dummyint);

  for (i = max; i > min; i -= delta)
    {
      XDrawArc(dpy, root, findgc, x - i/2, y - i/2, i, i, 0, 360*64);
      XSync(dpy, False);
      XDrawArc(dpy, root, findgc, x - i/2, y - i/2, i, i, 0, 360*64);
    }
}

#define MAXVARS 50

int numVars = 0;
VarMapping varMap[MAXVARS];

FuncMapping funcMap[] =
{
  { "led",			(Proc)led },
  { "minus",			(Proc)minus },
  { "mouseclick",		(Proc)mouseclick },
  { "sendclick",		(Proc)sendclick },
  { "sendclickmasked",		(Proc)sendclickmasked },
  { "set",			(Proc)set },
  { "setv",			(Proc)setv },
  { "switchv",			(Proc)swv },
  { "warpdelta",		(Proc)warpdelta },
  { "findmouse",		(Proc)findmouse },
  { NULL,			(Proc)NULL }
};

#define skipwhite(ptr) while (*ptr == '\n' || *ptr == ' ' ||	\
			      *ptr == '\t') ptr++

char *stringToArg();

char *parse_int(ptr, arg)
     char *ptr;
     Arg *arg;
{
  int i;
  char buffer[50];

  i = 0;
  if (*ptr == '-' || *ptr == '+')
    buffer[i++] = *ptr++;
  while (*ptr >= '0' && *ptr <= '9')
    buffer[i++] = *ptr++;
  buffer[i] = '\0';
  arg->data.i = atoi(buffer);
  return ptr;
}

char *parse_string(ptr, arg)
     char *ptr;
     Arg *arg;
{
  char *ptrstart, buffer[500];
  int i;

  ptrstart = ptr;

  i = 0;
  ptr++; /* skip quote */
  while (*ptr != '"' && *ptr != '\0')
    {
      buffer[i] = *ptr;
      if (*ptr == '\\')
	{
	  switch(*(ptr+1))
	    {
	    case 'n':
	      buffer[i] = '\n';
	      break;
	    case 't':
	      buffer[i] = '\t';
	      break;
	    default:
	      buffer[i] = *(ptr+1);
	      break;
	    }
	  ptr += 2;
	}
      else
	ptr++;
      i++;
    }
  if (*ptr == '\0')
    {
      fprintf(stderr, "%s: missing close \":\n%s",
	      pname, ptrstart);
      return NULL;
    }
  else /* *ptr == '"' */
    {
      ptr++;
      buffer[i] = '\0';
      arg->data.s = (char *)malloc(strlen(buffer)+1);
      strcpy(arg->data.s, buffer);
      return ptr;
    }
}

char *parse_var(ptr, arg)
     char *ptr;
     Arg *arg;
{
  char *ptrstart, buffer[50]; /* dump core */
  int i;

  ptrstart = ptr;
  i = 0;
  while (isgraph(*ptr) && *ptr != ',' && *ptr != ')')
    buffer[i++] = *ptr++;
  buffer[i] = 0;
  if (i == 0)
    {
      fprintf(stderr, "%s: line ends prematurely:\n%s",
	      pname, ptrstart);
      return NULL;
    }

  for (i = 0; i < numVars; i++)
    if (!strcmp(varMap[i].name, buffer))
      {
	arg->data.v = varMap[i].arg;
	return ptr;
      }

  numVars++;
  varMap[i].name = (char *)malloc(strlen(buffer)+1);
  strcpy(varMap[i].name, buffer);

  varMap[i].arg = (Arg *)malloc(sizeof(Arg));
  varMap[i].arg->type = ARG_UNDEF;
  arg->data.v = varMap[i].arg;

  return ptr;
}

char *parse_func(ptr, arg)
     char *ptr;
     Arg *arg;
{
  Arg *first, *last, **argptr;
  char *ptrstart, buffer[50];
  int i, parsing = 1;

  ptrstart = ptr;
  i = 0;
  while (*ptr != '(')
    buffer[i++] = *ptr++;
  buffer[i] = 0;
  if (i == 0)
    {
      fprintf(stderr, "%s: ( implies function - not named:\n%s",
	      pname, ptrstart);
      return NULL;
    }

  i = 0;
  while (funcMap[i].name != NULL)
    {
      if (!strcmp(buffer, funcMap[i].name))
	break;
      i++;
    }

  if (funcMap[i].name == NULL)
    {
      fprintf(stderr, "%s: function \"%s\" unknown\n",
	      pname, buffer);
      return NULL;
    }

  arg->data.ProcCall.p = funcMap[i].proc;
  argptr = &arg->data.ProcCall.args;
  ptr++;	/* skip "(" */

  while(parsing)
    {
      skipwhite(ptr);
      switch(*ptr)
	{
	case ')':
	  *argptr = NULL; /* no more args */
	  ptr++;
	  parsing = 0;
	  break;

	case ',': /* so, (,foo) is legit right now */
	  ptr++;
	  skipwhite(ptr);

	default: /* things not nec. comma separated */
	  ptr = stringToArg(ptr, argptr);
	  if (ptr != NULL)
	    argptr = &((*argptr)->next);
	  else
	    {
	      *argptr = NULL;
	      parsing = 0;
	    }
	  break;
	}
    }
  return ptr;
}

char *stringToArg(ptr, retarg)
     char *ptr;
     Arg **retarg;
{
  char *ptrend;
  Arg arg;

  skipwhite(ptr);

  /*
   * Figure out what we're parsing - int, string, var, or func
   */
  if (*ptr >= '0' && *ptr <= '9' || *ptr == '+' || *ptr == '-')
    arg.type = ARG_INT;
  else
    if (*ptr == '"')
      arg.type = ARG_STRING;
    else
      {
	ptrend = ptr;
	while (*ptrend != '\0' && *ptrend != '(' &&
	       *ptrend != ',' && *ptrend != ')')
	  ptrend++;
	if (*ptrend == '(')
	  arg.type = ARG_FUNC;
	else
	  arg.type = ARG_VAR;
      }

  /*
   * Parse it
   */
  switch(arg.type)
    {
    case ARG_INT:
      ptrend = parse_int(ptr, &arg);
      break;

    case ARG_STRING:
      ptrend = parse_string(ptr, &arg);
      break;

    case ARG_VAR:
      ptrend = parse_var(ptr, &arg);
      break;

    case ARG_FUNC:
      ptrend = parse_func(ptr, &arg);
      break;
    }

  if (ptrend)
    {
      arg.next = NULL;
      *retarg = (Arg *)malloc(sizeof(Arg));
      bcopy(&arg, *retarg, sizeof(Arg));
    }

  return ptrend;
}

KeyToArg *parseLine(e, line)
     XEvent *e;
     char *line;
{
  int i, exec = 0;
  char buffer[LINELEN], *ptrstart, *ptrend, *ptrmod = NULL;
  KeyToArg work, *ret;

  if (line == NULL)
    {
      fprintf(stderr, "%s: NULL string passed to parseLine\n", pname);
      return NULL;
    }

  if (line[0] == '#') /* comment line */
    return NULL;

  strncpy(buffer, line, LINELEN);
  buffer[LINELEN-1] = '\0';

  /*
   * Parse KeySym from line
   */
  ptrstart = buffer;
  skipwhite(ptrstart);
  if (ptrstart == '\0')
    return NULL;
  ptrend = ptrstart;
  while (*ptrend != '\0' && *ptrend != ':' && *ptrend!= '(')
    ptrend++;

  if (*ptrend == '(')
    ptrmod = ptrend + 1;

  if (*ptrend == ':' || *ptrend == '(')
    *ptrend = '\0';
  else
    {
      fprintf(stderr, "%s: line has no \":\" :\n%s",
	      pname, line);
      return NULL;
    }
  
  if (*ptrstart != '\0')
    {
      work.keysym = XStringToKeysym(ptrstart);
      if (work.keysym == NoSymbol)
	{
	  fprintf(stderr, "%s: keysym \"%s\" unknown\n",
		  pname, ptrstart);
	  return NULL;
	}

      work.modifiers = 0;
      if (ptrmod)
	{
	  while (*ptrmod != ')' && *ptrmod != '\0')
	    {
	      switch(*ptrmod)
		{
		case 'a':
		  work.modifiers |= AnyModifier;
		  break;
		case 's':
		  work.modifiers |= ShiftMask;
		  break;
		case 'l':
		  work.modifiers |= LockMask;
		  break;
		case 'c':
		  work.modifiers |= ControlMask;
		  break;
		case 'm':
		  work.modifiers |= Mod1Mask;
		  break;
		default:
		  fprintf(stderr, "%s: unknown modifier: '%c'\n",
			  pname, *ptrmod);
		}
	      ptrmod++;
	    }

	  if (*ptrmod == '\0')
	    {
	      fprintf(stderr, "%s: line needs \")\" :\n%s",
		      pname, line);
	      return NULL;
	    }
	  else
	    {
	      ptrend = ptrmod + 1;
	      skipwhite(ptrend);
	      if (*ptrend != ':')
		{
		  fprintf(stderr, "%s: \":\" should follow \")\" :\n%s",
			  pname, line);
		  return NULL;
		}
	    }
	}
    }
  else
    exec = 1;

  /*
   * Parse the rest
   */
  ptrstart = ptrend + 1;

  (void)stringToArg(ptrstart, &work.arglist);
  if (work.arglist == NULL)
    return NULL;

  if (exec)
    {
      Arg *dummy;
      get_arg_real(e, "init", &work.arglist, &dummy);

      /* write a free routine and use it here XXX */
      return NULL;
    }
  else
    {
      ret = (KeyToArg *)malloc(sizeof(KeyToArg));
      bcopy(&work, ret, sizeof(KeyToArg));
      return ret;
    }
}

KeyToArg *parse(e, file)
     XEvent *e;
     char *file;
{
  FILE *f;
  char line[LINELEN];
  KeyToArg *begin = NULL, *end = NULL, *new;
  
  f = fopen(file, "r");
  if (f == NULL)
    {
      perror(pname);
      return NULL;
    }

  while (1)
    {
      if (NULL == fgets(line, sizeof(line), f))
	{
	  if (feof(f))
	    {
	      fclose(f);
	      return begin;
	    }

	  fprintf(stderr, "%s: fgets returns %d in parse\n",
		  pname, ferror(f));
	  fclose(f);
	  return NULL;
	}

      new = parseLine(e, line);
      if (new != NULL)
	{
	  if (begin == NULL)
	    {
	      begin = new;
	      end = new;
	      new->next = NULL;
	    }
	  else
	    {
	      end->next = new;
	      end = new;
	      new->next = NULL;
	    }
	}
    }
}

int (*defaultHandler)();

myHandler(dpy, event)
     Display *dpy;
     XErrorEvent *event;
{
  if (event->error_code == BadWindow)
    return 0;
  else
    return(defaultHandler(dpy, event));
}
  
main(argc, argv)
     int argc;
     char **argv;
{
  Display *dpy;
  XEvent event;
  KeySym keysym;
  int i, dx, dy;
  static int oldcode = 0, speed = 25;
  char config[MAXPATHLEN], *home;
  KeyToArg *list, *a;

  success.type = ARG_UNDEF;
  failure.type = ARG_UNDEF;

  pname = argv[0];

  if ((dpy = XOpenDisplay(NULL)) == NULL)
    {
      fprintf(stderr, "%s: couldn't open display\n", pname);
      exit(1);
    }

  defaultHandler = XSetErrorHandler(myHandler);

  home = (char *)getenv("HOME");
  if (home != NULL)
    strncpy(config, home, MAXPATHLEN);
  else
    config[0] = '\0';
  strncat(config, "/.macros", MAXPATHLEN - strlen(config) - 1);

  event.xkey.display = dpy;
  list = parse(&event, config);
  if (list == NULL)
    {
      fprintf(stderr, "%s: no functions defined, exiting\n", pname);
      exit(0);
    }

  for (a = list; a != NULL; a = a->next)
    XGrabKey(dpy,
	     XKeysymToKeycode(dpy, a->keysym),
	     a->modifiers,
	     DefaultRootWindow(dpy),
	     True,
	     GrabModeAsync,
	     GrabModeAsync);

  while (1)
    {
      XNextEvent(dpy, &event);

      if (event.type == KeyPress)
	{
	  keysym = XKeycodeToKeysym(dpy, event.xkey.keycode, 0);

	  for (a = list; a != NULL; a = a->next)
	    if (a->keysym == keysym &&
		(a->modifiers == AnyModifier ||
		 a->modifiers == event.xkey.state))
	      {
		if (a->arglist->type == ARG_FUNC)
		  a->arglist->data.ProcCall.p(&event, 
					      a->arglist->data.ProcCall.args);
	      }
	}
    }
}
