/**********************************************************************
 * Bruce Lewis
 * MIT Project Athena
 * Created:  8/5/88
 *
 * $Source: /site/u1/exchange/teacher/RCS/utils.c,v $
 * $Author: brlewis $
 * $Header: utils.c,v 1.3 89/04/14 13:50:06 brlewis Exp $
 *
 * Copyright 1987 by the Massachusetts Institute of Technology.
 * For copying and distribution information, see the file
 * "mit-copyright.h".
 *
 * utils module -- contains useful procedures for client drivers
 **********************************************************************/
#include "mit-copyright.h"

#ifndef lint
static char rcsid_utils_c[] = "$Header: utils.c,v 1.3 89/04/14 13:50:06 brlewis Exp $";
#endif /* lint */

#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "teacher.h"
#define EXECUTABLE (u_short) 0111

/**********************************************************************
 * say_yes(question, d)
 *    char *question
 *    int d;  [default TRUE or FALSE]
 *
 *   + prints question
 *   + gets yes or no answer
 *
 * Returns:
 *   + TRUE if yes
 *   + FALSE if no
 **********************************************************************/

int
say_yes(question, d)

     char *question;
     int d;
{
  char answer[GENLEN];

  printf("%s (y/n, default: %c) ", question, d ? 'y' : 'n');
  switch(gets(answer)[0]) {
  case 'y':
  case 'Y':
    return(TRUE);
  case 'n':
  case 'N':
    return(FALSE);
  default:
    return(d);
  }
}

/**********************************************************************
 * promptfor(prompt, s)
 *    char *prompt, *s
 *
 *   + prints "prompt:  "
 *   + puts answer in s
 **********************************************************************/

promptfor(prompt, s)

     char *prompt, *s;
{
  int i;

  printf("%s:  ", prompt);
  if (strlen(gets(s)) == 0) return;

  /* remove trailing blanks */
  for(i=strlen(s)-1; i>0; i--) {
    if (isspace(s[i]))
      s[i] = '\0';
    else break;
  }

  /* remove leading blanks */
  for(i=0; i<strlen(s); i++)
    if (! isspace(s[i]))
      break;
  (void) strcpy(s, s+i);

  return;
}

/**********************************************************************
 * is_integer(string)
 *    char *string
 *
 * Returns:
 *   + TRUE if string consists only of digits
 *   + FALSE otherwise
 **********************************************************************/

is_integer(string)

     char *string;

{
  register int i;

  for (i=0; i<strlen(string); ++i)
    if (! isdigit(string[i]))
      return (FALSE);

  return (TRUE);
}

/**********************************************************************
 * crit_prompt(prompt, sp, dflt)
 *      char *prompt, **sp, *dflt;
 *
 *   + allocates memory for *sp
 *   + prompts for info
 *   + regognizes default
 **********************************************************************/

crit_prompt(prompt, sp, dflt)
     char *prompt, **sp, *dflt;
{
  char st[CANBSIZ];   /* max size of typewriter line (sys/param.h) */

  promptfor(prompt, st);
  savestring((strlen(st) == 0) ? dflt : st, *sp);
}
/**********************************************************************
 * crit_yank(st, sp)
 *      char *st, **sp;
 *
 *   + allocates space for *sp
 *   + puts part of st into *sp up to comma
 *   + regognizes default
 *   Returns part of st after comma
 *     or NULL if no comma found
 **********************************************************************/

char *
crit_yank(st,sp, dflt)
     char *st, **sp, *dflt;
{
  char *comma;

  if (st == NULL) {
    savestring(dflt, *sp);
    return(NULL);
  }

  if ((comma = index(st, ',')) == NULL) {
    if (strlen(st) == 0) {
      savestring(dflt, *sp);
    } else savestring(st, *sp);
    return(NULL);
  }
  if (comma == st) {
    savestring(dflt, *sp);
    return(comma+1);
  }
  *sp = strncpy(NewArray(char, comma-st+1), st, comma-st);
  (*sp)[comma-st] = '\0';
  if (strlen(*sp) == 0)
    *sp = strcpy(BiggerArray(char, *sp, strlen(dflt)+1), dflt);
  return(comma+1);
}

