/*
 * $Id$
 *
 * $Log$
 * Revision 1.2  1998/11/10 13:14:11  couet
 * - Y2K
 *
 * Revision 1.1.1.1  1996/03/08 15:32:55  mclareni
 * Kuip
 *
 */
/*CMZ :  2.07/05 01/06/95  16.59.02  by  Gunter Folger*/
/*-- Author :    Alfred Nathaniel   18/11/92*/
/* kalias.c: alias translations */

#include "kuip/kuip.h"
#include "kuip/kfor.h"
#include "kuip/kcom.h"
#include "kuip/klink.h"
#include "kuip/kmenu.h"
#include "kuip/kflag.h"
#include "kuip/khash.h"
#include "kuip/kalias.h"


static char* repl_alias( const char* );
static char* sys_cptime(void);
static char* sys_date(void);
static char* sys_rtime(void);
static char* sys_time(void);


/*
 * action routine for /KUIP/ALIAS/CREATE
 */
int kxacreate()
{
  char *name = ku_gets();
  char *value = ku_gets();
  char *option = ku_getc();

  if( name[0] == '\0' || len_alias( name, '\0' ) != strlen( name ) ) {
    ku_alfa();
    printf( " *** Invalid alias name '%s'\n", name );
    return -1;
  }

  kc_alias.translate = 1;       /* translation on */

  if( strchr( option, 'C' ) != NULL ) {
    char *alias = hash_lookup( kc_alias.arg_table, name, NULL );
    if( alias != NULL ) {
      ku_alfa();
      printf( " *** Argument alias '%s' superseded by command alias\n", name );
      hash_remove( kc_alias.arg_table, name );
    }
    hash_insert( kc_alias.cmd_table, name, value, 0 );
  }
  else {
    char *alias = hash_lookup( kc_alias.cmd_table, name, NULL );
    if( alias != NULL ) {
      ku_alfa();
      printf( " *** Command alias '%s' superseded by argument alias\n", name );
      hash_remove( kc_alias.cmd_table, name );
    }
    /* apply alias translations in value unless inhibited by option N */
    if( strchr( option, 'N' ) == NULL ) {
      char *p;
      if( (p = subst_arg_alias( value )) != NULL ) {
        hash_insert( kc_alias.arg_table, name, p, 0 );
        free( p );
        return 0;
      }
      else if( kc_alias.substitutions <= 0 ) {
        /* recursive alias */
        hash_remove( kc_alias.arg_table, name );
        return -1;
      }
    }
    hash_insert( kc_alias.arg_table, name, value, 0 );
  }

  return 0;
}


/*
 * action routine for /KUIP/ALIAS/TRANSLATION
 */
int kxatranslation()
{
  char *option = ku_getc();

  if( strcmp( option, "ON" ) == 0 )
    kc_alias.translate = 1;

  else if( strcmp( option, "OFF" ) == 0 )
    kc_alias.translate = 0;

  else
    printf( " Current alias translation: %s\n",
           kc_alias.translate ? "ON" : "OFF" );

  return 0;
}


/*
 * action routine for /KUIP/ALIAS/DELETE
 */
int kxadelete()
{
  char *pattern = ku_getc();
  int n = hash_entries( kc_alias.cmd_table );

  if( n > 0 ) {
    HashArray *values = hash_array( kc_alias.cmd_table );
    int i;

    for( i = 0; i < n; i++ ) {
      char *name = values[i].name;

      if( ku_match( name, pattern, 1 ) ) {
        hash_remove( kc_alias.cmd_table, name );
      }
    }
    free( (char*)values );
  }

  if( (n = hash_entries( kc_alias.arg_table )) > 0 ) {
    HashArray *values = hash_array( kc_alias.arg_table );
    int i;

    for( i = 0; i < n; i++ ) {
      char *name = values[i].name;

      if( ku_match( name, pattern, 1 ) ) {
        hash_remove( kc_alias.arg_table, name );
      }
    }
    free( (char*)values );
  }

  return 0;
}


/*
 * action routine for /KUIP/ALIAS/LIST
 */
