// --------------------------------------------------------------------------
// Citadel: Files.CPP
//
// File handling routines.

#include "ctdl.h"
#pragma hdrstop

#include "room.h"
#include "msg.h"
#include "log.h"

// --------------------------------------------------------------------------
// Contents
//
// ambig()				returns true if filename is ambiguous
// ambigUnlink()		unlinks ambiguous filenames
// bytesfree()			returns #bytes free on current drive
// IsFilenameLegal()	Is it?
// dltime() 			computes dl time from size & global rate
// filldirectory()		fills our directory structure
// hide()				hides a file. for limited-access u-load
// copyfile()			Copy file function. (and test code)

Bool haveRespProt(void)
	{
	for (const protocols *theProt = extProtList; theProt;
			theProt = (protocols *) getNextLL(theProt))
		{
		if (theProt->respDown[0])
			{
			return (TRUE);
			}
		}

	return (FALSE);
	}

const char *nameonly(const char *full)
	{
	if (strrchr(full, '\\'))
		{
		return (strrchr(full, '\\') + 1);
		}
	else
		{
		return (full);
		}
	}


// --------------------------------------------------------------------------
// ambig(): Returns TRUE if string is an ambiguous filename.

int ambig(const char *filename)
	{
	return (strchr(filename, '*') || strchr(filename, '?'));
	}


// --------------------------------------------------------------------------
// ambigunlink(): This routine will fill the directory structure according to
//	wildcard.

int ambigUnlink(const char *filename, Bool change)
	{
	struct find_t file_buf;
	int count = 0;

	if (change)
		{
		if (!changedir(GetCurrentRoomDirectory()))
			{
			return (0);
			}
		}

	// keep going till it errors, which is end of directory
	for (int ax = _dos_findfirst(starstar, _A_NORMAL, &file_buf); ax == 0;
			ax = _dos_findnext(&file_buf))
		{
		if (!strpos('.', file_buf.name))
			{
			strcat(file_buf.name, getmsg(1433));
			}

		// filename match wildcard?
		if (u_match(file_buf.name, filename) && !(file_buf.attrib & _A_VOLID))
			{
			if (!unlink(file_buf.name))
				{
				count++;
				}
			}
		}
	return (count);
	}


// --------------------------------------------------------------------------
// bytesfree(): Returns # bytes free on drive.

long bytesfree()
	{
	char path[64];
	union REGS REG;

	getcwd(path, 64);

	REG.h.ah = 0x36;	// select drive

	REG.h.dl = (uchar)(path[0] - '@');

	intdos(&REG, &REG);

	return ((long) ((long) REG.x.cx * (long) REG.x.ax * (long) REG.x.bx));
	}


// --------------------------------------------------------------------------
// IsFilenameLegal(): Is it?

Bool IsFilenameLegal(const char *filename, Bool AllowSpace)
	{
	char *s;
	FILE *fl;
	const char *invalid = getmsg(798);

	if (AllowSpace)
		{
		invalid++;
		}

	if (strpbrk(invalid, filename) != NULL)
		{
		return (FALSE);
		}

	char device[14];
	CopyStringToBuffer(device, filename, sizeof(device) - 1);

	if ((s = strchr(device, '.')) != NULL)
		{
		*s = '\0';
		}

	if ((fl = fopen(filename, FO_RB)) == NULL)
		{
		return (TRUE);
		}

	if (isatty(fileno(fl)))
		{
		fclose(fl);
		return (FALSE);
		}

	fclose(fl);
	return (TRUE);
	}

int cdecl fwdstrcmp(void *a, void *b)
	{
	return (strcmp(((directoryinfo *) a)->name, ((directoryinfo *) b)->name));
	}

int cdecl revstrcmp(void *a, void *b)
	{
	return (-fwdstrcmp(a, b));
	}

static int cdecl old_to_new_cmp(int *a, int *b)
	{
	if (((directoryinfo *) a)->date != ((directoryinfo *) b)->date)
		{
		return (((directoryinfo *) a)->date - ((directoryinfo *) b)->date);
		}

	return (((directoryinfo *) a)->time - ((directoryinfo *) b)->time);
	}

static int cdecl new_to_old_cmp(void *a, void *b)
	{
	return (-old_to_new_cmp((int *) a, (int *) b));
	}


// --------------------------------------------------------------------------
// filldirectory(): This routine will fill the directory structure according
//	to wildcard

