/*
 *                          DiskSpeed v2.0
 *                                by
 *                           Michael Sinz
 *
 *             Copyright (c) 1989 by MKSoft Development
 *
 *
 * Yes, this is yet another disk speed testing program, but with a few
 * differences.  It was designed to give the most accurate results of the
 * true disk performance in the system.  For this reason many of
 * DiskSpeed's results may look either lower or higher than current disk
 * performance tests.
 *
 * This program was thrown together in a few hours because I needed more
 * accurate and consistent results for disk performance as seen from the
 * application's standpoint.  This program has now served its purpose and
 * I am now giving it to the rest of the Amiga world to play with as long
 * as all of the files remain together in unmodified form.  (That is, the
 * files DiskSpeed, DiskSpeed.info, DiskSpeed.c, DiskSpeedWindow.c,
 * DiskSpeedWindow.h, MakeBoxes.c, MakeBoxes.h, StandardGadgets.c,
 * StandardGadgets.h, RenderInfo.c, RenderInfo.h, DiskSpeed.doc, and
 * MakeFile)
 *
 * Version 2.0 of this program added a few features and cleaned up the
 * user interface.  I hope you like this...
 *
 ******************************************************************************
 *									      *
 *	Reading legal mush can turn your bain into guacamole!		      *
 *									      *
 *		So here is some of that legal mush:			      *
 *									      *
 * Permission is hereby granted to distribute this program's source	      *
 * executable, and documentation for non-commercial purposes, so long as the  *
 * copyright notices are not removed from the sources, executable or	      *
 * documentation.  This program may not be distributed for a profit without   *
 * the express written consent of the author Michael Sinz.		      *
 *									      *
 * This program is not in the public domain.				      *
 *									      *
 * Fred Fish is expressly granted permission to distribute this program's     *
 * source and executable as part of the "Fred Fish freely redistributable     *
 * Amiga software library."						      *
 *									      *
 * Permission is expressly granted for this program and it's source to be     *
 * distributed as part of the Amicus Amiga software disks, and the	      *
 * First Amiga User Group's Hot Mix disks.				      *
 *									      *
 ******************************************************************************
 *
 * Main code and testing sections...
 */

#include	<EXEC/Types.h>
#include	<EXEC/Memory.h>
#include	<Intuition/Intuition.h>
#include	<Libraries/DOS.h>
#include	<Libraries/DOSextens.h>
#include	<Graphics/RastPort.h>
#include	<Devices/Timer.h>

#include	<PROTO/All.h>

#include	<string.h>

#include	"RenderInfo.h"
#include	"DiskSpeedWindow.h"

extern	struct	IntuitionBase	*IntuitionBase;
	struct	Library		*TimerBase;

#define	TIMESCALER		256L
#define	SEEK_READ_SIZE		300L

#define	IO_BUFFER_SIZE		262144L

/* Define the file name limits for file create/delete/open/scan */
#define	FINAL	'R'

static char ErrorText1[28]	="Can't create test directory";
static char ErrorText2[16]	="Bad device name";
static char ErrorText3[24]	="Can't open results file";
static char ErrorText4[19]	="Can't open printer";

static char PrinterFile[5]	="PRT:";
static char ResultFile[18]	="DiskSpeed.Results";
static char DirectoryName[22]	=" Disk Test Directory ";
static char FileNameRoot[22]	=" -- DiskTest File -- ";

static char Testing_String[10]	=">Testing<";