/**********************************************************************
 * get_crit(argc, argv, filetype)
 *      int filetype;
 *   + parses argv
 *   + prompts for information if needed
 *
 * Returns:
 *   + (FILE_ID *) criterion - use Fx_destroy_fid
 **********************************************************************/

FILE_ID *
get_crit(argc, argv, filetype)
     int argc;
     char *argv[];
     int filetype;
{
  FILE_ID *crit;
  char *p;

  if ((crit = NewFileId()) == NULL) {
    fprintf(stderr, "%s:  Quitting for lack of memory...\n", argv[0]);
    grade_quit();  /* should exit */
    exit(-1);
  }
  Fx_init_fid(crit);
  f_type(crit) = filetype;
  f_info(crit) = NewInfo();
  init_info(crit);
  savestring("", w_last_modify_time(crit));

  /* get specific info from command line */
  switch (argc) {
  case 2:    /* comma-separated arguments */
    p = crit_yank(argv[1], &f_assignment(crit), WILD_CARD);
    p = crit_yank(p, &f_student(crit), WILD_CARD);
    p = crit_yank(p, &f_version(crit), WILD_CARD);
    p = crit_yank(p, &f_filename(crit), WILD_CARD);
    break;
  case 0:   /* (case 0 probably superfluous, but there just in case) */
  case 1:
  case 3:
  case 4:
    crit_prompt("Assignment", &f_assignment(crit), WILD_CARD);
    crit_prompt("Author", &f_student(crit), WILD_CARD);
    crit_prompt("Version", &f_version(crit), WILD_CARD);
    crit_prompt("Filename", &f_filename(crit), WILD_CARD);
    break;
  default:  /* first four args are as,st,vs,fi */
    savestring(argv[1], f_assignment(crit));
    savestring(argv[2], f_student(crit));
    savestring(argv[3], f_version(crit));
    savestring(argv[4], f_filename(crit));
    break;
  }

#ifdef DEBUG
  printf("Criterion:  ");
  gshow(stdout, crit);
#endif DEBUG

  return(crit);
}

/**********************************************************************
 * gshow(stream, fid)
 *   FILE *stream;
 *   FILE_ID *fid
 *
 *  + prints fid in nice format for turnins.
 **********************************************************************/

gshow(stream, fid)

       FILE *stream;
       FILE_ID *fid;
{
  fprintf(stream, GFORMAT,
	 f_assignment(fid),
	 f_student(fid),
	 f_version(fid),
	 f_filename(fid),
	 w_last_modify_time(fid),
	 w_status(fid));
  return;
}

/**********************************************************************
 * same_id(a, b)
 *    FILE_ID *a, *b
 *
 * Returns:
 *   + TRUE if student, version, assignment, filename all match
 *   + FALSE otherwise
 *   + FALSE if either file id is NULL
 **********************************************************************/

same_id(a, b)

     FILE_ID *a, *b;
{
  if ((a != (FILE_ID *)0) && (b != (FILE_ID *)0))
    return ((strcmp(f_student(a), f_student(b)) == 0 &&
	   strcmp(f_version(a), f_version(b)) == 0 &&
	   strcmp(f_assignment(a), f_assignment(b)) == 0 &&
	   strcmp(f_filename(a), f_filename(b)) == 0));
  else return 0;
}

/**********************************************************************
 * idcmp(a, b)
 *    FILE_ID *a, *b
 *
 * Returned value:
 *   > 0 if a should come after b in a listing
 *   ==0 if it doesn't matter which comes first
 *   < 0 if a should come before b in a listing
 **********************************************************************/

