/* Copyright (c) 2000  Kevin Sullivan <nite@gis.net>
 *
 * Please refer to the COPYRIGHT file for more information.
 */

#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <ncurses.h>
#include <sys/time.h>

#include "colors.h"
#include "defines.h"
#include "scmds.h"
#include "scheck.h"
#include "vfuncs.h"
#include "alias.h"
#include "lists.h"
#include "nap.h"
#include "sscr.h"
#include "winio.h"

#ifdef MEMWATCH
  #include "memwatch.h"
#endif

/* the procedures in this file operate on two data structures: a
 * linked list of aliases and a linked list of "sets". Here a "set" is
 * something that is set by the command "set", i.e., a variable.
 *
 * alias_t provides for a simple key-value structure, where the key is
 * a string called "nm" and the value is a string called "args" (the
 * replacement for alias "nm").
 *
 * sets_t provides a similar key-value structure: the key is again a
 * string called "nm", and the value is a pair of a string "d" and an
 * integer "t". Apparently the intention is that t==0 if d represents
 * a number, t==1 otherwise.
 *
 * There is also the array vars, which is defined in vfuncs.c, and
 * maps certain names to functions.
 * */

extern vars_t vars[];
extern info_t info;

alias_t *alhead;
sets_t *setl;

/* ---------------------------------------------------------------------- */
/* the first part of this file deals with "sets", or variables that
   can be "/set" */

hardwired_t hw[] = {
  { "user", 1, &info.user, 0, "# your napster username" },
  { "pass", 2, &info.pass, 0, "# your napster password - optional" },
  { "email", 1, &info.email, 0, "# your napster email address" },
  { "upload", 1, &info.up, 0, "# list of upload directories, separated by semicolons" },
  { "download", 1, &info.down, 0, "# your download directory" },
  { "incomplete", 1, &info.incomplete, 0, "# your directory for incomplete files" },
  { "connection", 0, 0, 0,
    "# your connection speed, according to the following chart:\n"
    "#\n"
    "# Connection | Number\n"
    "# -------------------\n"
    "# Unknown    |  0\n"
    "# 14.4       |  1\n"
    "# 28.8       |  2\n"
    "# 33.6       |  3\n"
    "# 56.7       |  4\n"
    "# 64K ISDN   |  5\n"
    "# 128K ISDN  |  6\n"
    "# Cable      |  7\n"
    "# DSL        |  8\n"
    "# T1         |  9\n"
    "# T3 or >    | 10" },
  { "maxuploads", 0, 0, 0, "# maximal number of simultaneous uploads allowed" },
  { "maxupuser", 0, 0, 0, "# maximum number of simultaneous uploads per user" },
  { "maxdownloads", 0, 0, 0, "# maximum number of simultaneous downloads allowed" },
  { "maxdownuser", 0, 0, 0, "# maximum number of simultaneous downloads per user" },
  { "servers", 1, &info.serverlist, 0,
    "# list of servers, separated by semicolons (note: this is now ignored\n"
    "# and overwritten unless nometa is set)" },
  { "dataport", 1, &info.dataport, 0, "# port or range of ports to use for client-client connections.\n# Set this to 0 if you are behind a firewall" },
  { "logfile", 1, &info.logfile, 0, "# log file for transfer logs" },
  { "logallfile", 1, &info.logallfile, 0, "# log file for logging everything" },
  { "bandwidthdown", 0, 0, &info.bandwidthdown, "# global bandwidth limit for downloads (in kB/s)" },
  { "bandwidthdown1", 0, 0, &info.bandwidthdownconn, "# download bandwidth limit per connection (in kB/s)" },
  { "bandwidthup", 0, 0, &info.bandwidthup, "# global bandwidth limit for uploads (in kB/s)" },
  { "bandwidthup1", 0, 0, &info.bandwidthupconn, "# upload bandwidth limit per connection (in kB/s)" },

  { "announcepongs", 0, 0, 0, "# should receipt of PONG packets be announced?" },
  { "autopurge", 0, 0, 0, "# time to delete stopped items from up- and download lists" },
  { "autopurgeup", 0, 0, 0, "# time to delete stopped items from upload list" },
  { "autopurgedown", 0, 0, 0, "# time to delete stopped items from download list" },
  { "autoreply", 0, 0, 0, "# a string to send as an automatic reply to incoming user messages" },
  { "autorestart", 0, 0, &info.autorestart, "# automatically reconnect when connection to server lost?" },
  { "connecttimeout", 0, 0, 0, "# timeout after this many seconds while connecting to server" },
  { "cursorfollowsscreen", 0, 0, 0, "# should PgUp and PgDn move the screen, rather than the cursor, on the\n# search result screen?" },
  { "debug", 0, 0, 0, "# debug level" },
  { "hash", 0, 0, 0, "# shall we calculate MD5 hashes? Note that most servers don't use them." },
  { "identity", 0,0, 0, "# fake client id for fooling servers that refuse connections from nap client" },
  { "incompletesuffix", 0, 0, 0, "# suffix to use for incomplete files" },
  { "libraryfile", 1, &info.shared_filename, 0, "# location of your library file" },
  { "metaserver", 0, 0, 0, "# URL of a napigator-style metaserver" },
  { "metatimeout", 0, 0, 0, "# timeout for connecting to metaserver" },
  { "napping", 0, 0, 0, "# relative or absolute filename of the napping program to use" },
  { "newstimeout", 0, 0, 0, "# timeout for retrieving news" },
  { "noechosets", 0, 0, 0, "# should the /set command be quiet?" },
  { "nomasq", 0, 0, 0, "# should filename masquerading be disabled?" },
  { "nometa", 0, 0, 0, "# skip connecting to metaserver on startup?" },
  { "nonews", 0, 0, 0, "# do you want to skip news about new releases on startup?" },
  { "noping", 0, 0, 0, "# do you want to disable pings?" },
  { "noresultscreen", 0, 0, 0, "# should search results be displayed on the main screen?" },
  { "noscroll", 0, 0, 0, "# should main screen not scroll to bottom automatically on output?" },
  { "proxy", 0, 0, 0, "# URL of http proxy, if needed to access www through firewall" },
  { "savechannels", 0, 0, 0, "# should your open channels to be saved between sessions?" },
  { "savepass", 0, 0, 0, "# should password be saved in config file?" },
  { "scrollsize", 0, 0, 0, "# limit on the number of lines in main screen, or 0 for no limit" },
  { "sdefaults", 0, 0, 0, "# default keystrokes for search result screen appearance" },
  { "sharetypes", 0, 0, 0, "# semicolon-separated list of file extensions to share in addition to mp3 files\n" "# Case insensitive. Use '*' for 'all files'." },
  { "showtoomanyuploads", 0, 0, 0, "# display a message when upload limit is reached?" },
  { "turdsize", 0, 0, 0, "# size of the largest incomplete file which will be auto-removed" }
};