/* The Wait pointer I use... */
USHORT __chip WaitPointer[48] =
{
	0x0000, 0x0000,	0x6700, 0xC700,	0xCFA0, 0xCFA0,	0xBFF0, 0x3FF0,
	0x70F8, 0x7FF8,	0x7DFC, 0x7FFC,	0xFBFC, 0xFFFC,	0x70FC, 0x7FFC,
	0x7FFE, 0x7FFE,	0x7F0E, 0x7FFE,	0x3FDF, 0x3FFF,	0x7FBE, 0x7FFE,
	0x3F0E, 0x3FFE,	0x1FFC, 0x1FFC,	0x07F8, 0x07F8,	0x01E0, 0x01E0,
	0x0780, 0x0780,	0x0FE0, 0x0FE0,	0x0740, 0x0740,	0x0000, 0x0000,
	0x0070, 0x0070,	0x0078, 0x0078,	0x0030, 0x0030,	0x0000, 0x0000,
};

/*
 * These two defines set up and clear the WaitPointer...
 */
#define	SetWait(x)	SetPointer(x,WaitPointer,22L,16L,NULL,NULL)
#define	ClearWait(x)	ClearPointer(x)

/*
 * This does a requester on the window to tell the user something...
 */
VOID TellUser(struct MyWindow *MyWindow,char *Text)
{
register	struct	IntuiMessage	*msg;
register		SHORT		flag=TRUE;

	MyWindow->ReqIText.LeftEdge=(MyWindow->Req.Width>>1)-(strlen(Text)<<2);
	MyWindow->ReqIText.IText=Text;

	Request(&(MyWindow->Req),MyWindow->Window);
	while (flag)
	{
		WaitPort(MyWindow->Window->UserPort);
		while (msg=(struct IntuiMessage *)GetMsg(MyWindow->Window->UserPort))
		{
			if (msg->Class==GADGETUP)
			{
				if (((struct Gadget *)(msg->IAddress))->GadgetID==GADGET_REQ) flag=FALSE;
			}
			ReplyMsg((struct Message *)msg);
		}
	}
	EndRequest(&(MyWindow->Req),MyWindow->Window);
}

/*
 * Returns FALSE if the user clicked on the CLOSE gadget...
 */
SHORT Check_Quit(struct MyWindow *MyWindow)
{
register	struct	IntuiMessage	*msg;
register		SHORT		flag=TRUE;

	while(msg=(struct IntuiMessage *)GetMsg(MyWindow->Window->UserPort))
	{
		if (msg->Class==CLOSEWINDOW) flag=FALSE;
		ReplyMsg((struct Message *)msg);
	}
	return(flag);
}

/*
 * Clears a string entry to spaces...
 */
VOID Clear_Entry(char *String)
{
register	SHORT	t;

	for (t=0;t<9;t++) String[t]=' ';
	String[9]='\0';
}

/*
 * Sets a string entry to the string >Testing< and does a
 * PrintIText() to display it...
 */
VOID Set_Testing(SHORT ResultNum,struct MyWindow *MyWindow)
{
	strcpy(MyWindow->MyResults[ResultNum].text,Testing_String);
	PrintIText(MyWindow->Window->RPort,&(MyWindow->MyResults[ResultNum].IntuiText),NULL,NULL);
}

/*
 * Sets a string entry to the value passed and does a PrintIText()
 * to display it...
 */
VOID Set_Entry(SHORT ResultNum,LONG Value,struct MyWindow *MyWindow)
{
register	SHORT	t;
register	SHORT	tmp;
register	char	*String;

	String=MyWindow->MyResults[ResultNum].text;
	Clear_Entry(String);
	t=9;
	while (Value)
	{
		tmp=Value%10;
		Value=Value/10;
		String[--t]=(char)(tmp)+'0';
	}
	if (!String[t]) String[--t]='0';
	PrintIText(MyWindow->Window->RPort,&(MyWindow->MyResults[ResultNum].IntuiText),NULL,NULL);
}

/*
 * This gets the starting time...
 */
VOID Timer_Start(struct MyWindow *MyWindow)
{
register	struct	timerequest	*Time_Req;

	Time_Req=&(MyWindow->TimeReq);

	Time_Req->tr_node.io_Command=TR_GETSYSTIME;
	Time_Req->tr_node.io_Flags=IOF_QUICK;
	DoIO((struct IORequest *)Time_Req);
	MyWindow->StartTime=Time_Req->tr_time;
}