directoryinfo *filldirectory(const char *temp, int sort, OldNewPick which,
		Bool rev)
	{
	int i;
	struct find_t file_buf;
	label filename;

	struct date fdate;
	struct time ftime;

	ftime.ti_min = 0;
	ftime.ti_hour = 0;
	ftime.ti_hund = 0;
	ftime.ti_sec = 0;

	// to avoid possible memory corruption later
	CopyStringToBuffer(filename, temp, LABELSIZE);
	if (!strpos('.', filename) && (strlen(filename) < LABELSIZE))
		{
		strcat(filename, getmsg(1433));
		}

	stripdoubleasterisk(filename);

	directoryinfo *files = new directoryinfo[cfg.maxfiles];

	if (files == NULL)
		{
		mPrintfCR(getmsg(447));
		return (NULL);
		}

	const int filetypes = (TI()CurrentUser->IsAide() ? _A_HIDDEN : 0);

	// keep going till it errors, which is end of directory
	int ax;
	for (i = 0, ax = _dos_findfirst(starstar, filetypes, &file_buf);
			ax == 0 && i + 1 < cfg.maxfiles;
			ax = _dos_findnext(&file_buf), ++i)
		{
		Bool strip;

		if (which == NewOnly || which == OldOnly)
			{
			fdate.da_year = (file_buf.wr_date >> 9) + 1980;
			fdate.da_day = file_buf.wr_date & 31;
			fdate.da_mon = (file_buf.wr_date >> 5) & 15;

			long funix = dostounix(&fdate, &ftime);

			if (which == NewOnly)
				{
				if (funix < TI()MRO.CheckDate)
					{
					i--;
					continue;
					}
				}
			else
				{
				if (funix > TI()MRO.CheckDate)
					{
					i--;
					continue;
					}
				}
			}

		if (!strpos('.', file_buf.name))
			{
			strcat(file_buf.name, getmsg(1433));
			strip = TRUE;
			}
		else
			{
			strip = FALSE;
			}

		if ((!(file_buf.attrib & _A_SUBDIR)) &&
				// filename match wildcard?
				u_match(file_buf.name, filename) &&

				// never the volume name
				!(file_buf.attrib & _A_VOLID))
			{
			if (strip)
				{
				file_buf.name[strlen(file_buf.name)-1] = 0;
				}

			files[i].attrib = file_buf.attrib;
			files[i].time = file_buf.wr_time;
			files[i].date = file_buf.wr_date;
			files[i].size = file_buf.size;
			strcpy(files[i].name, file_buf.name);
			strlwr(files[i].name);
			}
		else
			{
			i--;
			}
		}

	files[i].name[0] = 0;

	// alphabetical order
	if (sort == 1)
		{
		qsort(files, i, sizeof(directoryinfo),
				(rev) ? (QSORT_CMP_FNP) revstrcmp :
				(QSORT_CMP_FNP) fwdstrcmp);
		}

	// by date
	else if (sort == 2)
		{
		qsort(files, i, sizeof(directoryinfo),
				(rev) ? (QSORT_CMP_FNP) new_to_old_cmp :
				(QSORT_CMP_FNP) old_to_new_cmp);
		}

	return (files);
	}


// --------------------------------------------------------------------------
// dltime(): Give this routine the size of your file and it will return the
//	amount of time it will take to download according to speed.

const char *dltime(long size)
	{
	long seconds;

	// could be more accurate
	if (SerialPort.HaveCarrier())
		{
		seconds = (size / (connectbauds[TI()ModemSpeed] / 10L));
		}
	else
		{
		seconds = (size / (bauds[TI()SerialPort.GetSpeed()] / 10L));
		}

	const long hours = (seconds / 3600);
	seconds %= 3600;
	const long minutes = (seconds / 60);
	seconds %= 60;

	sprintf(TI()dlTimeBuff, getmsg(797), hours, minutes, seconds);

	return (TI()dlTimeBuff);
	}


// --------------------------------------------------------------------------
// hide(): Hides a file. for limited-access u-load.

void hide(const char *filename)
	{
	_chmod(filename, 1, _chmod(filename, 0) | FA_HIDDEN);
	}

#define MAXBUF	32*1024 // 32K


// --------------------------------------------------------------------------
// copyfile(): Copy file function. (and test code).

Bool copyfile(const char *source, const char *dest)
	{
	FILE *sfl, *dfl;

	if ((sfl = fopen(source, FO_RB)) == NULL)
		{
		if (debug)
			{
			doccr();
			cPrintf(getdbmsg(87), source, dest);
			cPrintf(getdbmsg(14));
			doccr();
			}

		return (FALSE);
		}

	if ((dfl = fopen(dest, FO_WB)) == NULL)
		{
		if (debug)
			{
			doccr();
			cPrintf(getdbmsg(87), source, dest);
			cPrintf(getdbmsg(14));
			doccr();
			}

		fclose(sfl);
		return (FALSE);
		}

	if (debug)
		{
		doccr();
		cPrintf(getdbmsg(87), source, dest);
		}

	char *Buffer;
	int BufferLength;
	char StackBuffer[256];
	Bool Allocated = FALSE;

	for (BufferLength = MAXBUF; !Allocated && BufferLength > 256;
			BufferLength -= 1024)
		{
		Buffer = new char[BufferLength];

		if (Buffer)
			{
			Allocated = TRUE;
			}
		}

	if (!Allocated)
		{
		Buffer = StackBuffer;
		BufferLength = 256;
		}

	int bytes;
	do
		{
		if ((bytes = fread(Buffer, 1, BufferLength, sfl)) != 0)
			{
			if (fwrite(Buffer, 1, bytes, dfl) != bytes)
				{
				fclose(sfl);
				fclose(dfl);

				if (debug)
					{
					cPrintf(getdbmsg(14));
					doccr();
					}

				unlink(dest);

				if (Allocated)
					{
					delete [] Buffer;
					}

				return (FALSE);
				}
			}
		} while (bytes == BufferLength);

	fclose(sfl);
	fclose(dfl);

	if (Allocated)
		{
		delete [] Buffer;
		}

	if (debug)
		{
		cPrintf(getdbmsg(13));
		doccr();
		}

	return (TRUE);
	}