idcmp(a, b)
       FILE_ID **a, **b;
{
  register int ret;

  if (f_type(*a) == HANDOUTS) {
    if ((ret = intcmp(atoi(f_classnumber(*a)), atoi(f_classnumber(*b)))) != 0)
      return(ret);
    if ((ret = strcmp(f_topic(*a), f_topic(*b))) != 0)
      return(ret);
    return(strcmp(f_version(*a), f_version(*b)));
  }
  else {
    if ((ret = strcmp(f_student(*a), f_student(*b))) != 0)
      return(ret);
    if ((ret = intcmp(atoi(f_assignment(*a)), atoi(f_assignment(*b)))) != 0)
      return(ret);
    if ((ret = strcmp(f_filename(*a), f_filename(*b))) != 0)
      return(ret);
    return(strcmp(f_version(*a), f_version(*b)));
  }
}

intcmp(a, b)
     int a, b;
{
  if (a == b) return(0);
  else return((a-b) / abs(a-b));
}


/**********************************************************************
 * sort_list(l)
 *    LIST *l
 *
 *   + sorts list for output
 **********************************************************************/

sort_list(l)
       LIST *l;
{

  Debug((stderr, ">> Enter sort_list\n"));
  qsort( (char *) list_of_files(l),
	how_many_files(l),
	sizeof(FILE_ID *),
	idcmp);
  Debug((stderr, "<< Exit sort_list\n"));
}

/**********************************************************************
 * is_note(handid)
 *    FILE_ID *handid
 *
 * Returns:
 *   + TRUE if handid describes a note
 *   + FALSE otherwise
 **********************************************************************/

is_note(handid)

     FILE_ID *handid;
{
  return ((strncmp(f_topic(handid), NOTESTR, NOTELEN) == 0));
}

/**********************************************************************
 * make_note(id)
 *    FILE_ID *id
 **********************************************************************/

make_note(id)
     FILE_ID *id;
{
  char *tmp;

  /* Insert NOTESTR at beginning of topic */
  tmp = strcat(strcpy(NewArray(char, strlen(f_topic(id)) + NOTELEN),
		      NOTESTR), f_topic(id));
  free(f_topic(id));
  f_topic(id) = tmp;
}

/**********************************************************************
 * del_anno(l)
 *    struct llist *l
 *
 *   + removes element of list of annotated files
 *   + removes the corresponding /tmp file
 **********************************************************************/

del_anno(l)
     struct llist *l;
{
  char call[GENLEN];

  Debug((stderr, ">> Enterng del_anno (%s)\n", l->fname));

  /* try to delete /tmp file; ignore failure */
  (void) unlink(l->fname);

  /* remove entry from list of annotated files */
  if (l->parent != NULL)
    {
      l->parent->child = l->child;	/* Move next to where l is */
      if (l->child != NULL)
	{
	  l->child->parent = l->parent;	/* Reparent next if next exists */
	}
      free(l->fname);
      free((char *) l->fid);
      free((char *) l);
    }
  else l->fname = NULL;


  Debug((stderr, "<< Exit del_anno.\n"));
  return;
}

newline(count)
     int count;
{
  int i;

  for(i=0; i<count; i++)
    (void) putchar('\n');
}

/**********************************************************************
 * find_in_path(command)
 *      char *command;
 *
 * Returns: (char *) full pathname (should be freed)
 *          or NULL if command not found
 **********************************************************************/

char *
find_in_path(command)
     char *command;
{
  char *path, *s, *pathname, tmp[1024];
  struct stat buf;

  if ((path = getenv("PATH")) == NULL)
    return(NULL);

  do {
    if ((s = index(path, ':')) == NULL)
      (void) strcpy(tmp, path);
    else {
      (void) strncpy(tmp, path, s - path);
      tmp[s - path] = '\0';
      path = s + 1;
    }
    (void) strcat(tmp, "/");
    if (stat(strcat(tmp, command), &buf) == 0) {
      if (buf.st_mode & EXECUTABLE > 0) {
	savestring(tmp, pathname);
	return(pathname);
      }
    }
  } while (s != NULL);
  return(NULL);
}

/**********************************************************************
 * teacher_list(argc, argv, filetype)
 *   + calls get_crit
 *   + gets list of matching files
 *
 * Returns:
 *   + NULL if no match found
 *   + (LIST *) found
 **********************************************************************/