int hwsize = sizeof(hw)/sizeof(hardwired_t);

/* Append sets from file. If overwrite!=0, overwrite any old
 * values. If overwrite==0, only write values that were previously
 * unset (this is used for reading the initial config file, so as to
 * not overwrite anything that was set explicitly on the command
 * line). Return -1 on i/o error (with errno set), 0 on success, and 1
 * if there were ill-formed lines. Log progress info to win if verbose
 * is set. In any case, log warnings to win. */

int loadsets(const char *fn, WINDOW *win, int overwrite, int verbose)
{
  FILE *f;
  char *buf1, *nm, *val;
  int len;
  int warn = 0;

  f = fopen(fn, "r");
  if (!f)
    return(-1);
  
  while (1)
  {
    buf1 = nap_getline(f);
    if (buf1 == NULL) {
      break;
    }
    nm = buf1;
    if (*nm == '#' || *nm == 0) {          /* if comment or empty, skip line */
      free(buf1);
      continue;
    }
    val = strchr(nm, '=');
    if (!val)
      goto badline;
    *val=0;
    val = strip(val+1);
    nm = strip(nm);

    /* at this point, nm is the name and val is the value */
    if (overwrite || !getval(nm)) {
      chset(nm,val);
      if (verbose) {
	wp(win, "%s%s=%s%s\n", BRIGHT(BLUE), nm, val, WHITE);
	drw(win);
      }
    }
    free(buf1);
    continue;
    
    /* we go here is we found an ill-formed line */
  badline: 
    warn = 1;  /* remember that there was a warning */
    wp(win, "Warning: ill-formed line in config file (ignoring):\n");
    len = strlen(buf1);
    if (len>0 && buf1[len-1]=='\n')
      buf1[len-1]=0;
    wp(win, "%s\n", buf1);
    drw(win);
    free(buf1);
    continue;

  } /* while (1) */

  fclose(f);
  
  return warn ? 1 : 0;
}

/* save sets to file (in format readable by loadsets, with
   human-readable comments). Return 0 on success, -1 on i/o failure
   (with errno set). */
int savesets(const char *fn)
{
  FILE *f;
  sets_t *cur;
  int i;
  char *v;

  f = fopen(fn, "w");
  if (!f)
    return(-1);
  
  fprintf(f, "# nap config file. Note: for boolean options, 1=true, 0=false\n\n");

  /* first deal with hardwired variables */
  for (i=0; i<hwsize; i++) {
    if (hw[i].comment)
      fprintf(f, "%s\n", hw[i].comment);
    v = getrealval(hw[i].nm);
    if (v) {
      if (hw[i].type==2 && nvar_default("savepass", 0) == 0) {
	fprintf(f, "%s=%s\n", hw[i].nm, "?");
      } else {
	fprintf(f, "%s=%s\n", hw[i].nm, v);
      }
    } else {
      fprintf(f, "#%s=\n", hw[i].nm);
    }
    if (hw[i].comment)
      fprintf(f, "\n");
  }

  fprintf(f, "# other user variables:\n");

  /* now print all the remaining variables */
  for (cur=setl;cur;cur=cur->next) {
    for (i=0; i<hwsize; i++) {
      if (!strcasecmp(hw[i].nm, cur->nm))
	goto next;
    }
    fprintf(f, "%s=%s\n", cur->nm, cur->d);
  next:
    ;
  }

  fclose(f);
  
  return(0);
}