/*
 * This gets the ending time and finds out what the per second speed is...
 * It also does a RefreshGadgets() to display the text...
 */
VOID Timer_Stop(struct MyWindow *MyWindow,SHORT ResultNum,LONG Number)
{
register	struct	timerequest	*Time_Req;
register		LONG		Value;

	Time_Req=&(MyWindow->TimeReq);

	Time_Req->tr_node.io_Command=TR_GETSYSTIME;
	Time_Req->tr_node.io_Flags=IOF_QUICK;
	DoIO((struct IORequest *)Time_Req);
	MyWindow->StopTime=Time_Req->tr_time;

	SubTime(&(MyWindow->StopTime),&(MyWindow->StartTime));

	/* Now calculate Value based on Number and the time... */
	Value=(Number*TIMESCALER) /
		(	(MyWindow->StopTime.tv_secs*TIMESCALER) +
			(MyWindow->StopTime.tv_micro/(1000000L/TIMESCALER))
		);

	Set_Entry(ResultNum,Value,MyWindow);
}

/*
 * File create test...
 * This creates a bunch of files in our test directory...
 * The delete test deletes these files...
 */
VOID Do_FileCreate_Test(struct MyWindow *MyWindow)
{
register	char	*t1;
register	char	*t2;
register	BPTR	file;
register	LONG	count=0;

	t1=FileNameRoot+1;
	t2=t1+1;

	Set_Testing(RESULTS_CREATE,MyWindow);
	Timer_Start(MyWindow);
	for (*t1='A';*t1<FINAL;(*t1)++) for (*t2='A';*t2<FINAL;(*t2)++)
	{
		if (file=Open(FileNameRoot,MODE_NEWFILE)) Close(file);
		count++;
	}
	Timer_Stop(MyWindow,RESULTS_CREATE,count);
}

/*
 * File Open/Close test...
 * This Opens/Closes a bunch of files in our test directory...
 */
VOID Do_OpenClose_Test(struct MyWindow *MyWindow)
{
register	char	*t1;
register	char	*t2;
register	BPTR	file;
register	LONG	count=0;

	t1=FileNameRoot+1;
	t2=t1+1;

	Set_Testing(RESULTS_OPEN_CLOSE,MyWindow);
	Timer_Start(MyWindow);
	for (*t1='A';*t1<FINAL;(*t1)++) for (*t2='A';*t2<FINAL;(*t2)++)
	{
		if (file=Open(FileNameRoot,MODE_OLDFILE)) Close(file);
		count++;
	}
	Timer_Stop(MyWindow,RESULTS_OPEN_CLOSE,count);
}

/*
 * Directory scan test...  This is done by scanning the directory we
 * just created in the create test... (Three times...)
 */
VOID Do_DirScan_Test(struct MyWindow *MyWindow)
{
register	struct	FileInfoBlock	*FIB;
register		BPTR		lock;
register		LONG		count=0;
register		SHORT		loop;

	lock=CurrentDir(NULL);	/* Get our directory lock... */
	if (FIB=AllocMem(sizeof(struct FileInfoBlock),MEMF_PUBLIC))
	{
		Set_Testing(RESULTS_DIR_SCAN,MyWindow);
		Timer_Start(MyWindow);
		for (loop=0;loop<3;loop++)
		{
			Examine(lock,FIB);
			while (ExNext(lock,FIB)) count++;
		}
		Timer_Stop(MyWindow,RESULTS_DIR_SCAN,count);

		FreeMem(FIB,sizeof(struct FileInfoBlock));
	}
	CurrentDir(lock);	/* Return the locked directory... */
}

/*
 * File delete test...  We delete the files created in the create test...
 */
