#include "fido.h"
#include "fidomem.h"
#include <ascii.h>
#include <xfbuf.h>
#include <filexfer.h>
#include "proto.h"

#define abs(x) ((x)<0?-(x):(x))
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<=(b)?(a):(b))

/* Search all FILES.BBS's in all directories, and list all matching files. */

void ffind() {
char name[SS];
int total,i,saved;

	getfield(name,string(CM+45),0,1,sizeof(name),0); /* "files to search for" */
	if (! *name) return;
	saved= filearea.number;			/* remember which area */
	total= 0;				/* none found yet, */

	abort= 0;
	for (i= 0; getfarea(i); i++) {		/* go through each area */
		pollkbd();			/* check Control-C, etc */
		if (abort) break;
		if (! allowed(&filearea)) continue;
		if (filearea.number == fido.netfarea) 
			dsparea(0,&filearea,')');		/* list each one, */
		else dsparea(0,&filearea,')');
		total+= file_disp(name);
	}
	mprintf(CM+42,total);
	getfarea(saved);
}
/* Change the default directory. */

void chdir() {
int saved,n,i;
char *cp,buff[SS];

	saved= filearea.number;				/* remember which area */

	n= -1;
	if (isargs()) {					/* if input follows, */
		n= atoi(getarg(FM+0)) - 1;		/* if a number, */
		if (!getfarea(n)) n= -1;		/* check area validity */
		else if (!allowed(&filearea)) n= -1;	/* and access */
	}

/* If not a valid selection, list the areas or display the fileinfo file. */

	if (n == -1) if (! dispbbs("fileinfo.bbs")) {
		mprintf(CM+43);				/* "Areas:" */
		abort= 0;
		for (i= 0; getfarea(i); i++) {
			if (abort) break;
			if (! allowed(&filearea)) continue;
			if (filearea.number == fido.netfarea) 
				dsparea(0,&filearea,')');/* list each one, */
			else dsparea(0,&filearea,')');
		}
	}
	while (1) {
		if (n >= 0) if (getfarea(n)) {		/* if a valid choice, */
			if (allowed(&filearea)) break;	/* take it as is */
		}
		cp= getarg(CM+44,saved + 1);		/* no, or bad number */
		if (!*cp) n= saved;			/* if blank no change, */
		else n= atoi(cp) - 1;			/* else get number, */
	}
}

/* Delete disk files and their names in FILES.BBS. */

void fdelete() {

char buff[SS],pat[SS];
char *cp,ln[SS];
struct _xfbuf xfbuf;
int i;

	cp= getarg(CM+261);		/* "filename" */
	if (! *cp) return;

	makefname(buff,"files.bbs");	/* download FILES.BBS, */
	cpyarg(pat,cp);			/* copy of the file spec, */
	kfile(buff,pat);		/* delete from FILES.BBS, */

	makefname(buff,pat);		/* delete the real files */
	i= 0; xfbuf.s_attrib= 0;
	while (_find(buff,i++,&xfbuf)) {
		makefname(ln,xfbuf.name);
		delete(ln);
	}
}

/* Delete all lines that contain a matching filename. Return 0 if no file(s)
deleted. */

int kfile(fname,pat)
char *fname,*pat;
{
char buff[SS],ln[200];
int f,o;

	f= open(fname,0);			/* open source file, */
	if (f == -1) return(0);			/* no file to delete from */

	strip_path(buff,fname);			/* put temp file in */
	strcat(buff,"FILES.$$$");		/* same directory/drive */
	o= creat(buff,2);			/* create output file */
	if (o == -1) {
		close(f);
		mprintf(CM+151,buff);		/* "SORRY ..." */
		lprintf(LM+39,buff);		/* "cant create!" */
		return(0);
	}
	while (rline(f,ln,sizeof(ln))) {
		if (*ln == SUB) break;		/* exit if Control-Z */
		cpyarg(buff,ln);		/* copy file name */

		if ( (*ln == '-') || (*ln == ' ') ||
		    (! _fmatch(pat,buff)) ) {	/* or mismatch */
			if (write(o,ln,strlen(ln)) != strlen(ln)) {
				mprintf(CM+45);
				lprintf(LM+16,fname);
				close(o);
				o= -1;
				break;
			}
			write(o,"\r\n",2);
		}
	}
	close(f);				/* close input */
	if (o != -1) {
		close(o);			/* save output */
		delete(fname);			/* delete and */
		strip_path(buff,fname);		/* make temp file */
		strcat(buff,"FILES.$$$");
		rename(buff,fname);		/* rename, */
	}
	return(o != -1);
}