/* print a list of current variable assignments */
void printsets(WINDOW *win) {
  sets_t *elt;
  int count = 0;
  int i;
  char *v;

  /* first print hardwired sets */
  for (i=0; i<hwsize; i++) {
    v = getval(hw[i].nm);
    if (v) {
      wp(win, ""BRIGHT(BLUE)"%s = "WHITE"%s\n", hw[i].nm, v);
      count++;
    }
  }
  
  /* then print remaining sets */
  list_forall(elt, setl) {
    for (i=0; i<hwsize; i++) {
      if (!strcasecmp(hw[i].nm, elt->nm))
        goto next;
    }
    wp(win, ""BRIGHT(BLUE)"%s = "WHITE"%s\n", elt->nm, elt->d);
    count++;
  next:
    ;
  }

  if (!count)
    wp(win, "No variables are set\n");

  drw(win);
} 

/* look up set by name, or NULL if not found. Internal use only.  */
sets_t *findset(const char *nm)
{
  sets_t *cur;
  
  for (cur=setl; cur && strcasecmp(cur->nm, nm); cur=cur->next);
  
  return(cur);
}

/* add a set to the list. Note: we do not check for duplicates, thus
   this is for internal use only. External functions should call
   chset. */
void addset(const char *nm, const char *val)
{
  sets_t *elt;
  char *endptr;
  int i;

  /* check if this is a hardwired set */
  for (i=0; i<hwsize; i++) {
    if (!strcasecmp(nm, hw[i].nm)) 
      break;
  }

  if (i<hwsize && hw[i].type) {
    free(*hw[i].strp);
    *hw[i].strp = strdup(val);
    if (hw[i].intp) {
      *hw[i].intp = strtol(val, &endptr, 10);
      if (!endptr || *endptr!='\0') { /* string was invalid number */
	*hw[i].intp = 0;
      }
    }
    return;
  }

  /* make new assignment */
  elt = (sets_t *)malloc(sizeof(sets_t));
  elt->nm = strdup(nm);
  elt->d = strdup(val);
  elt->intval = strtol(val, &endptr, 10);
  elt->t = 0;
  if (!endptr || *endptr!='\0') { /* string was invalid number */
    elt->intval = 0;
    elt->t = 1;
  }

  if (i<hwsize && hw[i].intp) {
    *hw[i].intp = elt->intval;
  }

  /* now append it to the list */
  list_append(sets_t, setl, elt);
}

/* add a set to the list, but remove duplicate first. Set t=0 if this
   might be a number. If d is NULL, just delete the set. */
void chset(const char *nm, const char *d)
{
  delset(nm);
  if (d)
    addset(nm, d);
}

/* get the string value of a set, or "?" for a hidden value */
char *getval(const char *nm) {
  return getval_t_r(nm, NULL, 0);
}

/* get the string value of a set, even for a hidden value */
char *getrealval(const char *nm) {
  return getval_t_r(nm, NULL, 1);
}

/* get both string and integer values of a named set. Set *t to be the
   integer value (unless t=NULL). Let d=NULL if not found. */
char *getval_t(const char *nm, int *t) {
  return getval_t_r(nm, t, 0);
}

/* get both string and integer values of a named set. Set *t to be the
   integer value (unless t=NULL). Let d=NULL if not found. If real==0
   get "?" for a hidden value, else the actual value. */
char *getval_t_r(const char *nm, int *t, int real)
{
  sets_t *cur;
  int i;
  
  if (t)
    *t = 1;

  /* check for hardwired sets first */
  for (i=0; i<hwsize; i++) {
    if (!strcasecmp(nm, hw[i].nm) && hw[i].type != 0) {
      if (*hw[i].strp) {
	if (real==1 || hw[i].type == 1)
	  return *hw[i].strp;
	else
	  return "?";
      } else 
	return NULL;
    }
  }
  
  cur = findset(nm);
  if (!cur)
    return NULL;
  
  if (t)
    *t = cur->t;
  
  return(cur->d);
}

/* remove one named set from list */
void delset(const char *nm)
{
  sets_t *cur, *cur1=NULL;
  int i;

  /* check for hardwired sets first */
  for (i=0; i<hwsize; i++) {
    if (!strcasecmp(nm, hw[i].nm)) 
      break;
  }

  if (i<hwsize && hw[i].type != 0) {
    free(*hw[i].strp);
    *hw[i].strp = NULL;
    if (hw[i].intp) {
      *hw[i].intp = 0;
    }
    return;
  }

  for (cur=setl;cur&&strcasecmp(cur->nm, nm);cur=cur->next)
    cur1 = cur;
  
  if (!cur)
    return;
  
  if (cur1)
    cur1->next = cur->next;
  else if (cur->next)
    setl = cur->next;
  else
    setl = NULL;
  
  free(cur->d);
  free(cur->nm);
  free(cur);
  
  if (i<hwsize && hw[i].intp) {
    *hw[i].intp = 0;
  }
}

/* lookup set's value as a number, or -1 if not set or a non-number. */
long nvar(const char *nm) 
{
  return nvar_default(nm, -1);
}

