
/*
------------------------------------------------------------------------------

    =====
    CPCFS  --  t o o l s . c  --  Auxiliary functions
    =====

    Version 0.85                    (c) Derik van Zuetphen
------------------------------------------------------------------------------
*/

#include <errno.h>

#if DOS
#include "conio.h"
#include "dos.h"
#endif

#include "cpcfs.h"


/*************
  Auxiliaries
 *************/

void printm(int verb, char *fmt, ...)  {
/*   ^^^^^^ */
va_list	args;
const char	highlight[] = "\x1B[0;31;1m";	/* light red */
const char	unhighlight[] = "\x1B[0m";

	va_start (args, fmt);
	if (Verb >= verb) {
		if (verb>9) printf("%s",highlight);
		vprintf(fmt,args);
		if (verb>9) {
			printf("%s",unhighlight);
			fflush(stdout);
		}
	}
	va_end(args);
}

void putcharm(int verb, char ch)  {
/*   ^^^^^^^^ */
	if (Verb >= verb) putchar(ch);
}


char *lower(char *s)  {
/*    ^^^^^ */
char	*p=s;
	while (*s) {
		*s=tolower(*s);
		s++;
	}
	return p;
}

char *upper(char *s)  {
/*    ^^^^^ */
char	*p=s;
	while (*s) {
		*s=toupper(*s);
		s++;
	}
	return p;
}


char *append_suffix (char *name, char *suffix) {
/*    ^^^^^^^^^^^^^
Appends <suffix> to <name>, but not if the name already ends with <suffix>.
<name> must point to sufficient space. Returns <name> */
char	*p, *ds, *dot;

	p=name;
	ds=dot=NULL;
	while (*p) {
		if (*p==DIRSEPARATOR)	ds=p;
		if (*p=='.')		dot=p;
		p++;
	}
	if ((dot==NULL)||(dot<ds)) {
		strcat(name,".");
		strcat(name,suffix);
	}
	return name;
}


int errorf (bool perr,const char *fmt, ...) {
/*  ^^^^^^
Writes a formatted errormessage to stderr and appends a system errormessage
if <perr> set. The message in <fmt> should not end with \n.
Always returns -1. */

va_list	args;
	if (Verb<1) return -1;

	va_start(args,fmt);
	vfprintf(stderr,fmt,args);
	va_end(args);
	if (perr) {
#if UNIX
		fprintf(stderr,": ");
#endif
/* DOS put always a colon before the error message, UNIX only if the argument
to perror is not empty */
		perror("");
	} else
		fputc(10,stderr);
	return -1;
}

/* attribute names for parsing and writing attributes */

const char	*attr_name[3][11] = {
	{"A=ON", "SYS", "R/O", "F8=ON", "F7=ON", "F6=ON", "F5=ON",   /* set */
			       "F4=ON", "F3=ON", "F2=ON", "F1=ON" },
	{"A=OFF","DIR", "R/W", "F8=OFF","F7=OFF","F6=OFF","F5=OFF",  /* reset */
			       "F4=OFF","F3=OFF","F2=OFF","F1=OFF" },
	{"A", "SYS", "R/O", "8", "7", "6", "5", "4", "3", "2", "1" } /* short */
};

const char	*attr_empty[] = {"-", "   ", " "};


const char *show_attr(int mask, int attr, bool always) {
/*          ^^^^^^^^^
Answer a representation for attribute <attr>, if it appears in <mask>,
If <always> is set, answer also a string for zero-attributes */

	if (mask & attr) {	/* attribute set */
		switch (attr) {
		case ATTR_A: return attr_name[2][0];
		case ATTR_S: return attr_name[2][1];
		case ATTR_R: return attr_name[2][2];
		case ATTR_8: return attr_name[2][3];
		case ATTR_7: return attr_name[2][4];
		case ATTR_6: return attr_name[2][5];
		case ATTR_5: return attr_name[2][6];
		case ATTR_4: return attr_name[2][7];
		case ATTR_3: return attr_name[2][8];
		case ATTR_2: return attr_name[2][9];
		case ATTR_1: return attr_name[2][10];
		}
	} else if (always) /* attribute not set and output required */
		switch (attr) {
		case ATTR_S: return attr_name[1][1];
		case ATTR_R: return attr_name[1][2];
		default:     return attr_empty[0];
		}
	else {	/* only spaces, attribute not set and no output */
		switch (attr) {
		case ATTR_R: return attr_empty[1];
		case ATTR_S: return attr_empty[1];
		default:     return attr_empty[2];
		}
	}
	errorf(FALSE,"--==>>> show_attr");
	return NULL;
}


