#include <exec/memory.h>
#include <dos/dos.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <clib/alib_protos.h>
#include <string.h>
#include "split.h"

/* for debug purposes */
#include <stdio.h>

/* in diskhandler.c */
extern struct Task *thisTask;
extern UBYTE diskBit;

/* end of external definitions */

char *str_unreadable, *str_unknown;

/* OK then if you really want to look in this source you'll find some
   hacks'n'things made for efficiency... not a good style of doing things
   but they work, and well! */


VolumeList *allocVolumeList( void )
{
VolumeList *newList;
if ( newList = AllocVec( sizeof(struct List), MEMF_ANY | MEMF_CLEAR ) )
	{
	NewList( newList );
	return newList;
	}
return NULL;
}


void freeVolumeList( VolumeList *list )
{
struct volumeInfo *v_info;
while ( v_info = (struct volumeInfo *)RemHead( (struct List *)list ) )
	{
	if ( v_info->volumeNode.ln_Name )
		FreeVec( v_info->volumeNode.ln_Name );
	if ( v_info->volumeName )
		FreeVec( v_info->volumeName );
	if ( v_info->deviceName )
		FreeVec( v_info->deviceName );
	FreeVec( v_info );
	}
FreeVec( list );
}


char *getStringFromBSTR( BSTR source )
{
UBYTE length = *(UBYTE*)(source<<2), counter;
char *name;

if ( name = AllocVec( length + 2, MEMF_ANY | MEMF_CLEAR ) )
	{
	for ( counter = 0; counter < length; counter++ )
		if ( (*(name+counter) = *(char*)((source<<2)+counter+1)) == 0 ) break;
	*(name+counter)=':';
	return name;
	}
return (char *)NULL;
}


char *getDosType( ULONG disktype )
{
switch( disktype )
	{
	case ID_UNREADABLE_DISK:
		return str_unreadable;
	case ID_DOS_DISK:
		return "DOS";
	case ID_FFS_DISK:
		return "FFS";
	case ID_INTER_DOS_DISK:
		return "Intl DOS";
	case ID_INTER_FFS_DISK:
		return "Intl FFS";
	case ID_FASTDIR_DOS_DISK:
		return "Cached DOS";
	case ID_FASTDIR_FFS_DISK:
		return "Cached FFS";
	case ID_NOT_REALLY_DOS:
		return "Not DOS";
	case ID_KICKSTART_DISK:
		return "Kickstart";
	case ID_MSDOS_DISK:
		return "MS-DOS";
	default:
		return str_unknown;
	}
}


/*
 * Given a specific volume name, returns the physical device on which
 * the volume is mounted.
 *
 */

char *getDeviceName( char *volumename )
{
struct MsgPort *vol_Task = DeviceProc( volumename );
struct DosList *dlist;
char *devicename;

dlist = LockDosList( LDF_DEVICES | LDF_READ );
while( dlist->dol_Task != vol_Task )
	dlist = NextDosEntry( dlist, LDF_DEVICES );
devicename = getStringFromBSTR( dlist->dol_Name );
UnLockDosList( LDF_DEVICES | LDF_READ );
return devicename;
}


/*
 * Searches through the DosList for all mounted volumes.
 *
 */

struct InfoData diskinfo;
BOOL searchForVolumes( VolumeList *list )
{
struct DosList *dlist;
struct volumeInfo *el;
BOOL gotInfo;

if ( dlist = LockDosList( LDF_VOLUMES|LDF_READ ) )
	{
	while( dlist = NextDosEntry( dlist, LDF_VOLUMES ) )
		{
		gotInfo = FALSE;
		if ( el = AllocVec( sizeof(struct volumeInfo), MEMF_ANY | MEMF_CLEAR ) )
			{
			if ( el->volumeName = getStringFromBSTR(dlist->dol_Name) )
				{
				BPTR lock;

				if ( lock = Lock( el->volumeName, ACCESS_READ ) )
					{
					if ( Info(lock,&diskinfo) )
						{
						el->dosType = getDosType(diskinfo.id_DiskType);
						el->freeSpace = (diskinfo.id_NumBlocks-diskinfo.id_NumBlocksUsed-1)*diskinfo.id_BytesPerBlock;
						el->freeBlocks = diskinfo.id_NumBlocks-diskinfo.id_NumBlocksUsed-1;
						el->bytesPerBlock = diskinfo.id_BytesPerBlock;
						el->diskType = diskinfo.id_DiskType;

						Enqueue( (struct List *)list, (struct Node *)el );
						gotInfo = TRUE;
						}
					UnLock( lock );
					}
				}
			}
		if ( !gotInfo )
			FreeVec( el );
		}
	UnLockDosList( LDF_VOLUMES|LDF_READ );
	}
else return FALSE;
return TRUE;
}


