#include <stdio.h>
#include <string.h>
#ifdef __GNUC__
#else
#include <stdlib.h>
#endif
#include <ctype.h>
#include <time.h>
#include <ssdef.h>
#ifdef __GNUC__
#else
#include <unixio.h>
#endif
#include <file.h>
#include "base_includes.h"
#include "qio.h"
#include "alias.h"
#include "ff.h"
#include "builtin.h"

extern char userhost[MAXLEN], curr_time_s[30];
extern server_ptr gsrv;
static int did_expand = 0;

#define LEFT_PAREN '('
#define RIGHT_PAREN ')'
#define LEFT_BRACE '{'
#define RIGHT_BRACE '}'
#define LEFT_BRACKET '['
#define RIGHT_BRACKET ']'

/* --------------------- start string routines -------------------------- */
static int ff_strncpy(char *s1, char *s2, int len, int maxlen)
{
  maxlen--;
  if (len>maxlen) len = maxlen;
  (void)strncpy(s1, s2, len);
  s1[len] = 0;
  return len;
}

char *m_copy(char *data, int len)
{
  char *tmp;
  tmp = (char *)mymalloc(len);
  if (tmp) memcpy(tmp, data, len);
  return tmp;
}

char *m_strcpy(char *str)
{
  char *xx;
  xx = mymalloc(strlen(str)+1);
  if (!xx) return NULL;
  (void)strcpy(xx, str);
  return xx;
}

static char *m_strcpy_quote(char *start, char *end)
{
  char *new = NULL, *walk = NULL;
  int len;
  len = (int)end - (int)start + 1;
  if (len<=0) return NULL;
  walk = new = (char *)mymalloc(len);
  if (!walk) return NULL;
  while (start<end) {
    if (*start=='\\') start++;
    else *walk++ = *start++;
  }
  *walk = 0;
  return new;
}

#define FIRST   1
#define SECOND  2

char *m_strcat(char *s1, char *s2, int extra, int deallocate)
{
  char *new;
  int len;
  len = (s1?strlen(s1):0) + (s2?strlen(s2):0);
  new = (char *)mymalloc(len + 1 + extra);
  if (!new) return new;
  if (extra) (void)sprintf(new, "%s %s", s1?s1:"", s2?s2:"");
  else (void)sprintf(new, "%s%s", s1?s1:"", s2?s2:"");
  if (deallocate&FIRST) myfree(s1);
  if (deallocate&SECOND) myfree(s2);
  return new;
}

/* ---------------------- end string routines -------------------------- */

/*
 * given a pair of characters - such as [ and ] or ( and ), match them
 * up with one another, and return the closing characters posisition in
 * the string.
 */

char *strip_ends(char *str, char x, char y, char *ret, int len)
{
  int count = 1;
  len--;
  if (*str != x) {
    *ret = 0;
    return NULL;
  } else str++;
  while (*str && count && len) {
    if (*str == '\\') *ret++ = *str++;
    else if (*str == y) count--;
    else if (*str == x) count++;
    if (*str && count) *ret++ = *str++;
    else if (*str) str++;
    len--;
  }
  *ret = 0;
  if (!count) return str;
  return NULL;
}

char *matching_end(char *str, char x, char y)
{
  int count = 1;
  while (*str && count) {
    if (*str == '\\') str++;
    else if (*str == x) count++;
    else if (*str == y) count--;
    if (*str) str++;
  }
  if (count==0) return --str;
  return NULL;
}

/*
 * given a string, return the starting location of the 'num'th word of
 * the string. if the word doesn't exist, a pointer to the end of the
 * string is returned
 */

char *find_word_num(char *str, int num)
{
  if (!str) return NULL;
  while (*str == ' ') str++;
  while (*str && (num > 0))
    if (*str++ == ' ')
      if (*str != ' ') num--;
  return str;
}

/* ---------------------------------------------------------------------- */
static char *f_stack[128];
static int f_stack_ptr = -1;
/* ---------------------------------------------------------------------- */
void set_function_return(char *str)
{
  if (f_stack_ptr<0) return;
  if (f_stack[f_stack_ptr]) myfree(f_stack[f_stack_ptr]);
  f_stack[f_stack_ptr] = m_strcpy(str);
}
/* ---------------------------------------------------------------------- */
int word_count(char *str)
{
  int num = 1;
  while (isspace(*str)) str++;
  if (!*str) return 0;
  while (*str)
    if (isspace(*str++) && !isspace(*str) && *str) num++;
  return num;
}

