/***************************************************************************
 * FREE:	Display free space on your disk devices.
 *		Author:  Daniel J. Barrett, barrett@cs.umass.edu.
 *		Inspired by "FREE" by Tom Smythe on Fred Fish Disk #66,
 *		 but totally rewritten.  One function, AvailSpace(), is an
 *		 updated version of Smythe's function avail().
 *
 * This program is Freely Distributable.  Make all the copies you want
 * and give them away.  Use this code in any way you like.
 *
 * Changes/Improvements over Tom Smythe's program are:
 *
 *	o	Does not use self-modifying code, so it should work on
 *		ALL Amigas, not just 68000-based systems.
 *	o	You can specify a device list on the command line.
 *	o	You can specify a device list using an environment
 *		variable.
 *	o	If no device list is specified under AmigaOS 2.0 or higher,
 *		all disk volumes are listed.
 *	o	Requestors are turned off, so non-mounted devices are just
 *		skipped.  (See -m option.)
 *	o	Data is written in correct columns, regardless of the 
 *		lengths of the volume names.  (See -l option.)
 *	o	Total memory free is shown, along with CHIP and FAST
 *		subtotals.
 *	o	Command-line options added so the user can customize the
 *		program.
 *	o	Written in ANSI C, in a modular and easy-to-read style.
 *
 * Daniel J. Barrett makes no claims about the suitability of this program
 * for any particular purpose.  Any damages incurred through the use of
 * this program are entirely the responsibility of the USER.  Use at your
 * own risk.  So there!   :-)
 *
 ***************************************************************************/

#include "free.h"
#include <intuition/intuitionbase.h>
#include "version.h"


char *version = "$VER: Free " VERSION " " VERSION_DATE;

static char *outArray = NULL;
struct IntuitionBase *IntuitionBase;


main(int argc, char *argv[])
{
	InitializeGlobals();

	if ((argc == 2) && (EQUAL(argv[1], HELP_ARG)))
		Usage(argv[0]);
	else
		DoFree(argc, argv);

	ExitCleanly(NO_ERROR, RETURN_OK);
}


/* Process the user's command-line options.
 * Allocate the out[] array for output.
 * Disable requestors, if necessary.
 * Measure the available RAM.
 * Then, measure free space on each desired device. 
 * Print the output in a quick manner, using Write().
 * Turn requestors back on, if they were turned off.
 */

void DoFree(int argc, char *argv[])
{
	long chipRam, fastRam;
	int argIndex;

	argIndex = GetOptions(argc, argv);
	outArray = MakeOutArray();

	if (!(flags & FLAG_REQUESTORS))
	 	DisableRequestors();

	GetFreeRam(&chipRam, &fastRam);

	if (argIndex >= argc)
		FreeUsingEnv(chipRam, fastRam);
	else
		FreeFromArgs(argv+argIndex, chipRam, fastRam);

	Write(Output(), outArray, (long)strlen(outArray));  /* For speed. */

	if (!(flags & FLAG_REQUESTORS))
	 	EnableRequestors();
}


/* The user specified no volumes as command-line arguments.  So, check
 * the environment variable.  If it has no value, then use DEFAULT_VOLUMES
 * instead.
 * We use the WONDERFUL function strtok() iteratively, to get the name of
 * each volume, one at a time. */

void FreeUsingEnv(long chipRam, long fastRam)
{
	char *volume, *volumeList;
	static char *defaultVolumes = DEFAULT_VOLUMES_OLD;
	BOOL newversion = FALSE;
	
	IntuitionBase = (struct IntuitionBase *)
			OpenLibrary("intuition.library", 36);
	if (IntuitionBase)
	{
		newversion = TRUE;
		CloseLibrary(IntuitionBase);
	}

	volumeList = getenv(ENV_VARIABLE);
	if (!volumeList && newversion)
		FreeUsingDosList(chipRam, fastRam);
	else
	{
		if (!volumeList)
			volumeList = defaultVolumes;

		volume = strtok(volumeList, ENV_SEPARATOR);
		while (volume)
		{
			ShowFree(volume, chipRam, fastRam, outArray);
			volume = strtok(NULL, ENV_SEPARATOR);
		}
	}
}


void FreeUsingDosList(long chipRam, long fastRam)
{
	struct DosList *dlist, *saveDList;
	char *name;
	long len, maxLen = 0;
	char volume[MAX_NAME_LEN + 1];
	
	dlist = (struct DosList *)LockDosList(LDF_VOLUMES | LDF_READ);
	if (!dlist)
		ExitCleanly(ERROR_DOSLIST, RETURN_FAIL);
	else
		saveDList = dlist;

	while (dlist = (struct DosList *)NextDosEntry(dlist, LDF_VOLUMES))
	{
		name = BADDR(dlist->dol_Name);
		if (name[0] > volumeNameLen)
			maxLen = (name[0] > maxLen) ? name[0] : maxLen;
	}

	if (maxLen > volumeNameLen)
		volumeNameLen = maxLen;

	ShowFree("RAM:", chipRam, fastRam, outArray);
	
	dlist = saveDList;
	while (dlist = (struct DosList *)NextDosEntry(dlist, LDF_VOLUMES))
	{
		name	= BADDR(dlist->dol_Name);
		len	= name[0];
		name++;

		if (name)
		{
			strncpy(volume, name, len);
			volume[len]	= ':';
			volume[len+1]	= '\0';
			ShowFree(volume, chipRam, fastRam, outArray);
			
		}
	}

	UnLockDosList(LDF_VOLUMES | LDF_READ);
}


/* The user specified volumes on the command-line.  Check each one. */

void FreeFromArgs(char *argv[], long chipRam, long fastRam)
{
	while (*argv)
		ShowFree(*argv++, chipRam, fastRam, outArray);
}


/* Process the user's command-line options.
 * Return the index of the first argument appearing AFTER all options. */

int GetOptions(int argc, char *argv[])
{
	register char c;
	long m;

	while ((c = getopt(argc, argv, OPTSTRING)) != EOF)
	{
		switch (c)
		{
			case OPT_BLOCKS:
				flags |= FLAG_BLOCKS;
				break;
			case OPT_REQUESTORS:
				flags |= FLAG_REQUESTORS;
				break;
			case OPT_MALLOC:
				flags |= FLAG_MALLOC;
				if ((m = atoi(optarg)) > 0)
					memSize = m;
				break;
			case OPT_VOLUME_NAME_LEN:
				flags |= FLAG_VOLUME_NAME_LEN;
				volumeNameLen = atoi(optarg);
				CheckVolumeNameLen(&volumeNameLen);
				break;
			case OPT_UNKNOWN:
				break;
			default:	/* Sanity check! */
				ExitCleanly(ERROR_IMPOSSIBLE, RETURN_FAIL);
				break;
		}
	}
	return(optind);
}


/* Deallocate memory, print a message, and exit. */
void ExitCleanly(ERROR errorCode, int exitCode)
{
	if (outArray)
		free(outArray);
	if (errorCode)
		ErrorMsg(errorCode);
	exit(exitCode);
}


/* Like the name says... initialize our global variables. */

void InitializeGlobals()
{
	flags		= 0L;
	memSize		= DEFAULT_MEMORY_LIMIT;
	volumeNameLen	= DEFAULT_NAME_LEN;
}