VOID Do_FileDelete_Test(struct MyWindow *MyWindow)
{
register	char	*t1;
register	char	*t2;
register	LONG	count=0;

	t1=FileNameRoot+1;
	t2=t1+1;

	Set_Testing(RESULTS_DELETE,MyWindow);
	Timer_Start(MyWindow);
	for (*t1='A';*t1<FINAL;(*t1)++) for (*t2='A';*t2<FINAL;(*t2)++)
	{
		DeleteFile(FileNameRoot);
		count++;
	}
	Timer_Stop(MyWindow,RESULTS_DELETE,count);
}

/*
 * This does the seek/read test...
 */
VOID Do_SeekRead_Test(struct MyWindow *MyWindow,char *Buffer)
{
register	short	t;
register	LONG	count=0;
register	LONG	pos;
register	BPTR	bigfile;

	if (bigfile=Open(FileNameRoot,MODE_NEWFILE))
	{
		Set_Testing(RESULTS_SEEK_READ,MyWindow);
		Write(bigfile,Buffer,IO_BUFFER_SIZE);
		Timer_Start(MyWindow);
		for (t=0;t<200;t++)
		{
			Seek(bigfile,0L,OFFSET_BEGINNING);
			Read(bigfile,Buffer,SEEK_READ_SIZE);
			count++;

			pos=Seek(bigfile,-(2*SEEK_READ_SIZE),OFFSET_END);
			Read(bigfile,Buffer,SEEK_READ_SIZE);
			count++;

			Seek(bigfile,-(pos>>1),OFFSET_CURRENT);
			Read(bigfile,Buffer,SEEK_READ_SIZE);
			count++;
		}
		Timer_Stop(MyWindow,RESULTS_SEEK_READ,count);
		Close(bigfile);
		DeleteFile(FileNameRoot);
	}
}

/*
 * This does the testing at a set buffer size...
 */
SHORT Do_File_Test(LONG BufSize,struct MyWindow *MyWindow,UBYTE *Buffer,SHORT ResultNum)
{
register	SHORT	flag=TRUE;
register	SHORT	t;
register	SHORT	loop;
register	LONG	count;
register	LONG	numblocks;
register	BPTR	bigfile;

	if (bigfile=Open(FileNameRoot,MODE_NEWFILE))
	{
		numblocks=IO_BUFFER_SIZE/BufSize;
		if (flag&=Check_Quit(MyWindow))
		{
			count=0;
			Set_Testing(ResultNum,MyWindow);
			Timer_Start(MyWindow);
			for (t=0;t<(1<<(MyWindow->TestFlag));t++)
			{
				Close(bigfile);
				bigfile=Open(FileNameRoot,MODE_NEWFILE);
				for (loop=0;loop<numblocks;loop++) count+=Write(bigfile,Buffer,BufSize);
			}
			Timer_Stop(MyWindow,ResultNum++,count);
		}
		if (flag&=Check_Quit(MyWindow))
		{
			count=0;
			Set_Testing(ResultNum,MyWindow);
			Timer_Start(MyWindow);
			for (t=0;t<(2<<(MyWindow->TestFlag));t++)
			{
				Seek(bigfile,NULL,OFFSET_BEGINNING);
				for (loop=0;loop<numblocks;loop++) count+=Write(bigfile,Buffer,BufSize);
			}
			Timer_Stop(MyWindow,ResultNum++,count);
		}
		if (flag&=Check_Quit(MyWindow))
		{
			count=0;
			Set_Testing(ResultNum,MyWindow);
			Timer_Start(MyWindow);
			for (t=0;t<(3<<(MyWindow->TestFlag));t++)
			{
				Seek(bigfile,NULL,OFFSET_BEGINNING);
				for (loop=0;loop<numblocks;loop++) count+=Read(bigfile,Buffer,BufSize);
			}
			Timer_Stop(MyWindow,ResultNum++,count);
		}
		Close(bigfile);
		DeleteFile(FileNameRoot);
	}
	return(flag);
}