/*
 * given a string (str) check to see if it matches a builtin alias,
 * or if the value of str is an assigned string. If it is either,
 * copy the equivalent string into new
 */

static char *find_inline(char *str)
{
  char dummy[MAXLEN], *tmp;

  if ((str[0] != 0) && (str[1] == 0) && (check_builtins(str[0], dummy)))
    return m_strcpy(dummy);
  if (lookup_assign_value(str, dummy, sizeof(dummy)))
    return m_strcpy(dummy);
  if ((tmp = getvar_by_name(str)))
    return m_strcpy(tmp);
  return NULL;
}

/*
 * we use max_param when a user requests a list of words such as $4-
 * since we don't know how many words are in the string, we pick an
 * arbitrarily long string
 */

#define MAX_PARAM 80

/*
 * given a starting and ending word number, copy the correct words from
 * str into 'rest'.  assumes 'rest != NULL'
 */

static char *add_numbs(b, e, rest, end)
  char *rest;
  int b, e, end;
{
  char *p, *start, *s1;
  int c = 0, on = 1;
  did_expand = 1;
  s1 = start = find_word_num(rest, b);
  if (!start) return NULL;
  if (end) return m_strcpy(s1);
  while (*start && (b<=e)) {
    if (*start++ != ' ')
      if (*start == ' ') b++;
  }
  return m_strcpy_quote(s1, start);
}

static int isnum(char *str, int *num)
{
  *num = 0;
  if (!str) return FALSE;
  while (*str) {
    if (!isdigit(*str)) return FALSE;
    *num = *num * 10 + *str - 48;
    str++;
  }
  return TRUE;
}

/* --------------------------------------------------------------------- */
static int get_number(char **str)
{
  int neg = FALSE, num = 0;
  char *walk = *str;
  if (*walk=='-') {
    walk++;
    neg = TRUE;
  }
  while (isdigit(*walk)) num = num * 10 + *walk++ - 48;
  *str = walk;
  return (neg)?-num:num;
}

/* ---------------------------------------------------------------------- */
static int call_function(char *s1, char *function_args, char *ret, int retlen,
                         char *args, int *added)
{
  int ddd, indx, len;
  char dummy[MAXLEN], *ack, temp[MAXLEN], *wee;

  retlen--;
  *ret = 0;
  if (!*s1) {
    yell("*** Null routine lookup");
    return 0;
  }
  (void)expand_alias(function_args, args?args:"", added, temp, sizeof(temp));
  if (is_builtin(s1, temp, dummy, sizeof(dummy)))
    len = strlen(dummy);
  else {
    wee = m_strcat(s1, temp, 1, 0);
    f_stack[++f_stack_ptr] = NULL;
    handle_command_line(wee, 0, "");
    if (f_stack[f_stack_ptr]) {
      strcpy(dummy, f_stack[f_stack_ptr]);
      myfree(f_stack[f_stack_ptr]);
    } else *dummy = 0;
    f_stack_ptr--;
/*    lookup_assign_value("FUNCTION_RETURN", dummy, sizeof(dummy)); */
    len = strlen(dummy);
  }
  if (len>retlen) len = retlen;
  (void)strncpy(ret, dummy, len);
  ret[len] = 0;
  if (getivar(VAR_DEBUG) & DEBUG$ROUTINE)
    yell("Function %s(%s) returned %s", s1, function_args, ret);
  return len;
}

/* --------------------------------------------------------------------- */
#define LVL_EXPR  0
#define LVL_ASSN  1
#define LVL_COMP  2
#define LVL_ADD   3
#define LVL_MULT  4
#define LVL_BITW  5
#define LVL_UNIT  6
#define LVL_TERT  7
/* --------------------------------------------------------------------- */
char *myindex(char *str, char find)
{
  while (*str)
    if (find == *str) return str;
    else str++;
  return NULL;
}