LIST *
teacher_list(argc, argv, filetype)

     int argc;
     char *argv[];
     int filetype;
{
  FILE_ID *crit;
  LIST *list;

  crit = get_crit(argc, argv, filetype);
  if (Fx_list(crit, &list) != SUCCEEDED) {
    Fx_error("Fx_list");
    Fx_destroy_fid(crit);
    return(NULL);
  }
  if (how_many_files(list) == 0) {
    printf("No match found:\n");
    gshow(stdout, crit);
    Fx_destroy_fid(crit);
    Fx_destroy_list(&list);
    return(NULL);
  }
  Fx_destroy_fid(crit);
  return(list);
}

/**********************************************************************
 * show_list(argc, argv, filetype)
 *   + list files of filetype TURNIN, PICKUP, or HANDOUT
 **********************************************************************/

show_list(argc, argv, filetype)
     int argc;
     char *argv[];
     int filetype;
{
  LIST *list;

  if ((list = teacher_list(argc, argv, filetype)) == NULL)
    return;
  sort_list(list);

  if (vfork() != 0)
    (void) wait(0);
  else {
    int i;
    FILE *pipe;
    FILE_ID header;
    FILE_INFO headinfo;

    /* Put in values to print as head of list */
    f_assignment(&header) = "#";
    f_student(&header) = "AUTHOR";
    f_version(&header) = "VRS";
    f_filename(&header) = "FILENAME";
    f_info(&header) = &headinfo;
    w_status(&header) = (filetype == TURNIN) ? "STATUS" : "";
    w_last_modify_time(&header) = "DATE";

    if ((pipe = popen("MORE='' /usr/ucb/more", "w")) == NULL) {
      Debug((stderr, "Not piping to /usr/ucb/more.\n"));
      pipe = stdout;
    }

    gshow(pipe, &header);
    for(i=0; i<how_many_files(list); i++)
      if (! is_note(file_in(list, i)))
	gshow(pipe, file_in(list, i));

    (void) pclose(pipe);
    _exit(0);
  }
  
  Fx_destroy_list(&list);
  return;
}

/**********************************************************************
 * teacher_put(argc, argv)
 *   + copy a local file to the pickup bin
 **********************************************************************/

teacher_put(argc, argv, filetype)

     int argc;
     char **argv;
     int filetype;
{
  char inputbuf[GENLEN], prompt[GENLEN];
  char *basename, *dflt;
  FILE_ID *destination;

  newline(1);
  Debug((stderr, ">>>> Enter teacher_put\n"));

  /** parse argc, argv **/
  destination = get_crit(argc, argv, filetype);

  /** be sure to get assignment number **/
  if (strcmp(f_assignment(destination), WILD_CARD) == 0) {
    promptfor("Please specify assignment", inputbuf);
    free(f_assignment(destination));
    if (strlen(inputbuf) > 0) {
      savestring(inputbuf, f_assignment(destination)); }
    else savestring(NONE, f_assignment(destination));
  }

  /** be sure to get author **/
  switch (filetype) {
  case EXCHANGE:       /* author is the sender */
  case HANDOUTS:
    savestring(getenv("USER"), f_student(destination));
    break;
  default:             /* author is some student */
    if (strcmp(f_student(destination), WILD_CARD) == 0) do {
      promptfor("Please specify student", inputbuf);
      free(f_student(destination));
      savestring(inputbuf, f_student(destination));
    } while (strlen(inputbuf) == 0);
  }

  /** be sure to get version **/
  if (strcmp(f_version(destination), WILD_CARD) == 0)
    (void) strcpy(f_version(destination), LATEST);

  /** Form prompt **/
  (void) strcpy(prompt, "Local filename");
  if (strcmp(f_filename(destination), WILD_CARD) != 0) {
    dflt = f_filename(destination);
    (void) strcat(strcat(strcat(prompt, "(default "), dflt), ")");
  } else dflt = "* file not specified *";

  /** Prompt for local filename **/
  promptfor(prompt, inputbuf);
  if (strlen(inputbuf) == 0)
    (void) strcpy(inputbuf, dflt);

  /** If filename not specified in criterion, use local filename **/
  if (strcmp(f_filename(destination), WILD_CARD) == 0) {
    free(f_filename(destination));
    basename = rindex(inputbuf, '/');
    basename = basename ? 1 + basename : inputbuf;
    savestring(basename, f_filename(destination));
  }

  if (Fx_send(inputbuf, destination) != SUCCEEDED)
    Fx_error("Fx_send");
  else printf("Sent %s successfully.\n", inputbuf);

  Fx_destroy_fid(destination);
  newline(1);
  Debug((stderr, "<<<< Exit gput\n"));
  return;
}

