#define module_name	VERB
#define module_ident	"V2.4"
/* verb.c

	This is the main entry point for the VERB utility.

   V2.4		Stephen Hoffman		11-Mar-2004
        Tweaked the parameter passing for verb_init_cli,
        as it appears to have been incorrect -- while
        the cause is unclear, rebuilds of V2.3 started
        showing run-time command table format errors.

   V2.3		Stephen Hoffman		30-APR-2003
        Removed requirements for use of /STANDARD=VAXC, 
        switched to the cli$routines.h declarations, and
        miscellaneous updates and corrections.

   V2.2		Hunter Goatley		31-AUG-1995 12:46
	Added bug fix to VERB_ENTITY.C from Derek Dongray.
	Added arbitrary version number to keep track of versions.

   V2.1		Luke Brennan		??-AUG-1995
	Modified to compile with DEC C on VAX; fixed bug.

*/

#pragma module module_name module_ident

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <cli$routines.h>
#include <lib$routines.h>
#include <lnmdef.h>
#include <ots$routines.h>
#include <psldef.h>
#include <starlet.h>
#include <strdef.h>
#include <str$routines.h>
#include <stsdef.h>

/*
//  Load the verb data structures and the verb-specific extern declarations.
*/
#include "verb.h"

/*
//  Pull in the address of the VERB utility's command tables and the address
//  of the OpenVMS CLITABLES (for DCL, hopefully) for the current process.
*/
extern void *VERB_CLD;
extern VectorBlock *ctl$ag_clitable;

static int do_verbs( VectorBlock *, char *, int, int );
static int set_logical( struct dsc$descriptor_s *, string * );
static int print_verb( VectorBlock *, CommandBlock *, int );

typedef struct ITEM_LIST_2
{
  unsigned short buflen;
  unsigned short item_code;
  unsigned long *bufadr;
} item_list;

main(int argc, char *argv[])
{
  static $DESCRIPTOR(p1,"P1");
  static $DESCRIPTOR(q_all,"ALL");
  static $DESCRIPTOR(q_process,"PROCESS");
  static $DESCRIPTOR(q_table,"TABLE");
  static $DESCRIPTOR(q_output,"OUTPUT");
  static $DESCRIPTOR(q_width,"WIDTH");
  static $DESCRIPTOR(q_image, "IMAGE");
  static $DESCRIPTOR(q_symbol, "SYMBOL");
  static $DESCRIPTOR(q_list, "LIST");
  static $DESCRIPTOR(q_file, "FILE");
  static $DESCRIPTOR(q_count, "COUNT");
  static $DESCRIPTOR(load_image, "VERB_TABLE");
  static $DESCRIPTOR(load_symbol, "DCL$AL_TAB_VEC");
  static DYNAMIC_STRING(result);
  static DYNAMIC_STRING(file);
  static DYNAMIC_STRING(symbol);
  VectorBlock *cmd_table;
  char filename[255];
  int status;

  status = verb_init_cli( &VERB_CLD, "VERB");

  if ($VMS_STATUS_SUCCESS(cli$get_value(&q_output, &result)) )
  {
    strncpy(filename, SPTR(result), SLEN(result));
    filename[SLEN(result)] = '\0';

    freopen(filename, "w", stdout, "dna=.CLD", "rfm=var", "rat=cr");
  }

  if ($VMS_STATUS_SUCCESS( cli$present(&q_table) ))
  {
    cli$get_value(&q_table, &result);

    status = set_logical(&load_image, &result);
    if (!$VMS_STATUS_SUCCESS( status )) return status;

    status = lib$find_image_symbol(&load_image, &load_symbol, &cmd_table);
    if (!$VMS_STATUS_SUCCESS( status )) return status;
  }
  else
  {
    if ($VMS_STATUS_SUCCESS(cli$present(&q_image)) )
    {
      cli$get_value(&q_image, &result);

      status = set_logical(&load_image, &result);
      if (!$VMS_STATUS_SUCCESS( status )) return status;

      cli$get_value(&q_symbol, &result);
      status = lib$find_image_symbol(&load_image, &result, &cmd_table);
      if (!$VMS_STATUS_SUCCESS( status )) return status;
    }
    else
    {
      if ($VMS_STATUS_SUCCESS(cli$present(&q_file)) )
      {
        int count = 1;

        if ( $VMS_STATUS_SUCCESS(cli$present(&q_count)) )
        {
          status = cli$get_value(&q_count, &result);
          if (!$VMS_STATUS_SUCCESS( status )) return status;

          status = ots$cvt_ti_l(&result, &count);
          if (!$VMS_STATUS_SUCCESS( status )) return status;
        }
        cli$get_value(&q_file, &result);

        status = verb_load_file(&result, &cmd_table, count);
        if (!$VMS_STATUS_SUCCESS( status )) return status;
      }
      else
      { /* assume /process */
        cmd_table = (VectorBlock *)ctl$ag_clitable;
      }
    }
  }

  if ( $VMS_STATUS_SUCCESS(cli$present(&q_list)) )
  { /* all we need to do is list the commands! */
    if ( $VMS_STATUS_SUCCESS(cli$present(&q_all)) || !$VMS_STATUS_SUCCESS(cli$present(&p1)) )
    { /* just list all the commands! */
      do_verbs(cmd_table, "*", 1, 1);
    }
    else
    { /* just give 'em what they asked for */
      while( $VMS_STATUS_SUCCESS( cli$get_value(&p1, &result) ))
        do_verbs(cmd_table, SPTR(result), SLEN(result), 1);
    }
  }
  else
  {
    if ( $VMS_STATUS_SUCCESS(cli$present(&q_all)) || !$VMS_STATUS_SUCCESS(cli$present(&p1)) )
    { /* just display all the commands! */
      do_verbs(cmd_table, "*", 1, 0);
    }
    else
    { /* just give 'em what they asked for */
      while( $VMS_STATUS_SUCCESS(cli$get_value(&p1, &result)) )
        do_verbs(cmd_table, SPTR(result), SLEN(result), 0);
    }
  }
}