/* lookup set's value as a number, or default if not set or a non-number. */
long nvar_default(const char *nm, long deflt)
{
  sets_t *cur;
  int i;

  /* check for hardwired sets first */
  for (i=0; i<hwsize; i++) {
    if (!strcasecmp(nm, hw[i].nm) && hw[i].type != 0) {
      return *hw[i].strp ? strtol(*hw[i].strp, NULL, 10) : deflt;
    }
  }
  
  cur = findset(nm);
  if (!cur)
    return(deflt);
  
  if (cur->t)
    return(deflt);
  
  return(cur->intval);
}

/* ---------------------------------------------------------------------- */

/* return a copy of m with characters bp..ep-1 replaced by n. n may be
   longer or shorter than ep-bp; in fact even ep<bp is permitted.  The
   only requirement is that ep,bp <= strlen(m). */
char *ins(char *m, char *n, int bp, int ep)
{
  char *st;
  
  st = (char *)malloc(bp+strlen(n)+strlen(m+ep)+1);
  memset(st, 0, bp+strlen(n)+strlen(m+ep)+1);
  
  strncpy(st, m, bp);
  sprintf(st, "%s%s%s", st, n, m+ep);
  
  return(st);
}

/* tok is a NULL-terminated list of tokens. Print them to wchan (the
   main window), enclosed in |..|. Currently unused. */
void prt(char **tok)
{
  int i;
  char buf[1024];
  
  memset(buf, 0, sizeof(buf));

  for (i=0;tok[i];i++)
  {
    strcat(buf, tok[i]);
    strcat(buf, " ");
  }
  
  buf[strlen(buf)-1] = 0;
  
  wp(wchan, "|%s|\n", buf);
  drw(wchan);
}

unsigned char *dovars(unsigned char *nm)
{
  unsigned char *r=NULL, *t;
  char **vtok=NULL, b[512], **tok;
  int i, j, vcnt=0, cnt, x, y;
  
  if (findset(nm))
    r = strdup(getval(nm));
  
  if (strchr(nm, '('))
  {
    memset(b, 0, sizeof(b));
    strcpy(b, strchr(nm, '(')+1);
    b[strlen(b)-1] = 0;
    vtok = form_vtoks(b, &vcnt);
  }
  
  if (!vtok)
  {
    for (i=0;i<vcnt;i++)
    {
      if (*vtok[i] == '$')
      {
        t = dovars(vtok[i]+1);
        if (t)
        {
          free(vtok[i]);
          vtok[i] = t;
        }
      }
    }
  }
  else
  {
    for (i=0;i<vcnt;i++)
    {
      tok = form_toks(vtok[i], &cnt);
      tok = fxv(tok, cnt, &cnt);
      for (j=0;j<cnt;j++)
      {
        if (*tok[j] == '$' && !isdigit(tok[j][1]) && tok[j][1] != '+')
        {
          t = dovars(tok[j]+1);
          if (t)
          {
            free(tok[j]);
            tok[j] = t;
          }
        }
      }
      for (j=0;tok[j];j++)
      {
        if (*tok[j] == '$' && tok[j][1] == '+' && !tok[j][2])
        {
          if (j > 0 && tok[j+1] && tok[j-1])
          {
            for (x=1;tok[j-x]&&!(*tok[j-x]);x++);
            for (y=1;tok[j+y]&&!(*tok[j+y]);y++);
            if (!tok[j-x])
              x--;
            if (!tok[j+y])
              y--;
            tok[j] = (char *)realloc(tok[j], strlen(tok[j-x])+strlen(tok[j+y])+1);
            if (strcmp(tok[j-x], "$+"))
              strcpy(tok[j], tok[j-x]);
            else
              *tok[j] = 0;
            if (strcmp(tok[j+y], "$+"))
              strcat(tok[j], tok[j+y]);
            *tok[j-x] = 0;
            *tok[j+y] = 0;
          }
          else
            *tok[j] = 0;
        }
      }
      
      vtok[i] = (char *)realloc(vtok[i], 2048);
      memset(vtok[i], 0, 2048);
      for (j=0;tok[j];j++)
      {
        if (*tok[j])
        {
          strcat(vtok[i], tok[j]);
          strcat(vtok[i], " ");
        }
        free(tok[j]);
      }
      free(tok);
      vtok[i][strlen(vtok[i])-1] = 0;
      vtok[i] = (char *)realloc(vtok[i], strlen(vtok[i])+1);
    }
  }
  
  memset(b, 0, sizeof(b));
  for (i=0;nm[i]&&nm[i]!='(';i++)
    b[i] = nm[i];
  
  for (i=0;;i++)
  {
    if (!vars[i].nm)
    {
      if (vtok)
      {
        for (j=0;j<vcnt;j++)
          free(vtok[j]);
        free(vtok);
      }
      break;
    }
    if (!strcasecmp(vars[i].nm, b))
    {
      r = vars[i].func(vtok, vcnt);
      if (vtok)
      {
        for (j=0;j<vcnt;j++)
          free(vtok[j]);
        free(vtok);
      }
      break;
    }
  }
  
  if (!r)
    return(strdup(""));
  else  
    return(r);
}

/* tokenize a string t of the form " x1, x2, ..., xn" into the tokens
   x1 through xn. Here x1...xn are well-balanced strings (with respect
   to '(' and ')'). Notes: x1...xn may contain additional commas
   inside parentheses.  Whitespace is skipped at the beginning and after
   commas, but not before commas. The behavior is undefined (messy) if
   x1...xn are not well-balanced. Some tokens may be the empty string;
   this also happens if t consist of one or more spaces only.
   Returns: NULL-terminated array of tokens and number of tokens in
   n. */