/*
 * This is the master test routine...  It calles the ones above...
 */
VOID Do_Disk_Test(struct MyWindow *MyWindow)
{
register		BPTR	lock;
register		BPTR	mydir;
register		SHORT	flag=TRUE;
register		UBYTE	*Buffer;
register		SHORT	loop;
register	struct	Gadget	*gad;

	for (loop=0;loop<NUM_GADGETS;loop++)
	{
		gad=&(MyWindow->MyGadgets[loop].Gadget);
		RemoveGadget(MyWindow->Window,gad);
		gad->Flags|=GADGDISABLED;
		AddGadget(MyWindow->Window,gad,NULL);
		RefreshGList(gad,MyWindow->Window,NULL,1L);
	}

	if (lock=Lock(MyWindow->DeviceName,ACCESS_READ))
	{
		lock=CurrentDir(lock);
		if (mydir=CreateDir(DirectoryName))
		{
			mydir=CurrentDir(mydir);

			for (loop=0;loop<NUM_RESULTS;loop++)
			{
				Clear_Entry(MyWindow->MyResults[loop].text);
			}
			RefreshGList(&(MyWindow->Detail),MyWindow->Window,NULL,1L);

			if (flag&=Check_Quit(MyWindow))
			{
				Do_FileCreate_Test(MyWindow);
				if (flag&=Check_Quit(MyWindow)) Do_OpenClose_Test(MyWindow);
				if (flag&=Check_Quit(MyWindow)) Do_DirScan_Test(MyWindow);
				Do_FileDelete_Test(MyWindow);
			}
			if (Buffer=AllocMem(IO_BUFFER_SIZE,MEMF_PUBLIC))
			{
				if (flag&=Check_Quit(MyWindow)) Do_SeekRead_Test(MyWindow,Buffer);

				if (flag&=Check_Quit(MyWindow)) flag&=Do_File_Test(512L,MyWindow,Buffer,RESULTS_512_CREATE);
				if (flag&=Check_Quit(MyWindow)) flag&=Do_File_Test(4096L,MyWindow,Buffer,RESULTS_4096_CREATE);
				if (flag&=Check_Quit(MyWindow)) flag&=Do_File_Test(32768L,MyWindow,Buffer,RESULTS_32768_CREATE);
				if (flag&=Check_Quit(MyWindow)) flag&=Do_File_Test(262144L,MyWindow,Buffer,RESULTS_262144_CREATE);

				FreeMem(Buffer,IO_BUFFER_SIZE);
			}

			mydir=CurrentDir(mydir);
			UnLock(mydir);
			DeleteFile(DirectoryName);
		}
		else
		{	/* Tell user he messed up... */
			TellUser(MyWindow,ErrorText1);
		}

		lock=CurrentDir(lock);
		UnLock(lock);
	}
	else
	{	/* Tell user he messed up... */
		TellUser(MyWindow,ErrorText2);
	}

	for (loop=0;loop<NUM_GADGETS;loop++)
	{
		gad=&(MyWindow->MyGadgets[loop].Gadget);
		RemoveGadget(MyWindow->Window,gad);
		gad->Flags&=~GADGDISABLED;
		AddGadget(MyWindow->Window,gad,NULL);
	}
	RefreshGadgets(gad,MyWindow->Window,NULL);
}

/*
 * This function will open the file, append to the end of it the
 * test results and close it...
 */
VOID Save_To_File(struct MyWindow *MyWindow)
{
register	BPTR	TheFile;


	TheFile=Open(ResultFile,MODE_OLDFILE);
	if (!TheFile) TheFile=Open(ResultFile,MODE_NEWFILE);
	if (TheFile)
	{
		Seek(TheFile,0L,OFFSET_END);
		Write_Results(MyWindow,TheFile);
		Close(TheFile);
	}
	else
	{	/* Tell the user the file did not open... */
		TellUser(MyWindow,ErrorText3);
	}
}