/* --------------------------------------------------------------------- */
/* lastop: Given a pointer to an operator, find the last operator in the */
/*  string.  NOTE, this is taken from ircII -- see the copyright         */
/* --------------------------------------------------------------------- */
static char *lastop(char *str)
{
   while (str[1] && myindex("!=<>&^|#+/-*", str[1])) str++;
   return str;
}

/* --------------------------------------------------------------------- */
/* --------------------------------------------------------------------- */
static char *lvl_nonunit(char *str, char *args, int *added, int lvl)
{
  char *ptr, *ptr2, *r1, *r2, *r3, tmp[MAXLEN*10], *lastc, *right;
  long v1, v2, v3, op, f;
  unsigned long uv1, uv2, uv3;

  while (isspace(*str)) str++;
  if (!*str) return m_strcpy("");

  if (lvl == LVL_UNIT)
  {
    lastc = (char *)((int)str + strlen(str) - 1);
    while (isspace(*lastc)) *lastc-- = 0;
    if ((*str == '(') && (*lastc == ')'))
    {
      str++; *lastc-- = 0;
      return lvl_nonunit(str, args, added, LVL_EXPR);
    }
  }
  for (ptr = str; *ptr; ptr++)
  {
    switch (*ptr)
    {
    case '\\':
      if (ptr[1]) ptr++;
      break;
    case LEFT_PAREN:
      if ((lvl != LVL_UNIT) || (ptr == str))
      {
        if ((ptr2 = matching_end(ptr+1, '(', ')'))) ptr = ptr2;
        else ptr = ptr + strlen(ptr) - 1;
        break;
      }
      *ptr++ = 0;
      right = ptr;
      if ((ptr = matching_end(right, LEFT_PAREN, RIGHT_PAREN)) != NULL)
        *ptr++ = 0;
      call_function(str, right, tmp, sizeof(tmp), args, added);
      if (ptr && *ptr)
      {
        r1 = m_strcat(tmp, ptr, 0, 0);
        r2 = lvl_nonunit(r1, args, added, lvl);
        myfree(r1);
        r1 = r2;
      } else r1 = m_strcpy(tmp);
      return r1;
      break;
    case LEFT_BRACKET:
      if (lvl != LVL_UNIT)
      {
        if ((ptr2 = matching_end(ptr+1, '[', ']'))) ptr = ptr2;
        else ptr = ptr + strlen(ptr) - 1;
        break;
      }
      *ptr++ = 0;
      right = ptr;
      if ((ptr = matching_end(right, LEFT_BRACKET, RIGHT_BRACKET)))
        *ptr++ = 0;
      expand_alias(right, args, added, tmp, sizeof(tmp));
      if (*str)
      {
        r2 = (char *)mymalloc(strlen(str)+strlen(tmp)+(ptr?strlen(ptr):0)+2);
        sprintf(r2, "%s.%s%s", str, tmp, ptr?ptr:"");
        if (ptr && *ptr) r1 = lvl_nonunit(r2, args, added, lvl);
        else r1 = find_inline(r2);
        if (!r1) r1 = m_strcpy("");
        myfree(r2);
      } else if (ptr && *ptr) {
        r2 = m_strcat(tmp, ptr, 0, 0);
        r1 = lvl_nonunit(r2, args, added, lvl);
        myfree(r2);
      } else r1 = m_strcpy(tmp);
      if (getivar(VAR_DEBUG)&2048) yell("Brackets: [%s]", r1?r1:"{null}");
      return r1;
      break;
    case '-':
    case '+':
      if (ptr[0] == ptr[1])
      {
        int r;
        char *tptr;
        *ptr++ = 0;
        if (ptr == (str+1)) tptr = str + 2;   /* prefix operation */
        else tptr = str;
        r1 = find_inline(tptr);
        if (!r1) r1 = m_strcpy("0");
        v1 = atol(r1);
        if (*ptr == '+') v1++;
        else v1--;
        sprintf(tmp, "%s %ld", tptr, v1);
        user_assign(tmp, FALSE, NULL);
        if (ptr == (str + 1))
        {
          ptr[-1] = ' ';
          ptr[0] = ' ';
        } else {
          ptr[-1] = (*ptr=='+')?'-':'+';
          ptr[0] = 1;
        }
        ptr = str;
        myfree(r1);
        break;
      }
      if (ptr == str) break; /* No prefix for the operator */
      if (lvl != LVL_ADD) {
        ptr = lastop(ptr);
        break;
      }
      op = *ptr;
      *ptr++ = 0;
      r1 = lvl_nonunit(str, args, added, lvl);
      r2 = lvl_nonunit(ptr, args, added, lvl);
      v1 = atol(r1);
      v2 = atol(r2);
      myfree(r1); myfree(r2);
      if (op=='-') v3 = v1 - v2;
      else v3 = v1 + v2;
      sprintf(tmp, "%ld", v3);
      return m_strcpy(tmp);
      break;
    case '*':
    case '/':
    case '%':
      if (lvl != LVL_MULT)
      {
        ptr = lastop(ptr);
        break;
      }
      op = *ptr;
      *ptr++ = 0;
      r1 = lvl_nonunit(str, args, added, lvl);
      r2 = lvl_nonunit(ptr, args, added, lvl);
      f = 0;
      if (*r1=='-')
        { if (*r2!='-') f = 1; }
      else if (*r2=='-') f = 1;
      uv1 = v1 = atoi(r1);
      uv2 = v2 = atoi(r2);
      uv3 = v3 = 0;
      myfree(r1);
      myfree(r2);
      if (op == '*') { v3 = v1 * v2; uv3 = uv1 * uv2; }
      else if (op == '%') if (v2) { v3 = v1 % v2; uv3 = uv1 % uv2; }
                          else yell("*** Mod by zero");
      else if (op == '/') if (v2) {v3 = v1 / v2; uv3 = uv1 / uv2; }
                          else yell("*** Division by zero");
      if (f) sprintf(tmp, "%ld", v3);
      else sprintf(tmp, "%u", uv3);
      return m_strcpy(tmp);
      break;
    case '#':
      if ((lvl != LVL_ADD) || (ptr[1] != '#'))
      {
         ptr = lastop(ptr);
         break;
      }
      *ptr++ = 0;
      ptr++;
      r1 = lvl_nonunit(str, args, added, lvl);
      r2 = lvl_nonunit(ptr, args, added, lvl);
      if (getivar(VAR_DEBUG) & 2048)
        yell("Append: [%s] [%s]", r1?r1:"null", r2?r2:"null");
      return m_strcat(r1, r2, 0, 3);
      break;
    case '^':
    case '|':
    case '&':
      f = *ptr++;
      if ( ((*ptr == f) && (lvl != LVL_EXPR)) ||
           ((*ptr != f) && (lvl != LVL_BITW)) )
      {
        ptr = lastop(ptr);
        break;
      }
      ptr[-1] = 0;
      if (lvl == LVL_EXPR) ptr++;  /* get past the second operator */
      r1 = lvl_nonunit(str, args, added, lvl);
      v1 = atol(r1);
      r2 = lvl_nonunit(ptr, args, added, lvl);
      v2 = atol(r2);
      if (f=='|')
        if (lvl==LVL_EXPR) v3 = v1 || v2;
        else v3 = v1 | v2;
      else if (f=='&')
        if (lvl==LVL_EXPR) v3 = v1 && v2;
        else v3 = v1 & v2;
      else if (f=='^')
        if (lvl==LVL_EXPR)
        {
          v1 = v1?1:0;
          v2 = v2?1:0;
          v3 = v1 ^ v2;
        }
        else v3 = v1 ^ v2;
      else v3 = 0;
      myfree(r1); myfree(r2);
      sprintf(tmp, "%ld", v3);
      return m_strcpy(tmp);
      break;
    case '?':
      break;
    case '=':
      if (ptr[0] != ptr[1])
      {
         if (lvl != LVL_ASSN)
         {
           ptr = lastop(ptr);
           break;
         }
         *ptr++ = 0;
         r1 = tmp;
         expand_alias(str, args, added, tmp, sizeof(tmp));
         r2 = lvl_nonunit(ptr, args, added, lvl);
         r3 = r1 + strlen(r1) - 1;
         while ((r3>r1) && (*r3 == ' ')) *r3-- = 0;
         for (ptr=r1; *ptr == ' '; ptr++);
         while ((r1 = (char *)strstr(ptr, "[")) != NULL)
         {
           *r1++ = '.';
           if ((r3 = matching_end(r1, '[', ']')) != NULL)
           {
             *r3++ = 0;
             strcat(ptr, r3);
           }
           else break;
         }
         if (*ptr)
         {
           r3 = m_strcat(ptr, r2, 1, 0);
           user_assign(r3, FALSE, NULL);
           myfree(r3);
         } else yell("*** Invalid assignment expression.");
         return r2;
      }
      if (lvl != LVL_COMP) {
        ptr = lastop(ptr);
        break;
      }
      *ptr++ = 0;
      ptr++;
      r1 = lvl_nonunit(str, args, added, lvl);
      r2 = lvl_nonunit(ptr, args, added, lvl);
      if (strcasecmp(r1, r2) == 0)
        r3 = m_strcpy("1");
      else
        r3 = m_strcpy("0");
      myfree(r1);
      myfree(r2);
      return r3;
      break;
    case '<':
    case '>':
      if (lvl != LVL_COMP) {
        ptr = lastop(ptr);
        break;
      }
      op = *ptr;
      if (ptr[1]=='=') {
        v3 = 1;
        *ptr++ = 0;
      } else v3 = 0;
      *ptr++ = 0;
      r1 = lvl_nonunit(str, args, added, lvl);
      r2 = lvl_nonunit(ptr, args, added, lvl);
      if (isdigit(*r1) && isdigit(*r2))
      {
        v1 = atol(r1);
        v2 = atol(r2);
        v1 = (v1==v2)?0:((v1<v2)?-1:1);
      } else v1 = strcasecmp(r1, r2);
      if (v1)
        if (op=='<') v2 = (v1 == -1);
        else v2 = (v1 == 1);
      else v2 = v3;
      myfree(r1);
      myfree(r2);
      sprintf(tmp, "%ld", v2);
      return m_strcpy(tmp);
      break;
    case '~':
      break;
    case '!':
      if ((lvl != LVL_COMP) || (ptr[1] != '='))
      {
        ptr = lastop(ptr);
        break;
      }
      *ptr++ = 0;
      ptr++;
      r1 = lvl_nonunit(str, args, added, lvl);
      r2 = lvl_nonunit(ptr, args, added, lvl);
      if (strcasecmp(r1, r2) == 0) r3 = m_strcpy("0");
      else r3 = m_strcpy("1");
      myfree(r1); myfree(r2);
      return r3;
      break;
    case ',':
      break;
    }
  }

  if (lvl != LVL_UNIT)
  {
    ptr = lvl_nonunit(str, args, added, lvl+1);
    return ptr;
  }
  if (isdigit(*str) || (*str == '+') || (*str == '-'))
    return m_strcpy(str);
  if ((*str == '#') || (*str == '@')) op = *str++;
  else op = 0;
  r1 = find_inline(str);  /* Lookup function? */
  if (!r1) r1 = m_strcpy("");
  if (op)
  {
    if (op=='#') v1 = word_count(r1);
    else v1 = strlen(r1);
    myfree(r1);
    sprintf(tmp, "%ld", v1);
    r1 = m_strcpy(tmp);
  }
  return r1;
}