/* Display the downloadable files from the file list. If a filespec is
specified, list only those matching it. In any case, check to see if the
file is really there. Display the actual file size. Return true if any were
found. */

int file_disp(s)
char *s;
{
struct _xfbuf xfbuf;
char line[200];			/* possibly long line */
char name[SS],pattern[SS],fname[SS];
int i,file,found;
long fsize;
FLAG comments,dotime,dodate;

	found= 0;
	makefname(line,"files.bbs");
	file= open(line,0);
	if (file == -1) {
		mprintf(CM+46);
		return(found);
	}

	comments= 0;			/* files only */
	cpyarg(pattern,s);		/* the pattern, */
	if (! *s) {			/* if no pattern supplied, */
		comments= 1;		/* display comments */
		strcpy(pattern,"*.*");	/* list all files */
	}

/* Read lines from FILES.BBS. Pull off the filename (supposed to be in col 0)
and see if it matches. If so, display it, then the file size, (or a msg if
it does not exist) then the rest of the line. Stop if we find aline beginning
with @ */

	dodate= dotime= 0;

	while (rline(file,line,sizeof(line)) && !abort) {
		cpyarg(name,line);			/* get filename, */
		name[12]= NUL;				/* for idiot proofing */
		stoupper(name);				/* make upper case */
		s= line;
		if (*s == '%') {			/* meta command */
			while (*++s) switch (tolower(*s)) {
				case 'd': dodate= 1; break;
				case 't': dotime= 1; break;
				case '-': dodate= dotime= 0; break;
			}
			continue;			/* next line ... */
		}

		if (*s == 26) break;			/* stop if ^Z */
		if ((*s == '@') && (uval(CLR_PRV,CLR_PRVS) < EXTRA))
			break;				/* stop if @ sign */

		if ((*s == '-') || (*s == ' ') || (*s == NUL) || (*s == '@')) {
			if (comments) {			/* display comments */
				mputs(s);		/* as is */
				mcrlf();
			}

		} else if (_fmatch(pattern,name)) {	/* see if fname match */
			s= next_arg(line);		/* ptr to after fname */

			makefname(fname,name);		/* full pathname */
			xfbuf.s_attrib= 0; i= 0;
			while (_find(fname,i,&xfbuf)) {	/* get info */
				++i;
				mprintf(0,"%-12s ",xfbuf.name); /* display the name, */
				if (dodate) dos_date(&xfbuf);
				if (dotime) dos_time(&xfbuf);
				mprintf(0,"%8lu %s\377",xfbuf.fsize,s);
				++found;		/* found one, */

			} 
			if (! i) {			/* no matching file(s) */
				mprintf(0,"%-12s ",name);	/* display the name, */
				if (dodate) mprintf(0,"          ");
				if (dotime) mprintf(0,"      ");
				mprintf(0,"%s%s\377",string(CM+47),s);
			}
		}
	}
	close(file);
	return(found);
}

/* Download files. */