const char* show_all_attr(int att, bool always) {
/*          ^^^^^^^^^^^^^
Show all attribues like "R/O SYS A 1234 5678" or "R/W DIR - ---- ----"
*/

static char buf[25];
	sprintf(buf,"%s %s %s %s%s%s%s %s%s%s%s",
		show_attr(att,ATTR_R,always),
		show_attr(att,ATTR_S,always),
		show_attr(att,ATTR_A,always),
		show_attr(att,ATTR_1,always),
		show_attr(att,ATTR_2,always),
		show_attr(att,ATTR_3,always),
		show_attr(att,ATTR_4,always),
		show_attr(att,ATTR_5,always),
		show_attr(att,ATTR_6,always),
		show_attr(att,ATTR_7,always),
		show_attr(att,ATTR_8,always));
	return buf;
}


int parse_attr(char *str, int *mask, bool *set) {
/*  ^^^^^^^^^^
Parse the given string <str> as a attribute name. Set the <*mask> to the
recognized attribut, and <set> whether the attribut is requested to set.
Answer -1, if <str> is not a valid attribute name.
*/
int	i, j;
char	s[INPUTLEN];

	strcpy(s,str);
	upper(s);
	for (i=0;i<2;i++) {	/* set, reset */
		for (j=0;j<11;j++) {	/* all attributs */
			if (strcmp(s,attr_name[i][j])==0) {
				*set = (i==0);
				*mask = 0x1 << j;
				return 0;
			}
		}
	}

	return -1;
}




void do_break() {
/*   ^^^^^^^^
Jump to interactive mode or exit */
	printm(3," [Aborted]\n");
	if (Interactive)
		longjmp(break_entry,1);
	else
		exit(2);
}


void *Malloc(int bytes) {
/*    ^^^^^^
Allocate memory. If an error ocurrs, jump to input loop */
void	*p;

	p = malloc(bytes);
	if (p==NULL) {
		errorf(TRUE,"Malloc");
		abandonimage();
		do_break();
		return NULL;
	}
	return p;
}


char	valid_keys[4];	/* can be c, q, and/or r */

void newpage(char *keys) {
/*   ^^^^^^^
Set up a new paging cycle. <keys> contains a list of characters that may be
pressed. */
	strcpy(valid_keys,keys);
	lineno=1;
}


char nextline() {
/*   ^^^^^^^^
Anvance the line counter of the internal pager. Stop at a prompt, if the
screen is full. Return the pressed character or '\0' */
char	answer;
int	i;

	if (Break_Wish) do_break();
	if ((lineno==0) || (pagelen==0) || (Verb < 1)) return 0;
	lineno++;
	if (lineno+1>pagelen) {
		printm(1,"-- M o r e --   ");
		if (*valid_keys!=0) putcharm(1,'(');
		for (i=0; i<strlen(valid_keys);i++) {
			if (i>0) printm(1,", ");
			switch (valid_keys[i]) {
			case 'c': printm(1,"`c' = continous");	break;
			case 'q': printm(1,"`q' = quit");	break;
			case 'r': printm(1,"`r' = restart");	break;
			default:  errorf(FALSE,"--==>>> nextline");
			}
		}
		if (*valid_keys!=0) putcharm(1,')');

		answer = tolower(wait_for_key(0,TRUE));
		putcharm(1,10);
		if (Break_Wish) do_break();
		if (answer=='c' && strchr(valid_keys,'c')!=NULL) {
			lineno=-1;
			return 'c';
		}
		lineno=1;
		if (strchr(valid_keys,answer)!=NULL) return answer;
	}
	return 0;
}