char **form_vtoks(char *t, int *n)
{
  int i, j, k, c=0;
  char **ret;
  
  if (!strlen(t))
  {
    *n = 0;
    return(NULL);
  }
  
  ret = (char **)malloc(4096);
  
  for (i=0,j=0;;i++)  /* i=token number, j=character number in t. */
  {
    ret[i] = (char *)malloc(1024);
    memset(ret[i], 0, 1024);
    while (t[j] && isspace(t[j]))
      j++;
    for (k=0,c=0;t[j];k++,j++) /* k=char. number in ret[i], c=nesting level */
    {
      if (t[j] == ',' && !c)
        break;
      if (c < 0)
        c = 0;
      if (t[j] == '(')
        c++;
      else if (t[j] == ')')
        c--;
      ret[i][k] = t[j];
    }
    ret[i] = (char *)realloc(ret[i], strlen(ret[i])+1);
    if (!t[j])
      break;
    j++;
  }
  
  ret[i+1] = NULL;
  *n = i+1;
  
  return(ret);
}

unsigned char *doalias(alias_t *al, char **tok, int cnt)
{
  char *ret, echr;
  char **atok, p[16], pt[2048], tb[2048], *t;
  int i, j, acnt, n, l, c;
  
  t = strdup(al->args);
  
  for (i=0;al->args[i]&&al->args[i] == ' ';i++);
  
  if (al->args[i] == '{')
  {
    for (i=0,c=0;t[i];i++)
    {
      if (c < 0)
        c = 0;
      if (t[i] == '{' && !c)
      {
        t[i] = ' ';
        c++;
      }
      else if (t[i] == '{')
        c++;
      else if (t[i] == '}' && c == 1)
      {
        t[i] = ' ';
        c--;
      }
      else if (t[i] == '}')
        c--;
    }
  }
  
  if (t[strlen(t)-1] == '|' || t[strlen(t)-1] == '\n')
    t[strlen(t)-1] = 0;
  
  memset(pt, 0, sizeof(pt));
  strcpy(pt, t);
  free(t);
  
  for (i=0;pt[i];i++)
  {
    if (pt[i] == '$' || pt[i] == '?')
    {
      for (j=i,echr=0;;j++)
      {
        if (!pt[j] || pt[j] == ',' || pt[j] == ' ' || pt[j] == '=' || pt[j] == '!' || pt[j] == '>' || pt[j] == '<')
        {
          echr = pt[j];
          break;
        }
      }
      if (i > 0 && (pt[i-1] == '(' || pt[i-1] == ')' || pt[i-1] == ',' || pt[i-1] == '=' || pt[i-1] == '!' || pt[i-1] == '<' || pt[i-1] == '>' || pt[i-1] == ' '))
      {
        if (pt[i-1] != ' ')
        {
          memset(tb, 0, sizeof(tb));
          strcpy(tb, pt+i);
          strcpy(pt+i+1, tb);
          pt[i] = ' ';
        }
        else
          j--;
        if (echr && echr != ' ')
        {
          memset(tb, 0, sizeof(tb));
          strcpy(tb, pt+j+1);
          strcpy(pt+j+2, tb);
          pt[j+1] = ' ';
        }
      }
    }
  }
  
  t = strdup(pt);
  
  atok = form_tokso(t, &acnt);
  free(t);
  
  for (i=0,j=0;i<acnt;i++)
    if (*atok[i] == '$' && atok[i][1])
      if (atoi(atok[i]+1) > j)
        j = atoi(atok[i]+1);

  if ((cnt-1) < j) {
    for (i=0; i<acnt; i++) {
      free(atok[i]);
    }
    free(atok);
    return(NULL);
  }  

  for (i=0;atok[i];i++)
  {
    if (*atok[i] == '?' && isdigit(atok[i][1]))
    {
      if (atoi(atok[i]+1) <= (cnt-1))
        *atok[i] = '$';
      else
      {
        for (j=1;;j++)
        {
          if (!isdigit(atok[i][j]) && atok[i][j] != '-')
          {
            memset(tb, 0, sizeof(tb));
            strcpy(tb, atok[i]+j);
            strcpy(atok[i], tb);
            break;
          }
        }
      }
    }
  }
  
  for (i=0,l=0;atok[i];i++)
  {
    if (*atok[i] == '$' && isdigit(atok[i][1]) && !strchr(atok[i], '-'))
    {
      n = atoi(atok[i]+1);
      memset(tb, 0, sizeof(tb));
      for (j=1;atok[i][j];j++)
        if (!isdigit(atok[i][j]))
          break;
      strcpy(tb, atok[i]+j);
      atok[i] = (char *)realloc(atok[i], strlen(tok[n])+strlen(tb)+1);
      strcpy(atok[i], tok[n]);
      strcat(atok[i], tb);
    }
    else if (*atok[i] == '$' && isdigit(atok[i][1]))
    {
      memset(p, 0, sizeof(p));
      for (j=1;;j++)
      {
        if (!isdigit(atok[i][j]) && atok[i][j] != '-')
        {
          memset(tb, 0, sizeof(tb));
          strcpy(tb, atok[i]+j);
          strcpy(atok[i], tb);
          break;
        }
        if (atok[i][j] != '-')
          p[j-1] = atok[i][j];
      }
      n = atoi(p);
      memset(pt, 0, sizeof(pt));
      for (j=n;tok[j];j++)
      {
        strcat(pt, tok[j]);
        strcat(pt, " ");
      }
      pt[strlen(pt)-1] = 0;
      atok[i] = (char *)realloc(atok[i], strlen(pt)+strlen(tb)+1);
      strcpy(atok[i], pt);
      strcat(atok[i], tb);
    }
    else if (*atok[i] == '$' && !strcasecmp(atok[i]+1, "num"))
    {
      free(atok[i]);
      atok[i] = NULL;
      msprintf(&atok[i], "%i", cnt-1);
    }
    else if (*atok[i] == '$' && !strcasecmp(atok[i]+1, "str"))
    {
      free(atok[i]);
      atok[i] = (char *)malloc(4096);
      memset(atok[i], 0, 4096);
      for (j=0;tok[j];j++)
      {
        if (*tok[j])
        {
          strcat(atok[i], tok[j]);
          strcat(atok[i], " ");
        }
      }
      atok[i][strlen(atok[i])-1] = 0;
      atok[i] = (char *)realloc(atok[i], strlen(atok[i])+1);
    }
    l+=strlen(atok[i])+1;
  }

  ret = (char *)malloc(4096);
  memset(ret, 0, 4096);
  
  for (i=0;atok[i];i++)
  {
    if (*atok[i])
    {
      strcat(ret, atok[i]);
      strcat(ret, " ");
    }
    free(atok[i]);
  }
  
  free(atok);
  
  ret[strlen(ret)-1] = 0;
  ret = (char *)realloc(ret, strlen(ret)+1);
  
  return((unsigned char *)ret);
}

