#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include "global.h"
#include "constants.h"
#include "command.h"
#include "binding.h"
#include "window.h"
#include "command.h"
#include "inp.h"
#include "ff.h"

extern server_ptr gsrv;

/* Non meta keys are in META[MAX_META][...] */
bindings *META = NULL;

extern unsigned char (*input_routine)();
static void bind_execute_line(), bind_nothing(), bind_quote_char();

typedef struct all_bind_struct *all_binds;
struct all_bind_struct {
  char *name;
  bindings this_bind;
  all_binds next;
};
static all_binds master_bind_list = NULL;
/* ------------------------------------------------------------------------- */
void list_bindings(void)
{
  char *str = NULL;
  all_binds tmp = master_bind_list;
  str = m_strcpy("*** Available bindings: ");
  while (tmp) { str = m_strcat(str, tmp->name, TRUE, 1); tmp = tmp->next; }
  say(str);
  myfree(str);
}
/* ------------------------------------------------------------------------- */
int set_binding(char *name)
{
  all_binds walk = master_bind_list;
  while (walk && (strcasecmp(walk->name, name)!=0)) walk = walk->next;
  if (!walk) {
    yell("*** Could not find binding named %s", name);
    list_bindings();
    return 0;
  }
  META = &(walk->this_bind);
  return 1;
}
/* ------------------------------------------------------------------------- */
static void _init_binds(bindings this);
void create_binding(char *name)
{
  all_binds tmp;
  tmp = (all_binds)mymalloc(sizeof(struct all_bind_struct));
  if (!tmp) return;
  memset(tmp, 0, sizeof(struct all_bind_struct));
  tmp->name = m_strcpy(name);
  if (!tmp) { myfree(tmp); return; }
  tmp->next = master_bind_list;
  master_bind_list = tmp;
  _init_binds(tmp->this_bind);
}

/* ------------------------------------------------------------------------- */
static struct command_node bindlist[] =
{
  {{"META1"},              (char *)(-9)},
  {{"META2"},              (char *)(-8)},
  {{"META3"},              (char *)(-7)},
  {{"META4"},              (char *)(-6)},
  {{"META5"},              (char *)(-5)},
  {{"META6"},              (char *)(-4)},
  {{"META7"},              (char *)(-3)},
  {{"META8"},              (char *)(-2)},
  {{"META9"},              (char *)(-1)},
  {{"BACKSPACE"},          (char *)inp_backspace},
  {{"BACKWARD_CHARACTER"}, (char *)inp_backward_character},
  {{"BACKWARD_HISTORY"},   (char *)inp_backward_history},
  {{"BEGINNING_OF_LINE"},  (char *)inp_beginning_of_line},
  {{"DELETE_CHARACTER"},   (char *)inp_delete_current_char},
  {{"END_OF_LINE"},        (char *)inp_eol},
  {{"ERASE_TO_BEGINNING"}, (char *)inp_delete_to_bol},
  {{"FORWARD_CHARACTER"},  (char *)inp_forward_character},
  {{"FORWARD_HISTORY"},    (char *)inp_forward_history},
  {{"NOTHING"},            (char *)bind_nothing},
  {{"PARSE_COMMAND"},      (char *)bind_execute_line},
  {{"QUOTE_CHARACTER"},    (char *)bind_quote_char},
  {{"REFRESH_SCREEN"},     (char *)redraw},
  {{"SELF_INSERT"},        (char *)(-10)},
  {{"SEND_LINE"},          (char *)inp_send_line},
#ifdef ENABLE_SPAWN
  {{"STOP_IRC"},           (char *)user_spawn},
#endif
  {{"TOGGLE_INSERT_MODE"}, (char *)inp_toggle_insert_mode},
  {{"TYPE_TEXT"},          (char *)bind_typetext},
  {{NULL},                 NULL}
};

/* ------------------------------------------------------------------------ */
void set_bindlist(bindings new_meta)
{
  META = (bindings *)&new_meta;
}

/* ------------------------------------------------------------------------ */
static void bind_quote_char(char *str)
{ 
  char key;
  key = (char)input_routine();
  inp_insert_char(key);
}
/* ------------------------------------------------------------------------ */
static void bind_nothing(char *str)
 { }
/* ------------------------------------------------------------------------ */
static void bind_execute_line(char *line)
{
  parse_line(line, line, FALSE, FALSE);
}

/* ------------------------------------------------------------------------ */
static unsigned char (*old)();

static int bind_curr;
static char *bind_line;
static unsigned char bind_keyget(void)
{
  int key;
  if (bind_line[bind_curr] == '^')
    key = bind_line[++bind_curr] - 64;
  else
    key = bind_line[bind_curr];
  if (bind_line[bind_curr]) bind_curr++;
  if (!bind_line[bind_curr]) {
    input_routine = old;
    myfree(bind_line);
  }
  return key;
}