char *repstr(char c, int times) {
/*    ^^^^^^
Form a string out of <times> <c>.
There must only be one repstr in a printf statement!!!
*/
int	i;
static	char str[INPUTLEN];
	for (i=0;i<times;i++) {
		if (i==INPUTLEN) break;
		str[i]=c;
	}
	str[i]=0;
	return str;
}


char *show_format (uchar sec_offset) {
/*    ^^^^^^^^^^^ */
static char result[20];
	switch (sec_offset ) {
		case SYSTEMFORMAT:	strcpy(result,"System Format"); break;
		case DATAFORMAT:	strcpy(result,"Data Format"); break;
		case IBMFORMAT: 	strcpy(result,"IBM Format"); break;
		case VORTEXFORMAT: 	strcpy(result,"Vortex Format"); break;
		default:		strcpy(result,"Unknown Format");
	}
	return result;
}


char *show_mode (int m)  {
/*    ^^^^^^^^^ */
static char result[10];
	switch (m) {
	case M_BIN:  strcpy(result,"Binary"); break;
	case M_TEXT: strcpy(result,"Text"); break;
	case M_AUTO: strcpy(result,"Auto"); break;
	}
	return result;
}



void reparse (int argnb) {
/*   ^^^^^^^
transform the parsed command line back to one string containing all
arguments from "argnb" to last argument.*/
int	i;
char	*p;
	for (i=argnb; i<nbof_args; i++) {
		p = arg[i];
		while (*p) p++;
		*p = ' ';	/* replace terminating 0 with space */
	}
}



void expand_percent(char *from, char *to, int max) {
/*   ^^^^^^^^^^^^^^
WARNING: this function needs sprintf to return int, and not char* ! */
bool	active = *disk_header.tag;
char	buf[PATH_MAX];
char	*t, *t0;

	t0 = t = Malloc(max+80);	/* 80 (>len(%V)) as overflow area */

	while (*from)  {
		if (*from!='%') *t++=*from;
		else  {
			from++;
			switch (*from) {
			case 'u':
			case 'U': if (active)
					t+=sprintf(t,"%d",cur_user);
				  else
					*t++='#';
				  break;
			case 'i': if (active)
					t+=sprintf(t,"%s",imagename);
				  else
					*t++='#';
				  break;
			case 'I': if (active)
					t+=sprintf(t,"%s",full_imagename);
				  else
					*t++='#';
				  break;
			case 'f': if (active)
					t+=sprintf(t,"%5.1f",100.00-percentage);
				  else
					*t++='#';
				  break;
			case 'F': if (active)
					t+=sprintf(t,"%ld",
						(long)free_blks*dpb->BLS);
				  else
					*t++='#';
				  break;
			case 'a': if (active)
					t+=sprintf(t,"%5.1f",percentage);
				  else
					*t++='#';
				  break;
			case 'A': if (active)
					t+=sprintf(t,"%ld",
						(long)allocated_blks*1024);
				  else
					*t++='#';
				  break;
			case 'c':
			case 'C': t+=sprintf(t,"%s",getwd(buf));
				  break;
			case 'v': if (PATCHLEVEL==0)
					t+=sprintf(t,"%d.%d",
						MAJORVERSION,MINORVERSION);
				  else
					t+=sprintf(t,"%d.%d.%d",
					MAJORVERSION,MINORVERSION,PATCHLEVEL);
				  break;
			case 'V': t+=sprintf(t,"%d.%d.%d",
				MAJORVERSION,MINORVERSION,PATCHLEVEL);
				  break;
			case '%': *t++='%'; break;
			case '_': *t++=' '; break;
			case '#': *t++='#'; break;
			case 'e':
			case 'E': *t++=27; break;
			case 'q':
			case 'Q': *t++='"'; break;
			case 's':
			case 'S': *t++=';'; break;
			case 'M':
                /*	showing the free memory is not the duty of an application these days. */
				t+=sprintf(t,"%lu",0L); break;
			default : *t++='%'; *t++=*from; break;
			}
		}
		if (*from) from++;

/* check on string overflow */
		if (t>t0+max) break;
	}
	*t = 0;

	strncpy(to,t0,max-1);
	to[max-1] = 0;	/* if t0 is _exactly_ of length max-1 */
	free(t0);
}