void download() {
char c,*cp,trash[SS];
char name[SS];			/* name being processed */
char names[SS];			/* input list of names */
char list[200];			/* output list of names */
long msec;			/* DL start time, millisecs */
unsigned tleft,secs,ks,files,i,n;

	getfmode();				/* get file xfer mode, CRC, etc */
	if (filemode == -1) return;		/* quit if abort */
	getfield(names,string(CM+65),0,99,sizeof(names),1); /* "files" */
	cmdflush();				/* no typeahead now */
	if (*names == NUL) return;
	stolower(names);			/* pretty huh? */

/* Check each name inputted for reasonable length, non-device, and
that it exists. CHEAT: the input list ptr, cp, is used a an error
string pointer; if there is no error, then cp (the input list) will
be null. */

	files= 0; 				/* how many so far (none!) */
	cp= names;				/* input list of names */
	*list= NUL;				/* output list is empty */
	while (*cp) {				/* process each inputted name */
		cpyarg(name,cp);		/* one clean copy */
		cp= next_arg(cp);		/* next ... */
		cpyarg(name,strip_path(trash,name)); /* remove the path, */
		if (! *name) continue;		/* must have been path only */
		makefname(trash,name);		/* see if it exists */

		if (strlen(name) > strlen("FILENAME.EXT")) {
			cp= string(CM+84); 	/* "too long" */
			break;
		}
	    	if (same(name,"files.bbs") && (uval(CLR_PRV,CLR_PRVS) < EXTRA)) {
			cp= string(CM+154);	/* "reserved name" */
			break;
		}
		if (badname(name)) {		/* device names, etc */
			cp= string(CM+154);	/* "reserved name" */
			break;
		}
		if (! exist(trash)) {
			cp= string(CM+173);	/* "doesnt exist" */
			break;
		}
		if (strlen(list) + strlen(trash) < sizeof(list)) {
			strcat(list,trash);	/* OK, add to the list */
			if (*cp) strcat(list," "); /* seperate each name */
			++files;

		} else mprintf(CM+48,name);	/* "too many files" */
	}

/* (*cp) is null if we processed all the filenames ... */

	if (*cp) {				/* if there was an error, */
		mprintf(0,"\07\"%s\" %s\r\n",name,cp); /* report it */
		lprintf(LM+17,trash,cp);
		return;
	}

	if ((wild(list) || (files > 1)) && 
	    ((filemode == XMODEMC) || (filemode == XMODEM) || (filemode == ASCII))) {
		mprintf(CM+49);
		return;
	}
	lprintf(LM+18);				/* log the download list */
	lputs(list);

	mprintf(CM+50,names);			/* "ready to send" */
	xfer_time(list,&secs,&ks);		/* say how long it will take */

/* Check time limits and warn the user about various things. */

	n= (secs + 59) / 60;			/* min. time needed to download, */
	if (limit) {
		tleft= limit - minutes;		/* time remaining */

		if (n >= tleft) {		/* not enough time */
			lprintf(LM+19);
			mprintf(CM+51);
			return;
		}
		if (hlimit && ((tleft - n) < (n / 3))) {/* within 33% of the limit */
			lprintf(LM+20);
			mprintf(CM+52,limit - minutes);
			mprintf(CM+53);
			if (! yverf(CM+269)) return;
		}
	}

	if ((klimit > 0) &&			/* if a limit set, */
	 ((caller.dnldl + ks) > klimit)) { 	/* and exceeded, */
		lprintf(LM+21);
		if (uval(CLR_PRV,CLR_PRVS) < EXTRA) {
			if (! dispbbs("dnldl.bbs"))
				mprintf(CM+54);
			return;
		}
	}
	mprintf(CM+55);					/* "start now ..." */

	n= 0;						/* assume OK */
	msec= millisec;					/* DL start time */
	switch (filemode) {
		case ASCII: ascii_dl(list,1); break;
		case DIETIFNA:
		case XMODEM: case XMODEMC:
		case MODEM7: case MODEM7C:
		case TELINK: case TELINKC:  n= xsend(list,"",0,filemode); break;
		case ZMODEM: n= zsend(list,"",0); break;
		case KERMIT: n= ksend(list,"",0); break;
	}
	msec= millisec - msec;				/* remember DL elapsed time, */
	while (modin(100) != -1);			/* wait for quiet */
	mputs("\07\r\n");

	caller.dnld += tok(totl_bytes);			/* tally up caller stats */
	caller.dnldl += tok(totl_bytes);

	if (totl_files && (n < 0)) mprintf(CM+56);
	if (n < 0) switch (n) {
		case BADFILE: mprintf(CM+57); lprintf(LM+22); break;
		case ABORT: mprintf(CM+58); lprintf(LM+23);
		default: mprintf(CM+59); lprintf(LM+24);
	}
	lprintf(LM+25,totl_files);

	if (totl_files && (msec >= 60000L)) {	/* if over one minute */
		n= nearestn(msec,1000L);	/* time to nearest second, */
		i= totl_bytes / n;		/* bytes per second */
		mprintf(CM+60,totl_files,totl_bytes,n / 60,n % 60,i);

	} else mprintf(CM+61,totl_files);
}

