/* The normal routine "normalizes" a given path name to this form:
** drv:\id\id\id...\id
** so that two identical directory/file will yield the same normalized
** path regardless of how the user typed it in
** E.g.
**	 a:\x\y
**   and a:\X\Y\.
**  both yield A:\X\Y
**
** In addition, the later one would also set the flag "dir" indicating
** that the user specified it as an directory.
**
** Written by Peter Wu @ Faculty Support Center @ Univ. of Wisconsin
** 7/14/86
*/
#define LINT_ARGS

#define T_ID1 1  /* id without period */
#define T_ID2 2  /* id with period */
#define T_DOT 3  /* '.' */
#define T_DD  4
#define T_BS  5
#define T_COL 6
#define T_NUL 0

#define PLEN 200

#include "dta.h"
#include <dos.h>
#include <string.h>
#include <conio.h>

char *insert();

index(str,c) /* first occurence of c in str */
char *str, c;
{
  char *i, d;

  i = str;
  d = *i;
  while (d != '\0') {
    if (d == c) {
      return i - str;
    }
    i++;
    d = *i;
  }
  return -1;
}

char lastc(str)  /* return last character of the string */
register char *str;
{
  if (*str == '\0') {
    return '\0';
  }

  do {
    str++;
  } while (*str != '\0');

  return *(str-1);
}

normal(path)  /* normalize a path */
char *path;
{
  char work1[PLEN], work2[PLEN], token[13], token2[13], *ptr;
  int i, drv, marker, t, ftype, t2;

  /* first make the path a complete path (include drive, and root) */
  i = index(path, ':');  /* search for ':' */
  if (i < 0) {	/* no drive specification */
    if (*path == '\\') {  /* start from root */
      work1[0] = '@' + drive();  /* default disk */
      work1[1] = ':';
      work1[2] = '\0';
      strcat(work1, path);  /* now we have full path */
    } else {  /* no drive and no root */
      current(0,work1);  /* get current drive and path */
      strcat(work1, path);  /* now we have full path */
    }
  } else {  /* has drive specification */
    if (path[i+1] != '\\') {  /* but no root, so insert current path */
      drv = toupper(path[i-1]) - '@';  /* get drive number */
      current(drv,work1);  /* put down drive and path */
      strcat(work1, path+i+1);	/* now we have full path */
    } else {  /* nothing to change */
      strcpy(work1,path);
    }
  }

#ifdef debug
  printf("full path is %s\n", work1);
#endif

  /* now we have full path in work, but we need to normalize it to get rid
  ** of .. and .
  */
  marker = -2;
  work2[PLEN-1] = '\0';  /* this is where we accumulate result backwards */
  ptr = work2 + PLEN - 1;  /* point to first char */
  ftype = -1;  /* -1=init, 0=unknown,  A_DIR=dir */

  do {
    t = scan(work1,&marker,token);
#ifdef debug
    printf("ptr is %s so far, and t=%d\n", ptr, t);
#endif
    switch (t) {

      case T_ID1:
      case T_ID2:
	ptr = insert(token, ptr);
	if (ftype == -1) {
	  ftype = 0;  /* this could be a file or a directory */
	}
	break;

#ifdef ha
      case T_ID2:
	if (ftype == -1) {
	  ptr = insert(token, ptr);
	  ftype = 0;  /* this could be file or a directory */
	} else {
	  putn("invalid sub-directory name \"", token, "\"\n\015",0);
	  exit(1);
	}
	break;
#endif

      case T_DD:
	t2 = scan(work1,&marker,token);  /* see what's in front of .. */
	if (t2 != T_BS) {
	  cputs("incorrect use of \"..\" in path name\n\015");
	  exit(1);
	}
	/* now delete \id\.. */
	t2 = scan(work1,&marker,token);  /* read the id (hopefully) */
	if (t2 != T_ID1) {
	  if (t2 == T_COL) {  /* A:\.. is not permitted */
	    cputs("sorry, but root directory has no parent\n\015");
	    exit(1);
	  } else {  /* what could t2 be? */
	    cputs("missing directory name in front of \"\\..\"\n\015");
	    exit(1);
	  }
	}
	t2 = scan(work1,&marker,token2);  /* read the '\' */
	if (t2 != T_BS) {  /* can this ever happen? */
	  putn("missing \"\\\" before \"", token, "\" in path\n\015",0);
	  exit(1);
	}

	/* a:\id\.. should yield a:\ not a:
	** a:\id1\..\id2 should yield a:\id2
	** this is taken care of somewhere else
	*/

	/* ok, \id\.. deleted */
	if (ftype == -1) {
	  ftype = A_DIR;  /* this has to be a directory */
	}
	break;

      case T_DOT:
	t2 = scan(work1,&marker,token);  /* read the '\' */
	if (t2 != T_BS) {  /* is this error possible? */
	  cputs("missing \"\\\" in front of \".\"\n\015");
	  exit(1);
	}
	if (ftype == -1) {
	  ftype = A_DIR;  /* this has to be a directory */
	}
	/* special case is "a:\." we want this to turn into "A:\" and
	** not "A:"
	*/
	break;

      case T_BS:
	ptr = insert("\\", ptr);
	break;

      case T_COL:
	ptr = insert(":", ptr);
	break;
    }
  } while (t != T_NUL);

  if (lastc(ptr) == ':') {  /* special case like a: */
    strcat(ptr,"\\");
  }
  strcpy(path,ptr);  /* copy normalized path back */
  return ftype;
}

