/****************************************************************************
 * This module is all new
 * by Rob Nation 
 * Copyright 1993 Robert Nation. No restrictions are placed on this code,
 * as long as the copyright notice is preserved
 ****************************************************************************/

#include <FVWMconfig.h>

#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>

#include "fvwm.h"
#include "menus.h"
#include "misc.h"
#include "parse.h"
#include "screen.h"
#include "module.h"

/*****************************************************************************
 *
 * Waits Scr.ClickTime, or until it is evident that the user is not
 * clicking, but is moving the cursor
 *
 ****************************************************************************/
Bool IsClick(int x, int y, unsigned EndMask, XEvent * d)
{
  int xcurrent, ycurrent, total = 0;
  Time t0;
  extern Time lastTimestamp;

  xcurrent = x;
  ycurrent = y;
  t0 = lastTimestamp;

  while ((total < Scr.ClickTime) &&
	 (x - xcurrent < 3) && (x - xcurrent > -3) &&
	 (y - ycurrent < 3) && (y - ycurrent > -3) &&
	 ((lastTimestamp - t0) < Scr.ClickTime)) {
    sleep_a_little(20000);
    total += 20;
    if (XCheckMaskEvent(dpy, EndMask, d)) {
      StashEventTime(d);
      return True;
    }
    if (XCheckMaskEvent(dpy, ButtonMotionMask | PointerMotionMask, d)) {
      xcurrent = d->xmotion.x_root;
      ycurrent = d->xmotion.y_root;
      StashEventTime(d);
    }
  }
  return False;
}

/*****************************************************************************
 *
 * Builtin which determines if the button press was a click or double click...
 *
 ****************************************************************************/