/* Upload files */

void upload() {
char buff[SS],name[SS];		/* upload filename */
char *cp,*wp;
int i,n;

	if (! (filearea.flags & AREA_UPLD)) {
		mprintf(CM+62);			/* "uploading not allowed" */
		cmdflush();
		return;
	}
	getfmode();				/* get file xfer mode, CRC, etc */
	if (filemode == -1) return;		/* quit if abort */
	getfield(name,string(CM+65),0,1,sizeof(name),1); /* "file" */
	cmdflush();				/* no typeahead now */
	cpyarg(name,strip_path(buff,name));	/* strip off any path */
	if (*name == NUL) return;

	makeuname(buff,name);			/* name plus path */

	i= 0;					/* no error */
	if (strlen(name) > strlen("FILENAME.EXT")) i= CM+84;	/* "too long" */
	if (badname(name)) i= CM+154;		/* "reserved filename" */
    	if (same(name,"files.bbs") && (uval(CLR_PRV,CLR_PRVS) < EXTRA)) 
		i= CM+154;
	if (wild(name) && ((filemode == XMODEM) || (filemode == XMODEMC))) 
		i= CM+159;			/* "illegal transfer type" */
	if (wild(name) && (filemode == ASCII)) 
		i= CM+159;
	if (((filemode == XMODEM) || (filemode == XMODEMC) || (filemode == ASCII)) && (fexist(buff))) 
		i= CM+164;			/* "already exists" */

	if (i) {				/* see if there was anything wrong */
		mcrlf();			/* CR/LF */
		mprintf("\"%s\"",name);		/* the filename */
		mprintf(i);			/* the error code, */
		mcrlf();
		lprintf(LM+26,name,string(i));	/* log the error */
		return;
	}

	mprintf(CM+63,name);
	if (hlimit && limit) mprintf(CM+64,limit - minutes);
	mprintf(CM+55);				/* "start now" */

	*text= NUL;				/* empty upload list */
	switch (filemode) {
		case ASCII: n= ascii_ul(buff); strcpy(text,name); break;
		case DIETIFNA:
		case XMODEM: case XMODEMC:
		case MODEM7: case MODEM7C:
		case TELINK: case TELINKC: n= xrecv(buff,text,textsize,filemode); break;
		case ZMODEM: n= zrecv(buff,text,textsize); break;
		case KERMIT: n= krecv(buff,text,textsize); break;
	}

/* If there is less than 2 minutes, and there are files to describe, add two
minutes so that it can be done. */

	if (totl_files && (limit - minutes < 2)) minutes -= 2;

	while (modin(100) != -1);			/* wait for quiet */
	delay(200);					/* delay 2 more secs */
	mputs("\07\r\n");				/* beep */

	lprintf(LM+27);
	lputs(text);					/* log uploads */
	lprintf(FM+1);

	caller.upld += tok(totl_bytes);			/* tally user uploads */

	totl_bytes <<= 1;				/* double it */
	if (totl_bytes > caller.dnldl) caller.dnldl= 0;	/* give download credit (what a stupid idea) */
	else caller.dnldl -= totl_bytes;

	if ((totl_errors > 0) && (totl_files > 0)) mprintf(CM+66);
	if (n < 0) switch (n) {
		case ABORT: mprintf(CM+67); break;
		case DISKFULL: mprintf(CM+68); lprintf(LM+16,text); break;
		case BADFILE: mprintf(CM+69); break;
		default: mprintf(CM+59); break;
	}
	if (totl_files > 0) {
		mprintf(CM+71,totl_files);		/* "recvd X files" */
		mprintf(CM+8);
		cmdflush();
		descfile(text);
	}
}

