#include "FvwmIconMan.h"
#include "readconfig.h"
#include "xmanager.h"

static Button *get_select_button (void);

static struct {
  Button *current_button;
  Function *fp;
} function_context;

static void init_function_context (Function *func)
{
  function_context.current_button = get_select_button();
  function_context.fp = func;
}

void run_function_list (Function *func)
{
  init_function_context (func);

  while (function_context.fp) {
    function_context.fp->func (function_context.fp->numargs, 
			       function_context.fp->args);
    if (function_context.fp)
      function_context.fp = function_context.fp->next;
  }
}

static Button *get_select_button (void)
{
  if (globals.select_win)
    return globals.select_win->button;
  return NULL;
}

static Button *get_focus_button (void)
{
  if (globals.focus_win)
    return globals.focus_win->button;
  return NULL;
}

static WinManager *get_current_man (void)
{
  Button *b = function_context.current_button;

  if (globals.num_managers==1)
    return globals.managers;
  if (b && b->drawn_state.win)
    return b->drawn_state.win->manager;
  return NULL;
}

static WinData *get_current_win (void)
{
  Button *b = function_context.current_button;

  if (b && b->drawn_state.win)
    return b->drawn_state.win;
  return NULL;
}

static Button *get_current_button (void)
{
  return function_context.current_button;
}
  
static Button *button_move (ButtonValue *bv)
{
  Button *b = NULL, *cur;
  WinManager *man;
  int i;

  cur = get_current_button();

  switch (bv->base) {
  case NoButton:
    ConsoleMessage ("gotobutton: need a button to change to\n");
    return cur;

  case SelectButton:
    b = get_select_button();
    break;

  case FocusButton:
    b = get_focus_button();
    break;

  case AbsoluteButton:
    man = get_current_man();
    if (man && man->buttons.num_windows > 0) {
      i = bv->offset % man->buttons.num_windows;
      if (i < 0)
	i += man->buttons.num_windows;
      b = man->buttons.buttons[i];
    }
    break;
   
  default:
    man = get_current_man();
    if (!cur) {
      ConsoleDebug (FUNCTIONS, "\tno current button, skipping\n");
      return NULL;
    }

    switch (bv->base) {
    case UpButton:
      b = button_above (man, cur);
      break;

    case DownButton:
      b = button_below (man, cur);
      break;

    case LeftButton:
      b = button_left (man, cur);
      break;

    case RightButton:
      b = button_right (man, cur);
      break;
      
    case NextButton:
      b = button_next (man, cur);
      break;

    case PrevButton:
      b = button_prev (man, cur);
      break;

    default:
      ConsoleMessage ("Internal error in gotobutton\n");
      break;
    }
  }

  return b;
}

int builtin_gotobutton (int numargs, BuiltinArg *args)
{
  Button *b;
  ButtonValue *bv;

  ConsoleDebug (FUNCTIONS, "gotobutton: ");
  print_args (numargs, args);
  
  bv = &args[0].value.button_value;

  b = button_move (bv);

  if (b)
    function_context.current_button = b;

  return 0;
}
    
int builtin_gotomanager (int numargs, BuiltinArg *args)
{
  ButtonValue *bv;
  WinManager *man, *new;
  int i;

  ConsoleDebug (FUNCTIONS, "gotomanager: ");
  print_args (numargs, args);

  bv = &args[0].value.button_value;

  new = man = get_current_man();

  switch (bv->base) {
  case NoButton:
    ConsoleMessage ("gotomanager: need a manager argument\n");
    return 1;

  case SelectButton:
  case FocusButton:
    ConsoleMessage ("gotomanger: \"select\" or \"focus\" does not specify"
		    " a manager\n");
    break;

  case AbsoluteButton:
  {
    /* Now we find the manager modulo the VISIBLE managers */
    static WinManager **wa = NULL;
    int i, num_mapped, n;
    
    n = globals.num_managers;
    if (n) {
      if (wa == NULL) {
	wa = (WinManager **)safemalloc (n * sizeof (WinManager *));
      }
      for (i = 0, num_mapped = 0; i < n; i++) {
	if (globals.managers[i].buttons.num_windows > 0 &&
	    globals.managers[i].window_mapped) {
	  wa[num_mapped++] = &globals.managers[i];
	}
      }
      if (num_mapped) {
	i = bv->offset % num_mapped;
	if (i < 0)
	  i += num_mapped;
	new = wa[i];
      }
      else {
	new = NULL;
      }
    }
  }
  break;
  
  case NextButton:
    if (man) {
      for (i = man->index + 1, new = man + 1; 
	   i < globals.num_managers && new->buttons.num_windows == 0; 
	   i++, new++)
	;
      if (i == globals.num_managers)
	new = man;
    }
    break;

  case PrevButton:
    if (man) {
      for (i = man->index - 1, new = man - 1;
	   i > -1 && new->buttons.num_windows == 0;
	   i--, new--)
	;
      if (i == -1)
	new = man;
    }
    break;

  default:
    ConsoleMessage ("gotomanager: bad argument\n");
    break;
  }

  if (new && new != man && new->buttons.num_windows > 0) {
    function_context.current_button = new->buttons.buttons[0];
  }

  return 0;
}


int builtin_select (int numargs, BuiltinArg *args)
{
  WinManager *man = get_current_man();
  if (man) {
    move_highlight (man, get_current_button());
    if (get_current_button())
      run_binding (man, SELECT);
  }
  return 0;
}

int builtin_sendcommand (int numargs, BuiltinArg *args)
{
  WinData *win = get_current_win();

  if (!win) {
    return 0;
  }

  SendFvwmPipe (args[0].value.string_value, win->app_id);

  return 0;
}