void ComplexFunction(XEvent * eventp, Window w, FvwmWindow * tmp_win,
		     unsigned long context, char *action, int *Module)
{
  char type = MOTION;
  char c;
  MenuItem *mi;
  Bool Persist = False;
  Bool HaveDoubleClick = False;
  Bool NeedsTarget = False;
  char *arguments[10], *junk, *taction;
  int x, y, i;
  XEvent d, *ev;
  MenuRoot *mr;
  extern Bool desperate;

  mr = FindPopup(action);
  if (mr == NULL) {
    if (!desperate)
      fvwm_msg(ERR, "ComplexFunction", "No such function %s", action);
    return;
  }
  desperate = 0;
  /* Get the argument list */
  /* First entry in action is the function-name, ignore it */
  action = GetNextToken(action, &junk);
  if (junk != NULL)
    free(junk);
  for (i = 0; i < 10; i++)
    action = GetNextToken(action, &arguments[i]);
  /* These built-ins require a selected window 
   * The function code is >= 100 and < 1000
   * F_RESIZE
   * F_MOVE
   * F_ICONIFY
   * F_RAISE
   * F_LOWER
   * F_DESTROY
   * F_DELETE
   * F_STICK
   * F_RAISELOWER
   * F_MAXIMIZE
   * F_FOCUS
   *
   * These do not:
   * The function code is < 100
   * F_NOP
   * F_TITLE
   * F_BEEP
   * F_SCROLL
   * F_MOVECURSOR
   * F_RESTART
   * F_EXEC
   * F_REFRESH
   * F_GOTO_PAGE
   * F_TOGGLE_PAGE
   * F_CIRCULATE_UP
   * F_CIRCULATE_DOWN
   * F_WARP
   * F_DESK
   * F_MODULE
   * F_POPUP
   * F_QUIT
   * F_WINDOWLIST
   * F_FUNCTION
   * F_SEND_WINDOW_LIST
   */
  ev = eventp;
  /* In case we want to perform an action on a button press, we
   * need to fool other routines */
  if (eventp->type == ButtonPress)
    eventp->type = ButtonRelease;
  mi = mr->first;
  while (mi != NULL) {
    /* make lower case */
    c = *(mi->item);
    if ((mi->func_type >= 100) && (mi->func_type < 1000))
      NeedsTarget = True;
    if (isupper(c))
      c = tolower(c);
    if (c == DOUBLE_CLICK) {
      HaveDoubleClick = True;
      Persist = True;
    } else if (c == IMMEDIATE) {
      if (tmp_win)
	w = tmp_win->frame;
      else
	w = None;
      taction = expand(mi->action, arguments, tmp_win);
      ExecuteFunction(taction, tmp_win, eventp, context, -2);
      free(taction);
    } else
      Persist = True;
    mi = mi->next;
  }

  if (!Persist) {
    for (i = 0; i < 10; i++)
      if (arguments[i] != NULL)
	free(arguments[i]);
    return;
  }

  /* Only defer execution if there is a possibility of needing
   * a window to operate on */
  if (NeedsTarget) {
    if (DeferExecution
	(eventp, &w, &tmp_win, &context, SELECT, ButtonPress)) {
      WaitForButtonsUp();
      for (i = 0; i < 10; i++)
	if (arguments[i] != NULL)
	  free(arguments[i]);
      return;
    }
  }

  if (!GrabEm(SELECT)) {
    XBell(dpy, Scr.screen);
    for (i = 0; i < 10; i++)
      if (arguments[i] != NULL)
	free(arguments[i]);
    return;
  }
  XQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild,
		&x, &y, &JunkX, &JunkY, &JunkMask);


  /* Wait and see if we have a click, or a move */
  /* wait 100 msec, see if the user releases the button */
  if (IsClick(x, y, ButtonReleaseMask, &d)) {
    ev = &d;
    type = CLICK;
  }

  /* If it was a click, wait to see if its a double click */
  if ((HaveDoubleClick) && (type == CLICK) &&
      (IsClick(x, y, ButtonPressMask, &d))) {
    type = ONE_AND_A_HALF_CLICKS;
    ev = &d;
  }
  if ((HaveDoubleClick) && (type == ONE_AND_A_HALF_CLICKS) &&
      (IsClick(x, y, ButtonReleaseMask, &d))) {
    type = DOUBLE_CLICK;
    ev = &d;
  }
  /* some functions operate on button release instead of 
   * presses. These gets really weird for complex functions ... */
  if (ev->type == ButtonPress)
    ev->type = ButtonRelease;

  mi = mr->first;
  while (mi != NULL) {
    /* make lower case */
    c = *(mi->item);
    if (isupper(c))
      c = tolower(c);
    if (c == type) {
      if (tmp_win)
	w = tmp_win->frame;
      else
	w = None;
      taction = expand(mi->action, arguments, tmp_win);
      ExecuteFunction(taction, tmp_win, ev, context, -2);
      free(taction);
    }
    mi = mi->next;
  }
  WaitForButtonsUp();
  UngrabEm();
  for (i = 0; i < 10; i++)
    if (arguments[i] != NULL)
      free(arguments[i]);
}


char *expand(char *input, char *arguments[], FvwmWindow * tmp_win)
{
  int l, i, l2, n, k, j;
  char *out;

  l = strlen(input);
  l2 = strlen(input);

  i = 0;
  while (i < l) {
    if (input[i] == '$') {
      n = input[i + 1] - '0';
      if ((n >= 0) && (n <= 9) && (arguments[n] != NULL)) {
	l2 += strlen(arguments[n]) - 2;
	i++;
      } else if (input[i + 1] == 'w') {
	l2 += 16;
	i++;
      }

    }
    i++;
  }

  out = safemalloc(l2 + 1);
  i = 0;
  j = 0;
  while (i < l) {
    if (input[i] == '$') {
      n = input[i + 1] - '0';
      if ((n >= 0) && (n <= 9) && (arguments[n] != NULL)) {
	for (k = 0; k < strlen(arguments[n]); k++)
	  out[j++] = arguments[n][k];
	i++;
      } else if (input[i + 1] == 'w') {
	if (tmp_win)
	  sprintf(&out[j], "0x%x", tmp_win->w);
	else
	  sprintf(&out[j], "$w");
	j = strlen(out);
	i++;
      } else if (input[i + 1] == '$') {
	out[j++] = '$';
	i++;
      } else
	out[j++] = input[i];
    } else
      out[j++] = input[i];
    i++;
  }
  out[j] = 0;
  return out;
}