/* Ask for and get the file transfer type. Return filemode set, or
set to -1 if aborted. */

static getfmode() {
FLAG crcmode;
char *cp,prompt[SS * 2];

	filemode= -1;

	while (filemode == -1) {
		if (uval(CLR_HLP,CLR_HLPS) >= REGULAR) cp= getarg(CM+262);
		else cp= getarg(CM+263);		/* prompt for filemode */
		if (! *cp) break;

		crcmode= (tolower(cp[1]) == 'c' ? 1 : 0); /* set CRC type, */

		if (cp[1] == '?') help(cp,0,"xfertype.hlp");
		else switch (tolower(cp[0])) {
			case '?': help("",0,"xfertype.hlp"); break;
			case 'a': mprintf(CM+72); filemode= ASCII; break;
			case 'z': mprintf(CM+73); filemode= ZMODEM; break;
			case 'k': mprintf(CM+74); filemode= KERMIT; break;
			case 't': mprintf(CM+75); filemode= TELINKC; break;
			case 'b': mprintf(CM+76); filemode= MODEM7; break;
			case 'd': mprintf(CM+77); filemode= DIETIFNA; break;
			case 'x': mprintf(CM+78); filemode= XMODEM; 
				if (crcmode) { mprintf(CM+79); filemode= XMODEMC; } break;
		}
	}
	if (filemode != -1) mprintf(CM+80);
}
/* Return true if the filespec exists. */

static exist(spec)
char *spec;
{
struct _xfbuf xfbuf;
char fspec[SS];

	cpyarg(fspec,spec);
	xfbuf.s_attrib= 0;
	return(_find(fspec,0,&xfbuf));
}

/* Type command; open the file and treat as an ASCII download. */

void ftype() {
char *fn,fname[SS],buff[SS];
int i;

	fn= getarg(CM+261);			/* "filename:" */
	if (! *fn) return;			/* stop if blank */

	cpyarg(fname,fn);			/* copy first word */
	makefname(buff,fname);			/* make the full filename */
	if (badname(buff)) {			/* check filename */
		mprintf(CM+81,fname);		/* "cant use that name" */
		return;
	}
	if (ascii_dl(buff,0) < 0) mprintf(CM+82,fname);  /* do the download */
	caller.dnld += tok(totl_bytes);		/* keep stats */
	caller.dnldl += tok(totl_bytes);
}

/* Download a file in ASCII. */

static ascii_dl(fn,download)
char *fn;
int download;		/* ...else T)ype */
{
int file,i;
char c;
unsigned count;		/* amt we've read */
unsigned alpha;		/* count of alphanumeric, tabs, CR, LF, etc */
unsigned other;		/* all other chars, */
char buff[SS];

	totl_files= totl_errors= 0;
	totl_bytes= 0L;

	cpyarg(buff,fn);			/* clean copy from the list */
	file= open(buff,0);			/* open it */
	if (file == -1) return(-2);		/* (doesnt exist) */

	if (download) {
		mprintf(CM+83);			/* "Control-S to pause, Control-C to abort... key to start" */
		mconflush();			/* flush it, */
		c= mconin();
		mcrlf();
		if (c == ETX) return(0);
	}

/* Before we attempt to type it, check to see what kind of file this is. By a
rude statistic check, I found that "typeable" files, from pure ASCII like
C and ASM sources, to WordStar files with the 8th bit set, the ratio of
printable (alphas, plus TAB, CR, LF) is 2:1 to the others. There is an
arbitrary lower bound of 100 chars else we dont check. */

	count= 0;
	alpha= 0; other= 0; 
	for (count= 0; count < 500; count++) {
		if (! read(file,&c,1)) break;
		c &= 0x7f;
		if (c == SUB) break;
		if ((c >= ' ') && (c <= '~')) ++alpha; else ++other;
	}
	if ((count > 200) && (alpha < (2 * other))) {
		mprintf(CM+85,fn);
		return(0);
	}
	++totl_files;					/* its OK, */
	lseek(file,0L,0);				/* back to the start */

/* Output the file. */

	while (read(file,&c,1)) {
		if (abort) break;
		if (c == SUB) break;

		switch (c & 0x7f) {
			case CR: case LF:
			case TAB: case BS:
				mconout(c); break;

			default:
				if ((c & 0x7f) < ' ') modout(c);
				else mconout(c);
		}
		++totl_bytes;			/* count it */
		if (download) line= 0;		/* disable "More?" thing */
	}
	mcrlf();
	close(file);
	if (download) {				/* if OK, */
		mconflush();			/* flush again, */
		delay(100);
		for (i= 0; i < 3; i ++) {
			mconout(BEL);
			if (modin(100) != -1) break;
		}
	}
	return(0);				/* no error */
}