int kxalist()
{
  char *pattern = ku_getc();
  static char *header[] = { "Argument", "Command" };
  HashTable *table[2];
  int k;

  table[0] = kc_alias.arg_table;
  table[1] = kc_alias.cmd_table;

  for( k = 0; k < 2; k++ ) {
    int n = hash_entries( table[k] );
    if( n == 0 ) {
      printf( " No %s aliases defined.\n", header[k] );
    }
    else {
      HashArray *alias = hash_array( table[k] );
      int i;

      printf( " %s aliases:\n", header[k] );
      for( i = 0; i < n; i++ ) {
        char *name = alias[i].name;
        char *value = (char*)alias[i].value;

        if( ku_match( name, pattern, 1 ) ) {
          printf( " %-10s => %s\n", name, value );
        }
      }
      free( (char*)alias );
    }
  }
  return 0;
}


/*
 * return the number of leading characters which are valid for an alias name
 */
int len_alias( const char *line,
              int line_1 )
{
  char *sep = " /,<=>:;.%'()";
  int len = 0;

  /*
   * An alias name must be preceeded (line_1) and followed by one of the
   * separator characters or '\0'.
   */
  if( strchr( sep, line_1 ) != NULL ) {

    while( line[len] != '\0'
          && ( isalnum( line[len] )
              || strchr( "_@-$", line[len] ) != NULL ) ) {
      len++;
    }

    if( strchr( sep, line[len] ) == NULL )
      len = 0;
  }
  return len;
}


/*
 * if line contains a command alias substitute it
 * and return the allocated result
 * otherwise return NULL
 */
char *subst_cmd_alias( line )
     char *line;
{
  if( kc_alias.translate && kc_alias.substitutions > 0 ) {
    char *cmd;
    char *args;
    char *p;

    while( *line == ' ' )
      line++;

    cmd = strdup( line );

    /* separate command name from argument list  */
    if( (p = strchr( cmd, ' ' )) != NULL ) {
      *p = '\0';
      for( args = p + 1; *args == ' '; )
        args++;
    }
    else
      args = "";

    if( (p = hash_lookup( kc_alias.cmd_table, cmd, NULL )) != NULL
       || (p = hash_lookup( kc_alias.arg_table, cmd, NULL )) != NULL ) {

      char *new_line = strdup( p );

      if( *args != '\0' )
        new_line = mstr2cat( new_line, " ", args );

      if( --kc_alias.substitutions <= 0 ) {
        ku_alfa();
        printf( " *** Recursive command alias in %s\n", new_line );
      }

      free( cmd );
      return new_line;
    }
    free( cmd );
  }
  return NULL;
}


/*
 * if line contains argument aliases substitute them
 * and return the allocated result
 * otherwise return NULL
 */
char *subst_arg_alias( line )
     char *line;
{
  char *p;
  if( kc_alias.translate
     && (p = repl_alias( line )) != NULL ) {
    char *new_line = p;

    kc_alias.substitutions = 100;

    while( --kc_alias.substitutions > 0 ) {

      if( (p = repl_alias( new_line )) == NULL )
        return new_line;

      free( new_line );
      new_line = p;
    }
    ku_alfa();
    printf( " *** Recursive argument alias in %s\n", line );
    free( new_line );
  }
  return NULL;
}


static
char *repl_alias( const char *line )
{
  const char *p;

  for( p = line; *p != '\0'; p++ ) {
    int len = len_alias( p, (p == line) ? '\0' : p[-1] );

    /* isolate the possible alias name */
    if( len > 0 ) {
      char *name = strndup( p, len );
      char *value = hash_lookup( kc_alias.arg_table, name, NULL );

      free( name );
      if( value != NULL ) {
        /* substitute the alias name by its value */
        int lhead = p - line;

        /* contract ABC//DEF to ABCDEF in case that is an alias */
        if( lhead > 2 && p[-1] == '/' && p[-2] == '/'
           && p[-3] != '\'' && p[-3] != ' ' )
          lhead -= 2;

        return mstr2cat( strndup( line, lhead ), value, p + len );
      }
      p += len - 1;
    }
    else if( *p == '\'' ) {
      /* skip until end of quoted string */
      while( *++p != '\'' ) {
        if( *p == '\0' )
          return NULL;
      }
    }
  }
  return NULL;
}


/*
 * return value of a variable "[var]"
 */