char *insert(str1,str2)  /* insert str1 in front of str2 */
char *str1, *str2;
{
  int i;

  i = strlen(str1);
  return strncpy(str2 - i, str1, i);
}

scan(path,marker,tstr) /* return tokens backwards */
char *path, *tstr;
int *marker;
{
  char c, d;

  if (*marker == -2) {	/* signal new scan */
    *marker = strlen(path) - 1;
  }

  if (*marker == -1) {
    return T_NUL;  /* end of string */
  }

  c = path[*marker];
  if ( ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) ) {
    return collect_id(tstr, path, marker);
  }

  switch (c) {
    case '.':
      if (*marker == 0) {
	*marker = -1;
	return T_DOT;
      } else {
	d = path[*marker - 1];
	if (d == '.') {
	  *marker -= 2;
	  return T_DD;
	} else {
	  if ((d == '\\') || (d == ':')) {
	    (*marker)--;
	    return T_DOT;
	  } else {  /* must be an id with trailing dot */
	    return collect_id(tstr, path, marker);
	  }
	}
      }
      cputs("how do I get here?\n\015");
      error("scan", 0);

    case '\\':
      (*marker)--;
      return T_BS;

    case ':':
      (*marker)--;
      return T_COL;

    default:
      return collect_id(tstr, path, marker);  /* non-alpha ID */
  }
}

check_fn(fn)  /* look for invalid characters in file name */
char *fn;
{
  char c, *s;

  s = fn;
  while((c= *s) != '\0') {
    if ((c < 32) || (index("\"[]|<>+=;,/",c) > -1)) {
      cputs("invalid character '"); putch(c);
      putn("' in file/sub-directory name \"", fn, "\"", 0);
      exit(1);
    }
    s++;
  }
}

collect_id(tstr, path, marker)
char *tstr, *path;
int *marker;
{
  char c, id[13];
  int flag, i;

  id[13] = '\0';
  i = 12;
  flag = T_ID1;  /* no period in file name */
  do {
    c = path[*marker];

    if (c == '.') {  /* indicate there's period in the file name */
      flag = T_ID2;
    } else {
      c = toupper(c);
      if ((c == '\\') || (c == ':')) {  /* end of file name */
	strncpy(tstr, id+i+1, 13);
	check_fn(tstr);
	return flag;
      }
    }

    id[i] = c;
    (*marker)--;
    if (*marker < 0) {
      strncpy(tstr, id+i, 13);
      check_fn(tstr);
      return flag;
    }
    i--;
  } while (i >= 0);

  putn("file/sub-directory name \"...", id, "\" too long\n\015", 0);
  exit(1);
}

drive()  /* returns current drive number 1=A 2=B */
{
  return (char) bdos(0x19, 0, 0) + 1;
}

current(drv,cpath)  /* returns current path "drv:\id\..\id on drv */
char *cpath;
int drv;
{
  union REGS inregs, outregs;
  char tmp[64];

  inregs.h.ah = 0x47;  /* get current path */
  inregs.x.si = (int) tmp;
  inregs.h.dl = drv;  /* drive number 0=default, 1=A, 2=B */
  intdos(&inregs, &outregs);
  if (drv == 0) {
    drv = drive();
  }
  if (outregs.x.cflag) {
    cputs("cannot find current path on drive "); putch('@'+drv);
    cputs(":\n\015");
    error("current", 0);
  } else {
    cpath[0] = '@' + drv;
    cpath[1] = ':';
    cpath[2] = '\\';
    cpath[3] = '\0';
    if (*tmp != '\0') {  /* if not root directory, then append path */
      strcat(cpath,tmp);
      strcat(cpath,"\\");
    }
  }
}

catpath(path, name)  /* concatenate name to path, adding \ when neccessary */
char *path, *name;
{
  if (path[strlen(path)-1] != '\\') {
    strcat(path, "\\");
  }
  strcat(path, name);
}

chopath(path)  /* chop off the last portion of a path */
char *path;
{
  char *tmp;

  tmp = strrchr(path, '\\');
  if (tmp == (char *)0) {
    putn("can't find '\\' in ", path, "\n\015");
    error("chopath", 0);
  }
  if (*(tmp-1) == ':') {  /* special case for root */
    *(tmp+1) = '\0';
  } else {
    *tmp = '\0';
  }
}

#define A_INT 0x80	/* interactive flag */

/* extract the file-type specifier from the given path.
** E.g.  x/f becomes x with file type,
**	 animal\dog/d becomes animal\dog with sub-directory type,
**	 *./f becomes *. with file type
**	 *.* becomes *.* with no type
**	 *./i becomes interactive (i.e. prompts for y/n for each file)
*/
extype(path)
char *path;
{
  char *s;
  int type;

  s = strrchr(path,'/');  /* find last '/' */
  if (s == (char *) 0) {  /* no type specified */
    return 0;
  }

  type = 0;
  *s = '\0';
  for (++s; *s != '\0'; s++) {  /* start scanning after the '/' */
    switch (*s) {
      case 'f':
      case 'F':
	type |= A_FIL;
	break;

      case 'd':
      case 'D':
	type |= A_DIR;
	break;

      case 'h':
      case 'H':
	type |= A_HID;	/* search for hidden files */
	break;

      case 'i':
      case 'I':
	type |= A_INT;	/* requires user's confirmation for each file */
	break;

      default:
	cputs("unrecognized file attribute '"); putch(*s);
	putn("' in path \"", path, "\"\n\015",
"valid attributes are f, d, h, i (for file, directory, hidden, interac)\n\015",
 0);
	exit(1); /* maybe the user doesn't know what he/she's doing */
    }
  }
  return type;
}