/*
 * This function will open the printer, append to the end of it the
 * test results and close it...
 */
VOID Save_To_Printer(struct MyWindow *MyWindow)
{
register	BPTR	TheFile;

	if (TheFile=Open(PrinterFile,MODE_NEWFILE))
	{
		Write_Results(MyWindow,TheFile);
		Close(TheFile);
	}
	else
	{	/* Tell the user the file did not open... */
		TellUser(MyWindow,ErrorText4);
	}
}

VOID main(VOID)
{
register	struct	MyWindow	*MyWindow;
register	struct	Window		*Window;
register	struct	IntuiMessage	*msg;
register	struct	Gadget		*gad;
register	struct	timerequest	*Time_Req;
register		SHORT		QuitFlag=0;
register	struct	Process		*pr;
register		APTR		Old_WindowPtr;
register		SHORT		loop;
		struct	RenderInfo	RenderInfo;

	pr=(struct Process *)FindTask(NULL);
	Old_WindowPtr=pr->pr_WindowPtr;
	pr->pr_WindowPtr=(APTR)(-1);

	if (IntuitionBase=(struct IntuitionBase *)OpenLibrary("intuition.library",33L))
	{
		FillIn_RenderInfo(&RenderInfo);
		if (MyWindow=OpenMyWindow(&RenderInfo))
		{
			Window=MyWindow->Window;
			Time_Req=&(MyWindow->TimeReq);
			if (!OpenDevice(TIMERNAME,UNIT_VBLANK,(struct IORequest *)Time_Req,NULL))
			{
				Time_Req->tr_node.io_Message.mn_ReplyPort=Window->UserPort;
				TimerBase=(struct Library *)Time_Req->tr_node.io_Device;
				while (!QuitFlag)
				{
					WaitPort(Window->UserPort);
					if (msg=(struct IntuiMessage *)GetMsg(Window->UserPort))
					{
						if (msg->Class==CLOSEWINDOW) QuitFlag=TRUE;
						else if (msg->Class==ACTIVEWINDOW) ActivateGadget(&(MyWindow->DeviceGadget),Window,NULL);
						else if (msg->Class==GADGETDOWN)
						{
							gad=(struct Gadget *)msg->IAddress;
							if (gad->Flags & SELECTED)
							{
								MyWindow->TestFlag=gad->GadgetID;
								for (loop=0;loop<3;loop++)
								{
									gad=&(MyWindow->MyGadgets[loop].Gadget);
									if (MyWindow->TestFlag!=gad->GadgetID)
									{
										if (gad->Flags & SELECTED)
										{
											RemoveGadget(Window,gad);
											gad->Flags&=~SELECTED;
											AddGadget(Window,gad,NULL);
											RefreshGList(gad,Window,NULL,1L);
										}
									}
								}
							}
							else
							{
								RemoveGadget(Window,gad);
								gad->Flags|=SELECTED;
								AddGadget(Window,gad,NULL);
								RefreshGList(gad,Window,NULL,1L);
							}
						}
						else if (msg->Class==GADGETUP)
						{
							SetWait(Window);
							gad=(struct Gadget *)msg->IAddress;
							switch (gad->GadgetID)
							{
							case GADGET_STRING:	ActivateGadget((struct Gadget *)gad->UserData,Window,NULL);
										break;
							case GADGET_START:	Do_Disk_Test(MyWindow);
										break;
							case GADGET_SAVE:	Save_To_File(MyWindow);
										break;
							case GADGET_PRINT:	Save_To_Printer(MyWindow);
										break;
							}
							ClearWait(Window);
						}
						ReplyMsg((struct Message *)msg);
					}
				}
				CloseDevice((struct IORequest *)Time_Req);
			}
			CloseMyWindow(MyWindow);
		}
		CloseLibrary((struct Library *)IntuitionBase);
	}
	pr->pr_WindowPtr=Old_WindowPtr;
}
