#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "global.h"
#include "types.h"
#include "constants.h"
#include "chan.h"
#include "window.h"
#include "alias.h"
#include "set.h"
#include "ff.h"
#include "command.h"
#include "inp.h"
#include "server.h"

extern char *g_sent, *g_from, *g_body;
extern char *g_public, *g_join, *g_invite;
extern char curr_time[30];
extern server_ptr gsrv;
extern win_ptr gwin;

/* ------------------------------------------------------------------- */
struct alias_struct {
  char *key, *action;
  alias_ptr next, sub;
};
/*static alias_ptr new_alias_list = NULL, new_assign_list = NULL; */
static alias_header new_alias_list = { NULL };
static alias_header new_assign_list = { NULL };
/* ------------------------------------------------------------------- */

struct builtin_struct {
  char ack, pad1[3];
  void (*routine)();
};

static void curr_nick(char *str)
  { char *tmp = get_server_nick(gsrv);
    if (tmp) strcpy(str, tmp);
    else strcpy(str, "none");
  }

static void curr_query(char *str)
  { char *tmp;
    if ((tmp=getquery())) strcpy(str, tmp);
    else *str = 0;
  }
static void curr_chan_query(char *str)
  { char *tmp;
    if ((tmp = getquery())) strcpy(str, tmp);
    else (void)get_channel(str);
  }
static void curr_commandchar(char *str)
  { (void)strcpy(str, "/"); }
static void curr_get_channel(char *str)
  { (void)get_channel(str); }
static void curr_server(char *str)
{
  char *ret;
  ret = get_server_name(gsrv);
  if (ret) strcpy(str, ret);
  else str[0] = 0;
}
static void curr_body(char *str)
  { if (g_body) strcpy(str, g_body); else *str = 0; }
static void curr_sent(char *str)
  { if (g_sent) strcpy(str, g_sent); else *str = 0;}
static void curr_from(char *str)
  { if (g_from) strcpy(str, g_from); else *str = 0; }
static void curr_join(char *str)
  { if (g_join) strcpy(str, g_join); else *str = 0; }
static void curr_public(char *str)
  { if (g_public) strcpy(str, g_public); else *str = 0;}
static void curr_ztime(char *str)
  { (void) strcpy(str, curr_time); }
static void curr_version(char *str)
  { (void) strcpy(str, VERSION_DATE); }
static void curr_last_invite(char *str)
  { if (g_invite) strcpy(str, g_invite); else *str = 0; }
static void curr_chanop(char *str)
  { char *tmp = get_server_nick(gsrv);
    char *chan = get_channel(NULL);
    if (is_chanop(get_server_chanlist(gsrv), chan, tmp)) strcpy(str, "@");
    else strcpy(str, "");
  }

static struct builtin_struct builtins[] = {
  {',', {""}, curr_from         },
  {'.', {""}, curr_sent         },
  {':', {""}, curr_join         },
  {';', {""}, curr_public       },
  {'B', {""}, curr_body         },
  {'C', {""}, curr_get_channel  },
  {'I', {""}, curr_last_invite  },
  {'K', {""}, curr_commandchar  },
  {'N', {""}, curr_nick         },
  {'P', {""}, curr_chanop       },
  {'Q', {""}, curr_query        },
  {'S', {""}, curr_server       },
  {'T', {""}, curr_chan_query   },
  {'V', {""}, curr_version      },
  {'Z', {""}, curr_ztime        },
  { 0,  {""}, NULL              }
};

int check_builtins(char ch, char *str)
{
  int loop = 0;
  while (builtins[loop].ack) {
    if (builtins[loop].ack == ch) {
      (void) builtins[loop].routine(str);
      return TRUE;
    }
    loop++;
  }
  return FALSE;
}

/* ------------------------------------------------------------------- */
static void save_alias_list(FILE *fp, alias_ptr top, int alias, char *save_pref)
{
  int oldlen;
  oldlen = strlen(save_pref);
  while (top)
  {
    if (top->action)
      if (alias)
        fprintf(fp, "ALIAS %s%s {%s}\n", save_pref, top->key, top->action);
      else
        fprintf(fp, "ASSIGN %s%s %s\n", save_pref, top->key, top->action);
    if (top->sub) {
      sprintf(save_pref + oldlen, "%s.", top->key);
      save_alias_list(fp, top->sub, alias, save_pref);
      save_pref[oldlen] = 0;
    }
    top = top->next;
  }
}

void save_assigns(FILE *fp)
{
  char save_pref[255] = "";
  save_alias_list(fp, new_assign_list.list, FALSE, save_pref);
}