void echom (int v, char *p) {
/*   ^^^^^ */

char	buf[256]; /* 3 lines should be enough */
	if (Verb >= v) {
		expand_percent(p,buf,256);
		printf("%s",buf);
	}
}


bool confirmed()  {
/*   ^^^^^^^^^ */
char	answer;
	printm(1,"[y/N] _");
	if (force||(Verb<1)) {
		printm(3," [Forced]\n");
		return TRUE;
	} else {
		answer = wait_for_key(0,TRUE);
		putcharm(1,10);
		if (Break_Wish) return FALSE;
		return (tolower(answer) == 'y');
	}
}


int parse_cpm_filename (char *name, int *user, char *root, char *ext) {
/*  ^^^^^^^^^^^^^^^^^^
Split the CP/M conformant <name> "UU:RRRRRRRR.EEE" into its ASCIIZ parts.
<user> will be -1, if no UU: found; -2, if UU: is *:
<ext> will be "", if no .EEE found.
<root> = "" is allowed and signals a missing filename (e.g. dir 1:).
Filenames of kind ".xxx" are not possible.
The <root> or <ext> parts can be longer than 8 or 3 due to "[]" wildcards!
Errorcode is 1 on error.
*/
char	*p, *q, *r;		/* temp pointer */
int	i;

	*user = -1;
	*root = 0;
	*ext = 0;

/* scan usernumber */
	p = strchr(name,':');
	if (p!=NULL) {
		if ((name[0]=='*') && (name[1]==':')) {
			*user = -2;
			name += 2;
		} else {
			*user = (int)strtoul(name,&q,0);
			if ((*user<0) || (*user>255) || (*q!=':')
							|| (errno==ERANGE))
				return 1;
			name = p+1;
		}
	}

/* scan root part */
	p = name;
	i = 0;
	r = root;
	while ((*p!='.') && *p && i<8) {
		*r++ = *p++;
		i++;
	}
	*r=0;
	q=strchr(p,'.');
	if (q) p=q;

/* scan extension part */
	if (*p) strcpy(ext,p+1);


/* convert to upper case */
	upper(root);
	upper(ext);

	if (*root==0 && *ext!=0) return 1;
	return 0;
}



int parse_filename (char *name, int *drive, char *path, char *root, char *ext) {
/*  ^^^^^^^^^^^^^^
Split the DOS conformant <name> "D:\PP\..\PP\RRRRRRRR.EEE" into its parts.
<drive> will be 0, if no D: was found, and 1 for A:, 2 for B:, and so on;
<path> contains a trailing backslash (i.e. at least "\").
On UNIX systems <root> may contain dots and <ext> is always empty.
Errorcode is 1 on error
*/
char	*p;	/* temp pointer */
#if DOS
char	*r;
int	i;
#endif

	*drive = 0;
	*path = 0;
	*root = 0;
	*ext = 0;

#if DOS
/* scan drive */
	if ((*name!=0) && (name[1]==':')) {
		*drive = tolower(*name)-'a'+1;
		if ((*drive<1) || (*drive>26)) return 1;
		name += 2;
	}
#endif

/* scan path */
	p = strrchr(name,DIRSEPARATOR);
	if (p!=NULL) {			/* else no "\" found, path is empty */
		strncpy(path,name,p-name+2);
		name = p+1;
	}

/* scan root part */
	if (*name == 0) return 1;

#if UNIX
	strcpy(root,name);
#else
	r = root;
	i = 0;
	while ((*name!='.') && *name && i<8) {
		if (*name==':') return 1;	/* possibly mislead drive */
		*r++ = *name++;
	}
	*r=0;

/* scan extension part */
	if (*name) strncpy(ext,name+1,4);

#endif


#if DOS
/* convert to upper case */
	upper(path);
	upper(root);
	upper(ext);
#endif

	return 0;
}


int internal_pager(char *filename) {
/*  ^^^^^^^^^^^^^^
Display the file with stops after each screen */
FILE	*file;
char	line[256];
char	key;

	file = fopen(filename,"r");
	if (file==NULL) return errorf(TRUE,"I can't display \"%s\"",filename);

	newpage("cqr");
	while (fgets(line,256,file)) {
		printm(0,"%s",line);
		key = nextline();
		if (key=='r') {
			fseek(file,0L,SEEK_SET);
		}
		if (key=='q') break;
	}
	fclose(file);
	return 0;
}