static int do_verbs(VectorBlock *table, char *name, int len, int listflag)
{
  static CommandBlock **displayed_commands = NULL;
  static int next_saved_command = 0;
  string candidate = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0},
         pattern   = {len, DSC$K_DTYPE_T, DSC$K_CLASS_S, name};
  CommandTableBlock *cmds;
  VerbTableBlock *verbs;
  CommandBlock *cmd;
  int i, verb_count;
  char *command;

  if (table->vec_b_strlvl != VEC_K_STRLVL) return SS$_ABORT; /* sanity check */

  verbs = TRO(table, table->vec_l_verbtbl);
  cmds  = TRO(table, table->vec_l_comdptr);

  verb_count = (verbs->verb_w_size - VERB_K_HEADER_LENGTH) /
               sizeof(verbs->verb_l_name[0]);

  if (displayed_commands == NULL)
  {
    displayed_commands = malloc(sizeof (CommandBlock *) * verb_count);
    if (displayed_commands == NULL) return SS$_INSFMEM;
    next_saved_command = 0;
  }

  for (i = 0; i < verb_count; ++i)
  {
    int overall, start, length, first, match;

    cmd = TRO(table, cmds->comd_l_cmdtro[i]);

    if (cmd->cmd_b_subtype != CMD_K_VERB) return SS$_ABORT; /* sanity check */

    command = BRO(cmd, cmd->cmd_w_name);

    overall = command[0];
    first   = command[1];
    start  = 1;
    match = 0;
    while (!match && (start < overall))
    {
      length = command[start]; /* length portion of ASCIC string */
      ++start;

      if (length)
      {
        /* if pre-fix, then "length" may be truncated to size of verb */
        if (length > first)
        {
          /* verify that there are no count bytes in between (in case it
             was shortened on us), and that the end isn't past the end of
             the overall string */
          int i;
          for (i = start; i < start+length; ++i)
          {
            if ((command[i] < ' ') || i > overall)
            {
                      length = i - start;
            }
          }
        }
        SLEN(candidate) = length;
        SPTR(candidate) = &command[start];
        match = (str$match_wild(&candidate, &pattern) == STR$_MATCH);
        start += length;
      }
    }

    if (match)
    {
      int j;

      for (j = 0; match && (j < next_saved_command); ++j)
      {
        if (displayed_commands[j] == cmd) match = 0;
      }

      if (match)
      {
        displayed_commands[next_saved_command] = cmd;
        ++next_saved_command;
        print_verb(table, cmd, listflag);
      }
    }
  }
}

static int print_verb(VectorBlock *table, CommandBlock *cmd, int listflag)
{
  int sts;

  sts = verb_check_block(cmd, BLOCK_K_COMMAND);
  if (!$VMS_STATUS_SUCCESS( sts )) return sts;

  if (cmd->cmd_b_subtype != CMD_K_VERB) return SS$_ABORT; /* sanity check */

  if (listflag)
  {
    int overall, start, length, first, truncated;
    char *command;

    command = BRO(cmd, cmd->cmd_w_name);
    /* This is only passed a verb (not a syntax clause) */
    overall = command[0]; /* length portion of "overall" ASCIC string */
    first   = command[1];
    start  = 1;
    while (start < overall)
    {
      truncated = 0;
      length = command[start]; /* length portion of ASCIC string */
      ++start;
      if (length)
      {
        /* if pre-fix, then "length" may be truncated to size of verb */
        if (length > first)
        {
          /* verify that there are no count bytes in between (in case it
             was shortened on us), and that the end isn't past the end of
             the overall string */
          int i;
          for (i = start; i < start+length; ++i)
          {
            if ((command[i] < ' ') || i > overall)
            {
              length = i - start;
              truncated = 1;
            }
          }
        }
        printf("%s%.*s%s", (start > 2)?", ":"",
                           length, &command[start],
                           truncated?" (truncated by CDU)":"");
        start += length;
      }
    }
    printf("\n");
  }
  else
  {
    verb_print_command(table, cmd, cmd);
  }
}

/*
**  the following is called with a variety of block types, hence use of void *
*/
extern int verb_check_block( void *tstv, int block_type)
{
  static char *types[] = {"VECTOR", "COMMAND", "TYPE", "ENTITY", "EXPRESSION"};
  CommandBlock *tstcb = tstv;

  if (tstcb->cmd_b_type == block_type) return SS$_NORMAL;

  if ((tstcb->cmd_b_type < 1) || (tstcb->cmd_b_type > 5))
    printf("Illegal block type (%d)!\n", tstcb->cmd_b_type);
  else
    printf("Unexpected block type encountered (%s)!\n",types[tstcb->cmd_b_type]);

  return SS$_ABORT;
}

static int 
set_logical( struct dsc$descriptor_s *log, string *equiv)
{
  static $DESCRIPTOR(process_table, "LNM$PROCESS");
  item_list itm[2];

  itm[0].buflen    = PLEN(equiv);
  itm[0].item_code = LNM$_STRING;
  itm[0].bufadr    = (unsigned long *) PPTR(equiv);
  itm[1].buflen    = 0;
  itm[1].item_code = 0;
  itm[1].bufadr    = 0;

  return sys$crelnm(0, &process_table, log, &PSL$C_USER, itm);
}