void save_aliases(FILE *fp)
{
  char save_pref[255] = "";
  save_alias_list(fp, new_alias_list.list, TRUE, save_pref);
}

/* ------------------------------------------------------------------- */
static alias_ptr alias_match(char *key, alias_ptr top, int *exact)
{
  char *ind;
  int n = -1, klen;
  alias_ptr f;
  *exact = 0;
  ind = strstr(key, ".");
  if (ind) *ind = 0;
  for (f = top; f &&(n = strcasecmp(f->key, key)); f = f->next)
    if (n>=0) break;
  if (ind) *ind = '.';
  if (!f) return NULL;
  if (n==0) {
    if (ind) return alias_match(++ind, f->sub, exact);
    else { *exact = 1; return f; }
  }
  if (ind) return NULL;  /* didn't match a mid portion */
  klen = strlen(key);
  if (klen>=strlen(f->key)) return NULL; /* key is too long to match */
/* First part of ptr's key does not match our key */
  if (strncasecmp(key, f->key, klen) != 0) return NULL;
/* Partially matches more than 1 element */
  if (f->next && (strncasecmp(key, f->next->key, klen) == 0)) return NULL;
  return f;
}
/* ------------------------------------------------------------------- */
static char *get_alias_text_internal(alias_ptr alist, char *oldname,
                                     int must_exact)
{
  alias_ptr tmp;
  int exact;
  tmp = alias_match(oldname, alist, &exact);
  if (!tmp || !tmp->action || (must_exact && !exact)) return NULL;
  return tmp->action;
}
/* ------------------------------------------------------------------- */
char *get_alias_text(char *oldname, int must_exact)
{
  char *ret = NULL;

  ret = get_alias_text_internal(
           mode_set?(mode_set->mode_data.alias_list.list):new_alias_list.list,
           oldname, (!mode_set) || must_exact);
  return ret;
}

/* ------------------------------------------------------------------- */
static int alias_depth = 0;

int exec_alias(char *word, char *string, char *params, int must_exact)
{
  char *act;
  int ret = 1, exact;
  if (alias_depth++>getivar(VAR_MAX_RECURSIONS)) {
    alias_depth--;
    say("*** Recursion too deep %d!", alias_depth);
    return FALSE;
  }
  while (*string==' ') string++;
  if (!(act = get_alias_text(word, must_exact)))
    ret = 0;
  else
    parse_line(act, string, TRUE, FALSE);
  alias_depth--;
  return ret;
}

/* ------------------------------------------------------------------- */
static char bad_chars[] = "!@#$%^&*(){}[]-=+\";|\\?/,";

static int check_var(char *key)
{
  char *loc, *loc2;
  loc = strchr(key, '[');
  if (loc && ((loc2 = strchr(loc, ']')) != NULL))
  {
    *loc = '.';
    *loc2++ = 0;
    while (loc2 && (*loc2 == '['))
      if ((loc = strchr(loc2, ']')) != NULL)
      {
        *loc2 = '.';
        *loc++ = 0;
        strcat(key, loc2);
        loc2 = loc;
      } else loc2 = NULL;
  }
  if (strpbrk(key, bad_chars)) {
    say("*** %s is an invalid variable name.", key);
    return FALSE;
  }
  return TRUE;
}