/* save alias list to named file */
int savealiases(char *fn)
{
  FILE *f;
  alias_t *cur;
  
  f = fopen(fn, "w");
  if (!f)
    return(-1);
  
  for (cur=alhead;cur;cur=cur->next)
    fprintf(f, "%s %s\n", cur->nm, cur->args);
  
  fclose(f);
  
  return(1);
}

/* load aliases from named file and append to alias list. The format
   is ad hoc. */
int loadaliases(char *fn)
{
  FILE *f;
  alias_t *cur;
  char buf[4096], tb[4096];
  int i, c, j;
  char *p;
  
  f = fopen(fn, "r");
  if (!f)
    return(-1);
  
  while (1)
  {
    if (fgets(buf, sizeof(buf), f) == NULL)
      break;
    if (*buf == '#')   /* skip comments in column 0 */
      continue;
    if (buf[strlen(buf)-1] == '\n')
      buf[strlen(buf)-1] = 0;
    for (i=0,c=0;buf[i];i++)  /* find the first space, if any */
    {
      if (isspace(buf[i]))
      {
        c = 1;
        break;
      }
    }
    /* no space found; continue on next line. Note: no comment allowed
       here.  */

    if (!c)
    {
      buf[strlen(buf)] = ' ';
      fgets(buf+strlen(buf), sizeof(buf)-strlen(buf), f);
      if (buf[strlen(buf)-1] == '\n')
        buf[strlen(buf)-1] = 0;
    }
    
    buf[strlen(buf)] = '\n';
    
    for (c=0,i=0;;i++)
    {
      if (c < 0)
      {
        c = 0;
        break;
      }
      if (!buf[i] && c)
      {
        while (1)
        {
          memset(tb, 0, sizeof(tb));
          if (fgets(tb, sizeof(tb), f) == NULL)
	    break;
          for (j=0;tb[j]==' ';j++);
          if (tb[j] != '#')
          {
            strcpy(buf+i+1, tb);
            buf[i] = ' ';
            break;
          }
        }
      }
      else if (!buf[i])
        break;
      if (buf[i] == '{')
        c++;
      else if (buf[i] == '}')
        c--;
    }
    
    for (i=0;buf[i];i++)
    {
      if (buf[i] == '\n')
      {
        for (j=i+1;buf[j]==' '&&buf[j]!='{';j++);
        if (buf[j] == '{')
        {
          buf[i] = ' ';
          strncpy(buf+i+1, buf+j, strlen(buf+j));
          buf[strlen(buf)-(j-i)+1] = 0;
        }
      }
    }
    if (*buf == ' ')
      continue;
    for (i=strlen(buf)-1;buf[i] == ' '&&i>=0;i--);
    while (buf[i] == '\n')
    {
      buf[i] = 0;
      i--;
    }
    /* calculate name and value */
    p = strchr(buf, ' ');
    if (!p) {
      continue;
    }
    *p = 0;
    p++;

    /* delete any old alias of the same name */
    list_unlink_cond(alias_t, alhead, cur, !strcasecmp(cur->nm, buf));
    if (cur) {
      free(cur->nm);
      free(cur->args);
      free(cur);
    }
    /* now create new element of the alias list */
    cur = (alias_t *)malloc(sizeof(alias_t));
    cur->nm = strdup(buf);
    cur->args = strdup(p);

    /* and append it */
    list_append(alias_t, alhead, cur);

  } /* while (1) */
  
  fclose(f);
  
  return(1);
}