char *var_value( const char *var )
{
  int n = strlen( var );
  char *name = strndup( var + 1, n - 2 );
  char *value;
  char *p;

  if( name[0] == '%' ) {
    /* replace "[%var]" by "[[var]]" */
    name[0] = '[';
    name = mstrcat( name, "]" );
  }

  if( (p = subst_var_alias( name )) != NULL ) {
    free( name );
    name = p;
  }

  value = hash_lookup( kc_alias.var_table, name, NULL );

  if( value == NULL ) {
    int i;
    for( i = 0; name[i] != '\0'; i++ ) {
      if( !isdigit( name[i] ) ) {
        free( name );
        return strdup( var );
      }
    }
    /* undefined "[n]" are replaced by "' '" */
    value = " ";
  }

  free( name );

  return strdup( value );
}


char *subst_var_alias( line )
     char *line;
{
  static int subst_calls=0;
  char *p;

  subst_calls++;
  if ( subst_calls > 100 ) {
     ku_alfa();
     printf(" Recursive variable assignment stopped in line \n %s\n",line);
     return(NULL);
  }

  if( (p = repl_variable( line )) != NULL ) {
    char *new_line = p;

    while( (p = repl_variable( new_line )) != NULL ) {
      free( new_line );
      new_line = p;
    }
    subst_calls--;
    return new_line;
  }
  subst_calls--;
  return NULL;
}


/*
 * replace a variable inside line by its value
 */
char *repl_variable( line )
     char *line;
{
  char *p;

  for( p = line; *p != '\0'; p++ ) {

    if( *p == '[' ) {
      int paren = 1;
      char *name_end = p + 1;

      while( *name_end != '\0' ) {

        if( *name_end == '[' ) {
          paren++;
        }
        else if( *name_end == ']' ) {

          if( --paren == 0 ) {
            int len = name_end - p + 1;
            char *name = strndup( p, len );
            char *value = var_value( name );

            if( strcmp( name, value ) != 0 ) {
              char *new_line = strndup( line, p - line );
              value = quote_string( value, 0 );
              new_line = mstr2cat( new_line, value, p + len );
              free( name );
              free( value );
              return new_line;
            }
            free( name );
            free( value );
            break;
          }
        }
        name_end++;
      }
    }
    else if( *p == '\'' ) {
      /* skip until end of quoted string */
      while( *++p != '\'' ) {
        if( *p == '\0' )
          return NULL;
      }
    }
  }
  return NULL;
}


/*
 * put quotes around string and duplicate quotes inside string
 * input string is supposed to be allocated
 * if quote flag is set the string is quoted anyway
 * otherwise it is quoted only if necessary
 */
char *quote_string( char *string,
                   int quote )
{
  if( string[0] == '\0' ) {
    free( string );
    return strdup( "''" );
  }

  else {
    int n;

    if( string[0] == '\'' && (n = strlen( string )) > 1
       && string[n-1] == '\'' )
      return string;
  }

  if( !quote )
    quote = (strpbrk( string, " '" ) != NULL);

  if( !quote )
    return string;

  else {
    char *line = strdup( "'" );
    char *p;

    for( p = string; *p != '\0'; p++ ) {
      if( *p == '\'' )
        line = mstrcat( line, "''" );
      else
        line = mstrccat( line, *p, 1 );
    }
    line = mstrcat( line, "'" );
    free( string );
    return line;
  }
}


enum SysFunCode {
  SysANUM,
  SysARGS,
  SysCPTIME,
  SysDATE,
  SysKEYNUM,
  SysKEYVAL,
  SysLAST,
  SysMACHINE,
  SysNUMVEC,
  SysOS,
  SysPID,
  SysRTIME,
  SysSTYLE,
  SysTIME
};

static struct sysfun {
  enum SysFunCode code;
  char *name;
} sysfun[] = {
  { SysANUM,      "ANUM"     },
  { SysARGS,      "ARGS"     },
  { SysCPTIME,    "CPTIME"   },
  { SysDATE,      "DATE"     },
  { SysKEYNUM,    "KEYNUM"   },
  { SysKEYVAL,    "KEYVAL"   },
  { SysLAST,      "LAST"     },
  { SysMACHINE,   "MACHINE"  },
  { SysNUMVEC,    "NUMVEC"   },
  { SysOS,        "OS"       },
  { SysPID,       "PID"      },
  { SysRTIME,     "RTIME"    },
  { SysSTYLE,     "STYLE"    },
  { SysTIME,      "TIME"     }
};