/*
 * Given a volume list, fills the device field. (See getDeviceName)
 *
 */

void searchForDevices( VolumeList *list )
{
struct volumeInfo *el = (struct volumeInfo *)list->lh_Head;

while ( el->volumeNode.ln_Pred != list->lh_TailPred )
	{
	el->deviceName = getDeviceName( el->volumeName );
	el = (struct volumeInfo *)el->volumeNode.ln_Succ;
	}
}

/*
 * Walks through a given volume list and checks for NDos (really for
 * MS-DOS) disks and fixes free space and disk type, as 
 * Info(...,diskinfo) does not report ID_MSDOS_DISK in diskinfo->di_DiskType
 *
 */


void searchForNDos( VolumeList *list )
{
struct volumeInfo *el = (struct volumeInfo *)list->lh_Head;
struct DosList *dlist;
struct MsgPort *vol_Task;

while ( el->volumeNode.ln_Pred != list->lh_TailPred )
	{
	vol_Task = DeviceProc( el->volumeName );
	dlist = LockDosList( LDF_VOLUMES | LDF_READ );
	while( dlist->dol_Task != vol_Task )
		dlist = NextDosEntry( dlist, LDF_VOLUMES );
	if ( dlist->dol_misc.dol_volume.dol_DiskType == ID_MSDOS_DISK )
		{
		el->diskType = ID_MSDOS_DISK;
		el->dosType = getDosType( ID_MSDOS_DISK );
		if ( (LONG)el->freeSpace < 0 )
			el->freeSpace = 0;
		}
	el = (struct volumeInfo *)el->volumeNode.ln_Succ;
	UnLockDosList( LDF_VOLUMES | LDF_READ );
	}
}


BOOL putNamesAndSpace( VolumeList *list )
{
struct volumeInfo *el;
UBYTE len, cnt;
ULONG size;
char *outString;

len = VOLUMENODENAMELEN;
el = (struct volumeInfo *)list->lh_Head;
while ( el->volumeNode.ln_Pred != list->lh_TailPred )
	if ( outString = AllocVec( len + 1, MEMF_ANY | MEMF_CLEAR ) )
		{
		el->volumeNode.ln_Name = outString;
		strncpy( outString, el->volumeName, MAXVOLNAME );
		strncpy( outString + MAXVOLNAME + 2, el->deviceName, MAXDEVNAME );
		strncpy( outString + MAXVOLNAME + MAXDEVNAME + 4, el->dosType, MAXDOSTYPENAME );
		for ( cnt = 0; cnt < len; cnt++ )
			if ( !*(outString+cnt) )
				*(outString+cnt)=' ';

		/* Small hack to get available RAM instead of 0 ... :-) */
		if ( !strcmp( el->deviceName, "RAM:" ) )
			{
			el->freeSpace = AvailMem(MEMF_ANY);
			el->freeBlocks = el->freeSpace / el->bytesPerBlock;
			}

		size = el->freeSpace;
		cnt = len;
		if ( !size )
			*(outString+cnt) = '0';
		else
			while( size )
				{
				*(outString+cnt) = '0' + size % 10;
				size /= 10;
				cnt--;
				}
		el = (struct volumeInfo *)el->volumeNode.ln_Succ;
		}
	else return FALSE;
return TRUE;
}


/*
 * This is the main routine. If everything works fine, it returns a
 * list composed of volumeInfo structures (see Split.h).
 *
 */


VolumeList *getVolumeList( VolumeList *list )
{
VolumeList *newList;

if ( list != NULL )
	freeVolumeList( list );

if ( newList = allocVolumeList() )
	{
	if ( searchForVolumes( newList ) )
		{
		searchForNDos( newList );
		searchForDevices( newList );
		if ( putNamesAndSpace( newList ) )
			return newList;
		}
	freeVolumeList( newList );
	}
return NULL;
}