/* ------------------------------------------------------------------- */
static alias_ptr alias_create(char *key, alias_ptr next)
{
  alias_ptr tmp;
  tmp = (alias_ptr)mymalloc(sizeof(struct alias_struct));
  if (!tmp) return NULL;
  tmp->key = m_strcpy(key);
  tmp->next = next;
  tmp->action = NULL;
  tmp->sub = NULL;
  return tmp;
}
/* ------------------------------------------------------------------- */
static void alias_show(char *orig, char *key, alias_ptr list, int is_alias)
{
  alias_ptr f;
  char *ind;
  int n;
  ind = strstr(key, ".");
  if (ind) *ind++ = 0;
  for (f = list; f && ((n = strcasecmp(f->key, key)) < 0); f = f->next) ;
  if (!f || strncasecmp(key, f->key, strlen(key)) != 0) {
    say("*** No such %s: %s", is_alias?"alias":"assign", orig);
    return;
  }
  if (!ind) {
    say("*** %s:", is_alias?"Aliases":"Assigns");
    if (n==0) {
      if (f->action) say("***    %-15s\t%s", orig, f->action);
      if (f->sub) say("***    %-15s\t<Structure>", orig);
      return;
    }
    while (f && (strncasecmp(f->key, key, strlen(key)) == 0)) {
      if (f->action) say("***    %s%s\t%s", orig, &f->key[strlen(key)],
                         f->action);
      if (f->sub) say("***    %s%s\t<Structure>", orig, &f->key[strlen(key)]);
      f = f->next;
    }
  } else alias_show(orig, ind, f->sub, is_alias);
}
/* ------------------------------------------------------------------- */
static alias_ptr alias_delete(char *orig, char *key, alias_ptr list,
                              int is_alias)
{
  alias_ptr f, b;
  char *ind;
  int n;
  ind = strstr(key, ".");
  if (ind) *ind++ = 0;
  for (f = list, b = NULL; f && ((n = strcasecmp(f->key, key)) < 0);
       b = f, f = f->next) ;
  if (ind) ind[-1] = '.';
  if (!f || (n!=0)) { 
    say("*** No such %s: %s", is_alias?"alias":"assign", orig);
    return list;
  }
  if (!ind) {
    myfree(f->action);
    f->action = NULL;
    if (!f->sub) {
      if (b) b->next = f->next;
      else list = f->next;
      myfree(f->key);
      myfree(f);
    }
    return list;
  }
  f->sub = alias_delete(orig, ind, f->sub, is_alias);
  if (!f->sub && !f->action) {
    if (b) b->next = f->next;
    else list = f->next;
    myfree(f);
  }
  return list;
}
/* ------------------------------------------------------------------- */
static alias_ptr alias_insert(char *key, char *str, alias_ptr list,
                              int is_alias)
{
  alias_ptr f, b;
  char *ind;
  int n;
  n = -1;
  ind = strstr(key, ".");
  if (ind) *ind++ = 0;
  for (f = list, b = NULL; f && ((n = strcasecmp(f->key, key)) < 0);
       b = f, f = f->next) ;
  if (!ind) {
    if (n==0) myfree(f->action);
    else {
      if (!(f = alias_create(key, f))) return list;
      if (!b) list = f;
      else b->next = f;
    }
    f->action = m_strcpy(str);
    return list;
  }
  if (n!=0) {
    if (!(f = alias_create(key, f))) return list;
    if (!b) list = f;
    else b->next = f;
  }
  f->sub = alias_insert(ind, str, f->sub, is_alias);
  return list;
}
/* ------------------------------------------------------------------- */
static void new_user_alias(char *str, alias_ptr *list, int is_assign)
{
  char key[MAXLEN], *copy = NULL;
  grab_word(&str, ' ', key);
  if (*key == '-') {
    if (!check_var(&key[1])) return;
    if (!(copy = m_strcpy(&key[1]))) return;
    *list = alias_delete(&key[1], copy, *list, !is_assign);
    myfree(copy);
    return;
  }
  if (!check_var(key)) return;
  if (is_assign && strcasecmp(key, "FUNCTION_RETURN")==0) {
    set_function_return(str);
    return;
  }
  if (*str=='{') {
    char *end;
    end = matching_end(++str, '{', '}');
    if (end) *end = 0;
  }
  if ((!*str) && (is_assign != 2)) {
    if (!(copy = m_strcpy(key))) return;
    alias_show(key, copy, *list, !is_assign);
    myfree(copy);
  } else {
    *list = alias_insert(key, str, *list, !is_assign);
    if (is_assign != 2)
      say("*** %s %s added", is_assign?"ASSIGN":"ALIAS", key);
  }
}
/* ------------------------------------------------------------------- */
void user_assign(char *str, int first, char *subparams)
{
  new_user_alias(str, &new_assign_list.list, first?1:2);
}

void user_alias(char *str, int varpar, char *subparams)
{
  if (mode_set)
    new_user_alias(str, &mode_set->mode_data.alias_list.list, FALSE);
  else
    new_user_alias(str, &new_alias_list.list, FALSE);
}

int lookup_assign_value(char *str, char *ret, int len)
{
  alias_ptr match;
  int exact = 0;
  *ret = 0;
  match = alias_match(str, new_assign_list.list, &exact);
  if (!match || !match->action || !exact) return FALSE;
  strncpy(ret, match->action, len);
  ret[len] = 0;
  return TRUE;
}

int assign_matches(char *str, char *ret, int max)
{
  int len, copy, exact;
  alias_ptr match;
  char *good, *ind;
  *ret = 0;
  if (!(match = alias_match(str, new_assign_list.list, &exact))) return FALSE;
  good = str;
  while ((ind = strstr(good, ".")))
    good = ++ind;
  while (match && strncasecmp(good, match->key, strlen(good)) == 0) {
    if (max-->=0) strcat(ret, " ");
    if (max>strlen(match->key)) strcat(ret, match->key);
    max -= strlen(match->key);
    match = match->next;
  }
  return *ret;
}