/*
 * substitute system function
 * if eval flag is set functions with arguments are sent to expression parser
 */
char *subst_sysfun( line, eval )
     char *line;
     int eval;
{
  char *p;
  if( (p = repl_sysfun( line, eval )) != NULL ) {
    char *new_line = p;

    while( (p = repl_sysfun( new_line, eval )) != NULL ) {
      free( new_line );
      new_line = p;
    }
    return new_line;
  }
  return NULL;
}


/*
 * return the number of leading characters which are valid for a function name
 * line does not include the leading '$'
 */
int len_sysfun( const char *line )
{
  int len = 0;

  if( !isdigit( line[0] ) ) {
    while( line[len] != '\0'
          && ( isalnum( line[len] )
              || strchr( "_", line[len] ) != NULL ) ) {
      len++;
    }
  }
  return len;
}


char *repl_sysfun( line, eval )
     char *line;
     int eval;
{
  char *p;

  for( p = line; *p != '\0'; p++ ) {

    if( *p == '$' ) {
      int len = len_sysfun( p + 1 );

      /* protect against DISK$USER:[dir] etc. */
      if( len > 0 && p[len+1] != ':' ) {
        int i;
        char *value = NULL;

        /* test for system functions without arguments */
        for( i = 0; i < (sizeof sysfun) / sizeof( struct sysfun ); i++ ) {

          if( len == strlen( sysfun[i].name )
             && strncasecmp( p + 1, sysfun[i].name, len ) == 0 ) {

            switch( sysfun[i].code ) {

            case SysANUM:       /* number of aliases */
              value = malloc( 16 );
              sprintf( value, "%d", hash_entries( kc_alias.arg_table ) );
              break;

            case SysARGS:       /* program arguments */
              value = strdup( k_getar() );
              break;

            case SysCPTIME:     /* CPU time since last call */
              value = sys_cptime();
              break;

            case SysDATE:       /* current date */
              value = sys_date();
              break;

            case SysKEYNUM:
              value = strdup( (kc_window.panel_keynum != NULL)
                             ? kc_window.panel_keynum : "0.00" );
              break;

            case SysKEYVAL:
              value = strdup( (kc_window.panel_keyval != NULL)
                             ? kc_window.panel_keyval : "' '" );
              break;

            case SysLAST:
              if( kc_flags.last_cmd == NULL )
                value = strdup( "" );
              else
                value = strdup( kc_flags.last_cmd );
              /* avoid infinite recursion if last command contains $LAST */
              value = quote_string( value, 1 );
              break;

            case SysMACHINE:
              value = strdup( MACHINE_NAME );
              break;

            case SysNUMVEC:
              value = malloc( 16 );
              sprintf( value, "%d", (int)Kcvect.NUMVEC - 1 );
              break;

            case SysOS:
              value = strdup( OS_NAME );
              break;

            case SysPID:
              value = malloc( 16 );
              sprintf( value, "%d", (int)getpid() );
              break;

            case SysRTIME:      /* real time since last call */
              value = sys_rtime();
              break;

            case SysSTYLE:
              value = strdup( style_name( kc_flags.style ) );
              break;

            case SysTIME:
              value = sys_time();
              break;
            }
          }
        }

        /* test for system functions with arguments */
        if( value == NULL && eval && p[len+1] == '(' ) {
          int arglen = match_paren( p );

          if( arglen > 0 ) {
            char *expr = strndup( p, arglen );
            int is_eval = (strncasecmp( p + 1, "EVAL", len ) == 0);
            int is_unquote = (strncasecmp( p + 1, "UNQUOTE", len ) == 0);

            if( is_eval || is_unquote ) {
              expr[arglen-1] = '\0';
	      /* expr = strcpy( expr, expr + len + 2 );  */
	      expr = memmove( expr, expr + len + 2, strlen(expr + len + 2) + 1 );
            }
            value = ku_eval( expr );
            free( expr );
            if( value != NULL ) {
              if( !is_unquote )
                value = quote_string( value, 0 );
              len = arglen - 1;
            }
          }
        }

        if( value == NULL && kc_flags.try_getenv ) {
          /* try $var as environment variable */
          char *name = strndup( p + 1, len );
          char *env = getenv( name );
          /*
           * Note that on VMS getenv searches uppercase names first in the
           * logical names tables and then in the symbol table.
           * Lowercase names are searched for only in the symbol table.
           * Ignore value starting with $ or @ which are most probable
           * command abbreviations.
           */
          free( name );
#ifdef vms
          if( env != NULL && env[0] != '$' && env[0] != '@' )
            value = strdup( env );
#else
          if( env != NULL )
            value = strdup( env );
#endif
        }
        if( value != NULL ) {
          char *new_line = strndup( line, p - line );
          new_line = mstr2cat( new_line, value, p + len + 1 );
          free( value );
          return new_line;
        }
      }
    }
    else if( *p == '\'' ) {
      while( *++p != '\'' ) {
        if( *p == '\0' )
          return NULL;
      }
    }
  }
  return NULL;
}