int builtin_printdebug (int numargs, BuiltinArg *args)
{
  int i;

  for (i = 0; i < globals.num_managers; i++) {
    ConsoleDebug (FUNCTIONS, "Manager %d\n---------\n");
    ConsoleDebug (FUNCTIONS, "Keys:\n");
    print_bindings (globals.managers[i].bindings[KEYPRESS]);
    ConsoleDebug (FUNCTIONS, "Mice:\n");
    print_bindings (globals.managers[i].bindings[MOUSE]);
    ConsoleDebug (FUNCTIONS, "Select:\n");
    print_bindings (globals.managers[i].bindings[SELECT]);
    ConsoleDebug (FUNCTIONS, "\n");
  }

  return 0;
}

int builtin_quit (int numargs, BuiltinArg *args)
{
  ConsoleDebug (FUNCTIONS, "quit: ");
  print_args (numargs, args);
  ShutMeDown (0);
  return 0;
}

static void do_jmp (int off)
{
  int i;
  ConsoleDebug (FUNCTIONS, "jmp: %d\n", off);
  
  if (off < 0) {
    ConsoleMessage ("Can't have a negative relative jump offset\n");
    return;
  }
  for (i = 0; i < off; i++) {
    if (function_context.fp)
      function_context.fp = function_context.fp->next;
  }
}

static int eval_if (ButtonValue *bv)
{
  Button *cur;
  WinManager *man;

  switch (bv->base) {
  case NoButton:
    ConsoleMessage ("Internal error in eval_if: 1\n");
    break;

  case SelectButton:
    if (get_select_button())
      return 1;
    break;

  case FocusButton:
    if (get_focus_button())
      return 1;
    break;

  case AbsoluteButton:
    if (bv->offset != 0)
      return 1;
    break;

  default:
    cur = get_current_button();
    man = get_current_man();
    if (!cur || !man) {
      return 0;
    }
    
    switch (bv->base) {
    case UpButton:
      return (button_above (man, cur) != cur);

    case DownButton:
      return (button_below (man, cur) != cur);
      
    case LeftButton:
      return (button_left (man, cur) != cur);
      
    case RightButton:
      return (button_right (man, cur) != cur);
      
    case NextButton:
      return (button_next (man, cur) != cur);

    case PrevButton:
      return (button_prev (man, cur) != cur);
      
    default:
      ConsoleMessage ("Internal error in eval_if: 2\n");
      break;
    }
  }
    
  return 0;
}

int builtin_bif (int numargs, BuiltinArg *args)
{
  int off = args[1].value.int_value;
  ConsoleDebug (FUNCTIONS, "bif: off = %d\n", off);

  if (eval_if (&args[0].value.button_value)) {
    do_jmp (off);
  }
  
  return 0;
}

int builtin_bifn (int numargs, BuiltinArg *args)
{
  int off = args[1].value.int_value;
  ConsoleDebug (FUNCTIONS, "bifn: off = %d\n", off);

  if (eval_if (&args[0].value.button_value) == 0) {
    do_jmp (off);
  }
  
  return 0;
}

int builtin_jmp (int numargs, BuiltinArg *args)
{
  int off = args[0].value.int_value;
  ConsoleDebug (FUNCTIONS, "jmp: off = %d\n", off);

  do_jmp (off);
  return 0;
}

int builtin_ret (int numargs, BuiltinArg *args)
{
  function_context.fp = NULL;
  return 0;
}

int builtin_print (int numargs, BuiltinArg *args)
{
  char *s, buf[256];
  
  ConsoleDebug (FUNCTIONS, "print: %s\n", args[0].value.string_value);
  
  s = args[0].value.string_value;
  if (strlen (s) > 250) {
    ConsoleMessage ("String too long\n");
  }
  else {
    sprintf (buf, "%s\n", s);
    ConsoleMessage (buf);
  }
  
  return 0;
}

int builtin_searchforward (int numargs, BuiltinArg *args)
{
  char *s;
  Button *b, *cur;
  WinManager *man;

  s = args[0].value.string_value;

  ConsoleDebug (FUNCTIONS, "searchforward: %s\n", s);
  
  cur = get_current_button();
  man = get_current_man();
  b = cur;
  if (cur) {
    while (1) {
      if (cur->drawn_state.display_string &&
	  matchWildcards (s, cur->drawn_state.display_string))
	break;
      b = button_next (man, cur);
      if (b == cur) {
	cur = NULL;
	break;
      }
      cur = b;
    }
  }
  if (cur)
    function_context.current_button = cur;
  
  return 0;
}

int builtin_searchback (int numargs, BuiltinArg *args)
{
  char *s;
  Button *b, *cur;
  WinManager *man;

  s = args[0].value.string_value;

  ConsoleDebug (FUNCTIONS, "searchback: %s\n", s);
  
  cur = get_current_button();
  man = get_current_man();
  b = cur;
  if (cur) {
    while (1) {
      if (cur->drawn_state.display_string &&
	  matchWildcards (s, cur->drawn_state.display_string))
	break;
      b = button_prev (man, cur);
      if (b == cur) {
	cur = NULL;
	break;
      }
      cur = b;
    }
  }
  if (cur)
    function_context.current_button = cur;
  
  return 0;
}

int builtin_warp (int numargs, BuiltinArg *args)
{
  Button *cur;
  WinManager *man;
  int x, y;

  ConsoleDebug (FUNCTIONS, "warp\n");

  cur = get_current_button();
  if (cur) {
    man = get_current_man();
    x = cur->x + cur->w / 2;
    y = cur->y + cur->h / 2;
    XWarpPointer (theDisplay, None, man->theWindow, 0, 0, 0, 0, x, y);
  }
  
  return 0;
}