/* Display the transfer time for the file. */

void xfer_time(s,secs,k)
char *s;
unsigned *secs,*k;
{
struct _xfbuf xfbuf;
char work[SS];
unsigned files,i;
long bytes;

	files= 0;
	bytes= 0L;
	xfbuf.s_attrib= 0;

	while (num_args(s)) {
		cpyarg(work,s);			/* copy one filename */
		s= next_arg(s);			/* for next time */
		i= 0;
		while (_find(work,i++,&xfbuf)) {
			++files;		/* tally files */
			bytes += xfbuf.fsize;	/* and bytes */
		}
	}
	*secs= bps(bytes); 			/* transfer time, */
	*k= tok(bytes); 			/* K bytes, */

	if (files > 1) mprintf(CM+86,files);
	mprintf(CM+87,bytes,*secs / 60,*secs % 60);
}

/* Convert a long byte count into the nearest short K bytes. */

static tok(l)
long l;
{
	return((l + 1023L) / 1024L);
}

/* Return the number of seconds to send this many bytes. */

static bps(size)
long size;
{
unsigned blksize,seconds,bytes_sec;

	bytes_sec= datarate / 10;		/* theoretical transfer time */
	seconds= size / bytes_sec;		/* basal transfer time */

	switch (filemode) {			/* fudge accordingly */
		case KERMIT: blksize= 80; break;
		case ZMODEM: blksize= 1024; break;
		default: blksize= 128; break;
	}

	if (filemode == KERMIT) seconds <<= 2;	/* times 2.0 for kermit */
	else seconds += (seconds / 20);		/* times 1.05 for XMODEM, */

	seconds += (size / blksize) / 3;	/* plus .33 secs blk */
	if (seconds == 0) seconds= 1;		/* at least 1 sec */
	return(seconds);
}
/* Do an ascii upload. Attempt to create the file, and upload to the
text buffer. We assume the guy can support XON/XOFF. */

static ascii_ul(name)
char *name;
{
int f;
char go,c,echoback;

	totl_files= totl_errors= 0;
	totl_bytes= 0L;

	f= creat(name,2);
	if (f == -1) return(-2);

	go= 1;
	echoback= yverf(CM+270);

	mprintf(CM+88);

	while (go) {
		line= 0;			/* no "More?" ! */
		c= mconin();

		switch (c) {
			case CR:	/* CR */
				totl_bytes += 2L;
				write(f,"\r\n",2);
				if (echoback) mcrlf();
				break;

			case SUB:	/* ^Z */
			case ETX:	/* ^C */
			case CAN:	/* ^X */
			case VT:	/* ^K */
				go= 0;
				break;

			case TAB:
				c= ' ';
			default:
				if (c < ' ') break;
				++totl_bytes;
				write(f,&c,1);
				if (echoback) mconout(c);
				break;
		}
	}
	close(f);

	mprintf(CM+89,totl_bytes);
	if (totl_bytes > 0L) ++totl_files;
	return(0);
}