/*
 * find the distance to the next balanced ')'
 */
int match_paren( const char *line )
{
  int len;
  int paren = 0;

  for( len = 0; line[len] != '\0'; len++ ) {

    switch( line[len] ) {

    case '(':
      paren++;
      break;

    case ')':
      paren--;
      if( paren == 0 )
        return len + 1;
      break;

    case '\'':
      while( line[++len] != '\'' ) {
        if( line[len] == '\0' )
          return 0;
      }
      break;

    }
  }
  return 0;
}


/*
 * return value for $CPTIME
 */
static
char *sys_cptime()
{
  static int initialized = 0;
  static clock_t time0;
  clock_t time_now;
  char *value = malloc( 16 );

  if( !initialized ) {
    initialized = 1;
    time0 = clock();
  }
  time_now = clock();
  sprintf( value, "%.3f", (double)(time_now - time0) / CLOCKS_PER_SEC );
  time0 = time_now;
  return value;
}


/*
 * return value for $RTIME
 */
static
char *sys_rtime()
{
  static int initialized = 0;
  static time_t time0;
  time_t time_now;
  char *value = malloc( 16 );

  if( !initialized ) {
    initialized = 1;
    time0 = time( NULL );
  }
  time_now = time( NULL );
  sprintf( value, "%d", (int)difftime( time_now, time0) );
  time0 = time_now;
  return value;
}


/*
 * return value of $DATE
 */
static
char *sys_date()
{
  time_t time_now = time( NULL );
  struct tm *tms = localtime( &time_now );
  char *value = malloc( 16 );

  sprintf( value, "%s/%s/%s",
          strfromi( tms->tm_mday, 2 ), strfromi( tms->tm_mon + 1, 2 ),
          strfromi( tms->tm_year+1900, 4 ) );
  return value;
}


/*
 * return value of $TIME
 */
static
char *sys_time()
{
  time_t time_now = time( NULL );
  struct tm *tms = localtime( &time_now );
  char *value = malloc( 16 );

  sprintf( value, "%s.%s.%s",
          strfromi( tms->tm_hour, 2), strfromi( tms->tm_min, 2 ),
          strfromi( tms->tm_sec, 2 ) );
  return value;
}


/*
 * return commands to restore current environment
 */
void F77_ENTRY_C(Kuqenv,chline)
/* { */
  static char **env = NULL;
  static int next = 0;
  char *line = fstr0dup( chline, len_chline );

  if( line == NULL ) {
    env = ku_qenv();
    next = 0;
  }
  else
    free( line );

  if( env == NULL || env[next] == NULL )
    fstrset( chline, len_chline, "" );
  else
    fstrset( chline, len_chline, env[next++] );
}