int pager(char *filename) {
/*  ^^^^^
Display the file <filename> with the lister program from the environment
variable %PAGER or with the internal_pager()
*/
char	buf[INPUTLEN];
char	*pag;
int	err;

	pag = getenv("PAGER");
	if (pag==NULL)	{
		/* strcpy(buf,PAGERDEFAULT);*/
		internal_pager(filename);
		return 0;
	} else {
		strcpy(buf,pag);
	}
	strcat(buf," ");
	strcat(buf,filename);

	err = system(buf);
	if ((err==-1) || (err==127)) return -1;

	return 0;
}


void str2mem(char *mem, char *str, int spc) {
/*   ^^^^^^^
Copy the ASCIIZ string at <str> to position <mem> without trailing 0.
Fill the memory at <mem> with <spc> spaces.
*/
int	i;

	memset(mem,' ',spc);
	i=0;
	while (str[i]) {
		mem[i] = str[i];
		i++;
	}
}



void build_cpm_name(char *buf, int user, char *root, char *ext) {
/*   ^^^^^^^^^^^^^^
Construct a CP/M name out of the given stuff.
If <user> = -1, no usernumber is prepended,
if <user> = -2, a * (wild user) is prepended.
If <ext> = "", the last char is "."!
<buf> must point to memory of at least 3+1+8+1+3+1=17 Byte (UUU:RRRRRRRR.EEE0).
*/

	*buf = 0;
	if (user==-2) strcpy(buf,"*:");
	if (user>=0) sprintf(buf,"%d:",user);

	strcat(buf,root);
	strcat(buf,".");
	if (*ext) {
		strcat(buf,ext);
	}
}


void build_cpm_name_32(char *buf, int user, char *root, char *ext) {
/*   ^^^^^^^^^^^^^^^^^
The same function as <build_cpm_name> except, that <root> and <ext> are
considered as padded with ' ' (= ASCII 32) (as in <DirEntry>)
*/
int	j;

	*buf = 0;
	if (user==-2)	{strcpy(buf,"*:"); buf +=2;}
	if (user>=0)	buf += sprintf(buf,"%d:",user);

	memcpy(buf,root,8);
	j=7; while (buf[j] == ' ') {j--;}; j++;
	buf[j]='.'; j++;
	if (strncmp(ext,"   ",3)!=0) {
		memcpy(&buf[j],ext,3); j+=2;
		while (buf[j] == ' ') {j--;}; j++;
	}
	buf[j]=0;
}


bool has_wildcards(char os_tag, char *name) {
/*   ^^^^^^^^^^^^^
Checks whether <name> contains wildcard characters according to match.h
(if <os_tag> == 'c' ( *?[] )) or to DOS standards (if <os_tag> == 'd'
( *? )).
*/
char	wild[10];
	if (os_tag=='c')	strcpy(wild,"*?[]");	/* !^- ??? */
	else if (os_tag == 'd') strcpy(wild,"*?");
	else return errorf(FALSE,"--==>>> has_wildcards");

	while (*name) {
		if (strchr(wild,*name) != NULL) return TRUE;
		name++;
	}
	return FALSE;
}


char *show_hex(int nr, uchar *buf, int size) {
/*    ^^^^^^^^
Formats a hexdump line out out <size> bytes at <buf>. Prefix the line with
<nr> as address. This function uses a static variable as temporary buffer, so
don't call it twice in a function.
*/
static char line[INPUTLEN];
int	i;
char	*p;
uchar	c;

	p = line;
	p += sprintf(p,"%6X %c ",nr,vert);
	for (i=0;i<size;i++) {
		p += sprintf(p,"%2X ",buf[i]);
	}
	p += sprintf(p," %c ",vert);
	for (i=0;i<size;i++) {
		if (buf[i]<32)		c=' ';
		else if (buf[i]>=127)	c='~';
		else			c=buf[i];
		p += sprintf(p,"%c",c);
	}
/*	*(p++) = '\n';*/
	return line;
}