/* --------------------------------------------------------------------- */
char *parse_inline(char *str, char *args, int *added)
{
  char *ret, *tmp;
  int old;
  tmp = m_strcpy(str);
  if (getivar(VAR_DEBUG) & DEBUG$EXPAND)
    yell("Expand: \002%s\002", str);
  ret = lvl_nonunit(tmp, args, added, 0);
  if (getivar(VAR_DEBUG) & DEBUG$EXPAND)
    yell("Expanded to: \002%s\002", ret?ret:"(null)");
  myfree(tmp);
  return ret;
}
 
/* -----------------------------------------------------------------------
 * real = the original string
 * args = the argumnts to the alias
 * ret = where we right our return string
 * added = did we used on of the special characters
 * ----------------------------------------------------------------------- */
static int depth = 0;

/* alias_illegals: characters that are illegal in alias names */
static char alias_illegals[] = " #+-*/\\()={}[]<>!@$%^~`,?;:|'\"";

char *alias_special(char *ptr, char *args, int *added, char *buffer, int len)
{
  int lower, upper, width, real, end = 0, did_width = 0;
  char *t1, *temp, saved[MAXLEN], saved2[MAXLEN], saved3[MAXLEN], *s;
  char olds, c;

  *buffer =0;
  width = 0;
  if ((c = *ptr) == '[')  /* A width specification */
  {
    if ((temp = (char *)strstr(++ptr, "]")) == NULL) {
      yell("*** Missing ]");
      return ptr;
    }
    *temp++ = 0;
    width = atol(ptr);
    did_width = 1;
    ptr = temp;
    c = *ptr;
  }
  temp = (char *)((int)ptr + 1);
  switch (c)
  {
  case LEFT_PAREN:
    *added = TRUE;
    if ((ptr = matching_end(ptr, LEFT_PAREN, RIGHT_PAREN)) ||
        (ptr = myindex(temp, RIGHT_PAREN)))
      *ptr++ = 0;
    expand_alias(temp, args, added, saved3, sizeof(saved3));
    alias_special(saved3, args, added, buffer, len);
    if (width && (strlen(buffer) > width)) buffer[width] = 0;
    return ptr;
    break;
  case LEFT_BRACE:
    if ((ptr = (char *)strstr(ptr, "}")) != NULL) *ptr++ = 0;
    if ((temp = parse_inline(temp, args, added)) != NULL)
    {
      strcpy(buffer, temp);
      myfree(temp);
    }
    if (width && (strlen(buffer) > width)) buffer[width] = 0;
    return ptr;
    break;
  case '*':
    *added = TRUE;
    strcpy(buffer, args);
    if (width && (strlen(buffer) > width)) buffer[width] = 0;
    return (ptr+1);
    break;
  case '$':
    strcpy(buffer, "$");
    if (width && (strlen(buffer) > width)) buffer[width] = 0;
    return (ptr+1);
    break;
  default:
    if (isdigit(c) || (c == '-') || (c == '~'))
    {
     
      *added = TRUE;
      if (*ptr == '~') {
        lower = upper = word_count(args);
        ptr++;
      } else {
        if (*ptr == '-') lower = 0;
        else lower = get_number(&ptr);
        if (*ptr=='-') {
          ptr++;
          if (isdigit(*ptr)) upper = get_number(&ptr);
          else end = TRUE;
        } else upper = lower;
      }
      if ((t1 = add_numbs(lower, upper, args, end))) {
        (void)ff_strncpy(buffer, t1, strlen(t1), len);
        myfree(t1);
      }
      if (width && (strlen(buffer) > width)) buffer[width] = 0;
      return ptr;
    } else {
      int loop;
      char *rest, *tmp, c = 0, *t1;

#if 0
      if ((rest = (char *)strpbrk(ptr+1, alias_illegals)) != NULL)
#else
      rest = ptr+1;
      while ((*rest >= 32) && !strchr(alias_illegals, *rest)) rest++;
      if (*rest)
#endif
      {
        if (isalpha(*ptr) || (*ptr == '_'))
          while (((*rest == LEFT_BRACKET) || (*rest == LEFT_PAREN)) &&
                 (tmp = matching_end(rest+1, *rest,
                                (*rest==LEFT_PAREN)?RIGHT_PAREN:RIGHT_BRACKET)))
            rest = tmp + 1;
         c = *rest;
         *rest = 0;
      }
      if ((t1 = parse_inline(ptr, args, added)) != NULL)
      {
        char *tbuff = buffer;
        if (did_width && (width > 0))
          for (loop=0; loop < (int)(width-strlen(t1)); loop++, len--) {
            *tbuff++ = ' ';
          }
        ff_strncpy(tbuff, t1, strlen(t1), len);
        myfree(t1);
      }
      if (rest) *rest = c;
      if (width && (strlen(buffer) > width)) buffer[width] = 0;
      if (did_width && (width < 0))
      {
        for (loop=strlen(buffer); loop<-width; loop++) buffer[loop] = ' ';
        if (loop == -width) buffer[loop] = 0;
      }
      return rest;
    }  /* End of default case for not a number */
    break;
  }
  if (width && (strlen(buffer) > width)) buffer[width] = 0;
  return NULL;
}