char **ku_qenv()
{
  static char **env = NULL;
  char numbers[64];
  int nenv = 0;
  int k;

  if( env != NULL ) {
    int n;
    for( n = 0; env[n] != NULL; n++ )
      free( env[n] );
    free( (char*)env );
  }
  env = (char**)malloc( 64 * sizeof(char*) );

  env[nenv++] = str3dup( "/KUIP/SET_SHOW/BREAK '",
                        kc_value.set_break, "'" );

  env[nenv++] = str2dup( "/KUIP/SET_SHOW/COLUMNS ",
                        kc_value.set_columns );

  if( kc_value.set_command != NULL ) {
    env[nenv++] = str3dup( "/KUIP/SET_SHOW/COMMAND '",
                          kc_value.set_command, "'" );
  }

  env[nenv++] = str3dup( "/KUIP/SET_SHOW/DOLLAR '",
                        kc_value.set_dollar, "'" );

  env[nenv++] = str3dup( "/KUIP/SET_SHOW/FILECASE '",
                        kc_value.set_filecase, "'" );

  env[nenv++] = str3dup( "/KUIP/SET_SHOW/HOST_EDITOR '",
                        kc_value.set_host_editor, "'" );

  sprintf( numbers, "%d %d %d %d",
          kc_window.edit_pad.top,
          kc_window.edit_pad.left,
          kc_window.edit_pad.width,
          kc_window.edit_pad.height );
  env[nenv++] = str2dup( "/KUIP/SET_SHOW/HOST_EDITOR 'WINDOW' ", numbers );

  sprintf( numbers, "%d %d %d %d %d %d %d",
          kc_window.help_pad.top,
          kc_window.help_pad.left,
          kc_window.help_pad.width,
          kc_window.help_pad.height,
          kc_window.hoffset,
          kc_window.voffset,
          kc_window.shift_max );
  env[nenv++] = str2dup( "/KUIP/SET_SHOW/HOST_EDITOR 'PAD' ", numbers );

  env[nenv++] = str3dup( "/KUIP/SET_SHOW/HOST_PAGER '",
                        kc_value.set_host_pager, "'" );

  if( kc_value.set_print_cmds != NULL ) {
    int i;
    for( i = 0; kc_value.set_print_cmds[i] != NULL; i++ ) {
      env[nenv++] = str4dup( "/KUIP/SET_SHOW/HOST_PRINTER '",
                            kc_value.set_print_cmds[i], "' ",
                            kc_value.set_print_exts[i] );
    }
  }

  env[nenv++] = str3dup( "/KUIP/SET_SHOW/HOST_PSVIEWER '",
                        kc_value.set_host_psviewer, "'" );

  env[nenv++] = str3dup( "/KUIP/SET_SHOW/HOST_SHELL '",
                        kc_value.set_host_shell, "'" );

  env[nenv++] = str3dup( "/KUIP/SET_SHOW/PROMPT '",
                        kc_value.set_prompt, "'" );

  env[nenv++] = str3dup( "/KUIP/SET_SHOW/RECALL_STYLE '",
                        kc_value.set_recall_style, "'" );

  env[nenv++] = str2dup( "/KUIP/SET_SHOW/RECORDING ",
                        kc_value.set_recording );

  env[nenv++] = str2dup( "/KUIP/SET_SHOW/ROOT ",
                        kc_value.set_root );

  env[nenv++] = str3dup( "/KUIP/SET_SHOW/STYLE '",
                        kc_value.set_style, "'" );

  env[nenv++] = str3dup( "/KUIP/SET_SHOW/TIMING '",
                        kc_value.set_timing, "'" );

  env[nenv++] = str2dup( "/KUIP/HELP ",
                        kc_value.help_edit );

  env[nenv++] = str4dup( "/MACRO/DEFAULTS '",
                        kc_value.defaults_path, "' ",
                        kc_value.defaults_order );

  for( k = 0; k < 2; k++ ) {
    HashArray *alias;
    int nalias;
    char *opt;
    int i;

    if( k == 0 ) {
      nalias = hash_entries( kc_alias.arg_table );
      alias = hash_array( kc_alias.arg_table );
      opt = "'";
    }
    else {
      nalias = hash_entries( kc_alias.cmd_table );
      alias = hash_array( kc_alias.cmd_table );
      opt = "' 'C'";
    }

    if( nalias > 0 ) {
      env = (char**)realloc( (char*)env, (nenv + nalias + 1) * sizeof(char*) );

      for( i = 0; i < nalias; i++, nenv++ ) {
        char *value = strdup( (char*)alias[i].value );
        char *p;

        env[nenv] = str3dup( "/KUIP/ALIAS/CREATE '", alias[i].name, "' '" );
        while( (p = strchr( value, '\'' )) != NULL ) {
          *p = '\0';
          env[nenv] = mstr2cat( env[nenv], value, "''" );
          strcpy( value, p + 1 );
        }
        env[nenv] = mstr2cat( env[nenv], value, opt );
        free( value );
      }
      free( (char*)alias );
    }
  }
  env[nenv] = NULL;
  return env;
}