uniqname(fname, fid, prefix)
     char *fname, *prefix;
     FILE_ID *fid;
{
  /* prepare a unique filename */
  (void) strcpy (fname, prefix);
  (void) strcat (fname, getenv("USER"));
  (void) strcat (fname, f_assignment(fid));
  (void) strcat (fname, f_student(fid));
  (void) strcat (fname, f_version(fid));
  (void) strcat (fname, f_filename(fid));
  return;
}

/**********************************************************************
 * teacher_display(argc, argv, filetype)
 *   + copy files to /tmp
 *   + start editor
 **********************************************************************/

teacher_display(argc, argv, filetype)
     int argc;
     char *argv[];
     int filetype;
{
  FILE_ID *target;
  LIST *tlist;
  char localname[GENLEN], cargv[NOFILE][GENLEN];
  char **cargv2;  /* array of pointers needed for execv */
  int i, pid, cargc=0;  /* process id, child arg count */
  int nothing_displayed = TRUE;
  extern struct llist *anno_list;
  extern GENLIST Editor[2];

  Debug((stderr, ">>>> Enter teacher_display\n"));
  newline(1);

  /* get list of files to display */
  if ((tlist = teacher_list(argc, argv, filetype)) == NULL)
    return;
  if (how_many_files(tlist) == 0)
    return;

  /* determine what editor to use */
  for(i=0; i<Editor[0].n; i++) {
    (void) strcpy(cargv[cargc++], Editor[0].genlist[i]);
    Debug((stderr, ">> cargv[%d] = %s\n", cargc-1, cargv[cargc-1]));
  }

  for(i=0; i<how_many_files(tlist); i++) {

    target = file_in(tlist, i);
    gshow(stdout, target);

    /* prepare a unique filename */
    uniqname(localname, target, "/tmp/");

    /* retrieve the file */
    if (Fx_retrieve(target, localname) != SUCCEEDED) {
      Fx_error(localname);
    }
    else {
      nothing_displayed = FALSE;
      (void) strcpy(cargv[cargc++], localname);
      Debug((stderr, ">> cargv[%d] = %s\n", cargc-1, cargv[cargc-1]));
    }
    if (cargc > NOFILE) {
      fprintf(stderr, "Maximum files per editor reached.\n");
      fprintf(stderr, "Repeat %s request to display the rest.\n", argv[0]);
      break;
    }
  }

  if (nothing_displayed == TRUE) {
    Fx_destroy_list(&tlist);
    printf("No files displayed.\n");
  }
  else {
    cargv2 = NewArray(char *, 2 + cargc);
    for(i=0; i<cargc; i++) {
      cargv2[i] = cargv[i];
      Debug((stderr, ">> cargv2[%d] = %s\n", i, cargv2[i]));
    }
    cargv2[cargc] = NULL;

    if (!(pid = fork())) {
      setpgrp(0, getpid());
      if (execv(cargv[0], cargv2) == -1) {
	fprintf(stderr, "%s:  Cannot execute.\n", cargv[0]);
	Fx_destroy_list(&tlist);
	free( (char *) cargv2);
      }
    }

    if (pid == -1) {
      fprintf(stderr, "Can't create subprocess for %s.", cargv[0]);
      Fx_destroy_list(&tlist);
      free( (char *) cargv2);
      return;
    }
  }

  newline(1);
  Debug((stderr, "<<<< Exit teacher_display\n"));
  return;
}