/* ------------------------------------------------------------------------ */
void bind_typetext(char *line, int varpar, char *subparams)
{
  if (input_routine==bind_keyget) {
    yell("*** Recursive TYPE. Eeeek!");
    return;
  }
  bind_curr = 0;
  bind_line = m_strcpy(line);
  old = input_routine;
  input_routine = bind_keyget;
}

/* ------------------------------------------------------------------------ */
static unsigned char rtrans_key(int key, int *ctrl)
{
  if (key <= 32) {
    *ctrl = TRUE;
    return key+64;
  } else if (key==127) {
    *ctrl = TRUE;
    return '?';
  } else {
    *ctrl = FALSE;
  }
  return key;
}      

/* ------------------------------------------------------------------------ */
static void show_bind(int meta, int key)
{
  int loop, good = -1, newkey, isctrl;
  char comm[MAXLEN], string[MAXLEN];
  if ((*META)[meta][key].func_bind == 0) return;
  *comm = 0;
  if ((int)(*META)[meta][key].func_bind < (int)0) {
    (void)strcpy(comm, bindlist[(int)(*META)[meta][key].func_bind + MAX_META].command);
  } else {
    for (loop=0; bindlist[loop].command; loop++)
       if ((int)(*META)[meta][key].func_bind == (int)bindlist[loop].constant)
         good = loop;
    if (good != -1)
      (void)strcpy(comm, (char *)bindlist[good].command);
  }
  if (meta<MAX_META) sprintf(string, "META%d-", meta+1);
  else string[0] = 0;
  newkey = rtrans_key(key, &isctrl);
  say("*** %s%s%c is bound to %s %s", string, isctrl?"^":"", newkey, comm,
      (*META)[meta][key].param?(*META)[meta][key].param:"");
}

/* ------------------------------------------------------------------------ */
/*
 *   -(MAX_META+1) implies no character
 */

static int trans_key(key)
  char **key;
{
  char *duh = *key;
  int ret = 0;

  if (*duh == 0)
    ret = -(MAX_META+1);
  else if (*duh=='^') {
    duh++;
    if ((*duh >= 65) && (*duh <= 96)) ret = *duh-64;
    else if ((*duh >= 97) && (*duh <= 126)) ret = *duh-96;
    else if (*duh == '?') ret = 127;
    else if (*duh!=0) ret = 0;
    if (*duh) duh++;
  } else {
    ret = *duh;
    if (strncasecmp(duh, "meta", 4) == 0) {
      duh += 4;
      ret = *duh - '1' - MAX_META;
      duh += 2;
    } else duh++;
  }
  *key = duh;
  return ret;
}

void dobind(char *str, int varpar, char *subparams)
{
  int num, meta, mlame = -1, loop, loop2, i, indx;
  char first[MAXLEN], *ff;

  (void)grab_word(&str, ' ', first);
  if (!*first) {
    for (loop=0; loop<=MAX_META; loop++)
      for (loop2=0;loop2<128;loop2++)
        show_bind(loop, loop2);
    return;
  }

  ff = first;
  meta = trans_key(&ff);
  num = trans_key(&ff);
  if (num==-(MAX_META+1)) {  /* Only a signle key */
    num = meta;
    meta = 0;
  }

/* Was there something left in the input string */
  if (*ff) {
    say("*** Key sequences may not contain more than two keys");
    return;
  }

/* Get the right META key 0 -> MAX_META-1 */
  if (!meta) mlame = MAX_META;
  else if (meta<0)
    mlame = meta + MAX_META;
  else if ((int)(*META)[MAX_META][meta].func_bind < (int)0)
    mlame = (int)(*META)[MAX_META][meta].func_bind + MAX_META;
  if (mlame == -1) {
    say("*** Illegal key sequence: %c is not a meta-key", meta);
    return;
  }

  (void)grab_word(&str, ' ', first);
  loop = lookup_command(bindlist, first, &indx);
  if (loop<0)
    say("*** Unknown or ambiguous function: %s", first);
  else {
    (*META)[mlame][num].param = m_strcpy(str);
    if ((int)indx==(-MAX_META-1))
      (*META)[mlame][num].func_bind = 0;
    else
      (void)memcpy(&(*META)[mlame][num].func_bind, &indx, sizeof(indx));
    show_bind(mlame, num);
  }
}

static void _init_binds(bindings this)
{
  int loop,loop2;
  if (!META) return;
  for (loop=0; loop<=MAX_META; loop++)
    for (loop2=0; loop2<128; loop2++) {
      this[loop][loop2].func_bind = NULL;
      this[loop][loop2].param = NULL;
    }
  this[MAX_META][1].func_bind = inp_toggle_insert_mode;
  this[MAX_META][4].func_bind = inp_delete_current_char;
  this[MAX_META][5].func_bind = inp_eol;
  this[MAX_META][12].vfunc_bind = redraw;
  this[MAX_META][13].func_bind = inp_send_line;
  this[MAX_META][21].func_bind = inp_delete_to_bol;
  this[MAX_META][127].func_bind = inp_backspace;
}

void init_binds(void)
{
  create_binding("normal");
  set_binding("normal");
  _init_binds(*META);
}