/* evaluate a (presumably boolean) expression. Used by such things as
   "/if" and "/while" */
char *strev(char *str, unsigned char l, unsigned char r, char *(*func)())
{
  char *s1, *s2, *rt, *t, *t2;
  char t3[1024], t4[1024], **tok, echr;
  int i, j, cnt;
  
  memset(t3, 0, sizeof(t3));
  strcpy(t3, str);
  
  for (i=0;t3[i];i++)
  {
    if (t3[i] == '$')
    {
      for (j=i,echr=0;;j++)
      {
        if (!t3[j] || t3[j] == ',' || t3[j] == ' ' || t3[j] == '=' || t3[j] == '!' || t3[j] == '>' || t3[j] == '<')
        {
          echr = t3[j];
          break;
        }
      }
      if (i > 0 && (t3[i-1] == '(' || t3[i-1] == ')' || t3[i-1] == ',' || t3[i-1] == '=' || t3[i-1] == '!' || t3[i-1] == '<' || t3[i-1] == '>' || t3[i-1] == ' '))
      {
        if (t3[i-1] != ' ')
        {
          memset(t4, 0, sizeof(t4));
          strcpy(t4, t3+i);
          strcpy(t3+i+1, t4);
          t3[i] = ' ';
        }
        else
          j--;
        if (echr && echr != ' ')
        {
          memset(t4, 0, sizeof(t4));
          strcpy(t4, t3+j+1);
          strcpy(t3+j+2, t4);
          t3[j+1] = ' ';
        }
      }
    }
  }
  
  if (strlen(t3) > 3 && t3[strlen(t3)-1] == ')' && t3[strlen(t3)-2] == ')')
  {
    t3[strlen(t3)-1] = ' ';
    t3[strlen(t3)] = ')';
  }
  
  t3[strlen(t3)-1] = ' ';
  t3[strlen(t3)] = ')';
  
  tok = form_tokso(t3, &cnt);
  tok = fxv(tok, cnt, &cnt);
  
  for (i=0;tok[i];i++)
  {
    if (*tok[i] == '$' && tok[i][1] && !isdigit(tok[i][1]))
    {
      t = dovars(tok[i]+1);
      if (t)
      {
        free(tok[i]);
        tok[i] = t;
      }
    }
  }
  
  memset(t3, 0, sizeof(t3));
  
  for (i=0;tok[i];i++)
  {
    if (*tok[i])
    {
      strcat(t3, tok[i]);
      strcat(t3, " ");
    }
    free(tok[i]);
  }
  
  free(tok);
  
  if (t3[strlen(t3)-1] == ' ')
    t3[strlen(t3)-1] = 0;
  
  if (t3[strlen(t3)-1] != r)
  {
    t3[strlen(t3)] = ' ';
    t3[strlen(t3)] = r;
    t3[strlen(t3)] = 0;
  }
  
  rt = strdup(t3);
  
  for (;;)
  {
    s1 = strrchr(rt, l);
    if (s1 == NULL)
      break;
    if (strchr(rt, r) == NULL)
    {
      free(rt);
      return(NULL);
    }
    s2 = (char *)malloc(abs(strchr(s1, r)-s1));
    strncpy(s2, s1+1, abs(strchr(s1, r)-s1)-1);
    s2[abs(strchr(s1, r)-s1)-1] = '\0';
    t = func(s2);
    if (t == NULL)
    {
      free(rt);
      free(s2);
      return(NULL);
    }
    t2 = strdup(rt);
    free(rt);
    s1 = strrchr(t2, l);
    rt = ins(t2, t, abs(s1-t2), abs((s1-t2))+(abs((strchr(s1, r)-s1))+1));
    free(t2);
    free(t);
  }
  
  return(rt);
}

char *cmp(char *st)
{
  char b[512], r[512], **tok, *tm=NULL, *rt=NULL;
  int i=0, j, cnt, k, l=0, t;
  
  memset(b, 0, sizeof(b));
  memset(r, 0, sizeof(r));
  
  while (st[i])
  {
    while (st[i] == ' ' && st[i])
      i++;
    for (b[0]='\'',j=1;st[i]&&st[i]!='&'&&st[i]!='^';j++,i++)
    {
      if (st[i] == '=' || st[i] == '!' || st[i] == '>' || st[i] == '<')
      {
        b[j++] = '\'';
        b[j++] = ' ';
        b[j++] = st[i];
        b[j++] = ' ';
        b[j] = '\'';
      }
      else
        b[j] = st[i];
    }
    b[j] = '\'';
    b[j+1] = 0;
    if (st[i] == '&' || st[i] == '^')
      i++;
    tok = form_toks(b, &cnt);
    tok = fxv(tok, cnt, &cnt);
    
    if (!cnt)
    {
      tok[0] = NULL;
      tok[1] = NULL;
      tok[2] = NULL;
    }
    if (cnt == 1)
    {
      tok[1] = NULL;
      tok[2] = NULL;
    }
    else if (cnt == 2)
      tok[2] = NULL;
    
    if (tok[0] && *tok[0] == '$')
    {
      tm = dovars(tok[0]+1);
      if (tm)
      {
        free(tok[0]);
        tok[0] = tm;
      }
    }
    if (tok[1] && *tok[1] == '$')
    {
      tm = dovars(tok[1]+1);
      if (tm)
      {
        free(tok[1]);
        tok[1] = tm;
      }
    }
    if (tok[2] && *tok[2] == '$')
    {
      tm = dovars(tok[2]+1);
      if (tm)
      {
        free(tok[2]);
        tok[2] = tm;
      }
    }
    
    tm = NULL;
    if (!tok[0])
      tm = strdup("");
    else
      msprintf(&tm, "\'%s\' %s \'%s\'", tok[0]?tok[0]:"", tok[1]?tok[1]:"", tok[2]?tok[2]:"");
    for (k=0;k<cnt;k++)
      free(tok[k]);
    free(tok);
    tok = form_toks(tm, &cnt);
    free(tm);
    
    if (!cnt)
    {
      tok[0] = NULL;
      tok[1] = NULL;
      tok[2] = NULL;
    }
    if (cnt == 1)
    {
      tok[1] = NULL;
      tok[2] = NULL;
    }
    else if (cnt == 2)
      tok[2] = NULL;
    
    if (tok[2] && !(*tok[2]))
    {
      free(tok[2]);
      tok[2] = NULL;
      cnt--;
    }
    if (tok[1] && !(*tok[1]))
    {
      free(tok[1]);
      tok[1] = NULL;
      if (tok[2])
      {
        tok[1] = strdup(tok[2]);
        free(tok[2]);
        tok[2] = NULL;
      }
      cnt--;
    }
    if (tok[0] && !(*tok[0]))
    {
      free(tok[0]);
      if (tok[1])
      {
        tok[0] = strdup(tok[1]);
        free(tok[1]);
        tok[1] = NULL;
      }
      if (tok[2])
      {
        tok[1] = strdup(tok[2]);
        free(tok[2]);
        tok[2] = NULL;
      }
      cnt--;
    }
    
    if (cnt == 2)
    {
      if (*tok[0] == '=' || *tok[0] == '!' || *tok[0] == '>' || *tok[0] == '<')
      {
        if (*tok[0] == '!')
        {
          if (strcmp(tok[1], "0"))
          {
            free(tok[0]);
            free(tok[1]);
            tok[3] = NULL;
            tok[2] = strdup("0");
            tok[1] = strdup("!");
            tok[0] = strdup("0");
          }
          else
          {
            free(tok[0]);
            free(tok[1]);
            tok[3] = NULL;
            tok[2] = strdup("0");
            tok[1] = strdup("=");
            tok[0] = strdup("0");
          }
        }
        else
        {
          tok[3] = NULL;
          tok[2] = strdup(tok[1]);
          free(tok[1]);
          tok[1] = strdup(tok[0]);
          free(tok[0]);
          tok[0] = strdup("0");
        }
      }
      else if (*tok[1] == '=' || *tok[1] == '!' || *tok[1] == '>' || *tok[1] == '<')
      {
        tok[3] = NULL;
        tok[2] = strdup(tok[0]);
        if (*tok[1] == '=')
          *tok[1] = '!';
        else if (*tok[1] == '!')
          *tok[1] = '=';
      }
      cnt = 3;
    }
    else if (cnt == 1)
    {
      if (strcmp(tok[0], "0") || *tok[0] == '!')
      {
        free(tok[0]);
        tok[0] = strdup("1");
        tok[1] = strdup("=");
        tok[2] = strdup("1");
        tok[3] = NULL;
      }
      else
      {
        free(tok[0]);
        tok[0] = strdup("1");
        tok[1] = strdup("!");
        tok[2] = strdup("1");
        tok[3] = NULL;
      }
      cnt = 3;
    }
    else if (!cnt)
    {
      tok[0] = strdup("1");
      tok[1] = strdup("!");
      tok[2] = strdup("1");
      tok[3] = NULL;
      cnt = 3;
    }
    
    if (cnt == 3)
    {
      if (*tok[1] == '=')
      {
        if (!strcasecmp(tok[0], tok[2]))
          r[l++] = 1;
        else
          r[l++] = 0;
      }
      else if (*tok[1] == '!')
      {
        if (!strcasecmp(tok[0], tok[2]))
          r[l++] = 0;
        else
          r[l++] = 1;
      }
      else if (*tok[1] == '>')
      {
        if (atoi(tok[0]) > atoi(tok[2]))
          r[l++] = 1;
        else
          r[l++] = 0;
      }
      else if (*tok[1] == '<')
      {
        if (atoi(tok[0]) < atoi(tok[2]))
          r[l++] = 1;
        else
          r[l++] = 0;
      }
    }
    
    for (k=0;tok[k];k++)
      free(tok[k]);
    free(tok);
    
    if (i > 0 && (st[i-1] == '&' || st[i-1] == '^'))
      r[l++] = st[i-1];
  }
  
  r[l] = 0;
  
  for (i=0,t=0;i<l;i++)
  {
    if (r[i] == '&')
      t &= r[++i];
    else if (r[i] == '^')
      t |= r[++i];
    else
      t = r[i];
  }
  
  msprintf(&rt, "%i", t);
  
  return(rt);
}
