//	PhysicalFloppy.c

#include <stdio.h>
#include "IC_Errors.h"
#include "MainEvent.h"
#include "GenStructs.h"
#include "CDesktop.h"
#include "FSUtils.h"
#include "CDialogInsertDisk.h"
#include "CDialogFormatDisk.h"

#ifndef __GNUC__
	#include <FSM.h>
#endif

#include "PhysicalFloppy.h"

#define		MoreAssertQ		ASSERT

#ifndef __68k__

typedef QHdrPtr	(*GetDrvQHdrFuncPtr)(void);
GetDrvQHdrFuncPtr	GetDrvQHdrFunc = NULL;

typedef OSErr	(*EjectFuncPtr)(
	ConstStr63Param		volName,       /* can be NULL */
	short				vRefNum);
EjectFuncPtr	EjectFunc;

typedef OSErr	(*UnmountVolFuncPtr)(
	ConstStr63Param		volName,       /* can be NULL */
	short				vRefNum);
UnmountVolFuncPtr	UnmoutVolFunc;

typedef OSErr	(*PBReadSyncFuncPtr)(
	ParmBlkPtr paramBlock);
PBReadSyncFuncPtr	PBReadSyncFunc;

typedef OSErr	(*PBWriteSyncFuncPtr)(
	ParmBlkPtr paramBlock);
PBWriteSyncFuncPtr	PBWriteSyncFunc;

typedef OSErr	(*PBGetVInfoSyncFuncPtr)(
	ParmBlkPtr paramBlock);
PBGetVInfoSyncFuncPtr		PBGetVInfoSyncFunc;

typedef OSErr	(*GetVInfoFuncPtr)(
  short       drvNum,
  StringPtr   volName,
  short *     vRefNum,
  long *      freeBytes);
GetVInfoFuncPtr		GetVInfoFunc;

typedef OSErr (*DIXFormatFuncPtr)(
	short			drvNum, 
	Boolean			fmtFlag, 
	unsigned long	fmtArg, 
	unsigned long	*actSize);
DIXFormatFuncPtr	DIXFormatFunc;

typedef OSErr (*DIFormatFuncPtr)(
	short			drvNum);
DIFormatFuncPtr		DIFormatFunc;

typedef OSErr (*DIVerifyFuncPtr)(
	short				drvNum);
DIVerifyFuncPtr		DIVerifyFunc;

static OSErr		GetInterfaceLibFuncs(void)
{
	OSErr					err = noErr;
	
	if (GetDrvQHdrFunc == NULL) {
		Str255					errMsgAC;
		CFragConnectionID		connID;
		
		err = GetSharedLibrary(
			"\pInterfaceLib", kPowerPCCFragArch, kReferenceCFrag, 
			&connID, NULL, errMsgAC);
		
		if (err) {
			char	errAC[256];
			
			sprintf(errAC, "Error loading code fragment: %#s", errMsgAC);	
			ReportErrorStr(err, errAC);
		} else {
			if (!err) err = FindSymbol(connID, "\pDIXFormat",		(Ptr *)&DIXFormatFunc,		NULL);
			if (!err) err = FindSymbol(connID, "\pDIFormat",		(Ptr *)&DIFormatFunc,		NULL);
			if (!err) err = FindSymbol(connID, "\pDIVerify",		(Ptr *)&DIVerifyFunc,		NULL);
			if (!err) err = FindSymbol(connID, "\pGetVInfo",		(Ptr *)&GetVInfoFunc,		NULL);
			if (!err) err = FindSymbol(connID, "\pPBGetVInfoSync",	(Ptr *)&PBGetVInfoSyncFunc,	NULL);
			if (!err) err = FindSymbol(connID, "\pGetDrvQHdr",		(Ptr *)&GetDrvQHdrFunc,		NULL);
			if (!err) err = FindSymbol(connID, "\pEject",			(Ptr *)&EjectFunc,			NULL);
			if (!err) err = FindSymbol(connID, "\pUnmountVol",		(Ptr *)&UnmoutVolFunc,		NULL);
			if (!err) err = FindSymbol(connID, "\pPBReadSync",		(Ptr *)&PBReadSyncFunc,		NULL);
			if (!err) err = FindSymbol(connID, "\pPBWriteSync",		(Ptr *)&PBWriteSyncFunc,	NULL);
			if (err) ReportOSErr(err);
		}
	}
	
	return err;
}

#endif

static OSErr MoreUTFindDriveQ(SInt16 drive, DrvQElPtr *foundDrvQEl)
{
	OSErr err;
//	UInt32 fsmVers;

	MoreAssertQ(drive > 0);
	MoreAssertQ(foundDrvQEl != NULL);
	
	// Check to see whether we have a useful version of FSM.  Versions of FSM
	// prior to 1.2 do not support the documented FSM API, so we just treat
	// them as if FSM wasn't installed.
	
	if (0) {// (Gestalt(gestaltFSMVersion, (SInt32 *) &fsmVers) == noErr) && (fsmVers >= 0x0120)) {

		// We have FSM, let's call its version of UTFindDrive,
		// and handle the weirdo error we get for non-HFS disks.

		//err = MoreUTFindDrive(drive, foundDrvQEl);
		if (err == extFSErr) {
			err = noErr;
		}
	} else {
		DrvQElPtr thisDrv;
	
		// No FSM, let's go poking around in low memory )-:
		
		*foundDrvQEl = NULL;
		
		#ifndef __68k__
			thisDrv = (DrvQElPtr)GetDrvQHdrFunc()->qHead;
		#else
			thisDrv = (DrvQElPtr)GetDrvQHdr()->qHead;
		#endif
		
		while (thisDrv != NULL && *foundDrvQEl == NULL) {
			if (thisDrv->dQDrive == drive) {
				*foundDrvQEl = thisDrv;
			} else {
				thisDrv = (DrvQElPtr) thisDrv->qLink;
			}
		}
		if (*foundDrvQEl == NULL) {
			err = nsDrvErr;
		} else {
			err = noErr;
		}
	}
	
	return err;
}

OSErr		EjectFloppy(PhysVolumeRec *volRecP)
{
	OSErr		err = noErr;
	
	if (GetSystemVers() < 0x1000) {
	
		#ifndef __68k__
			err	= EjectFunc(NULL, volRecP->driveNumS);
		#else
			err = Eject(NULL, volRecP->driveNumS);
		#endif
	
	} else {
		err = nsDrvErr;
	}
	
	return err;
}

OSErr	GetVolBlocks(PhysVolumeRec *volRecP)
{
	OSErr			err = noErr;
	Gen_Block		block;
	
	volRecP->blocksS = 1440 << 1;
	if (!err) err = GetPhysicalBlock(volRecP, volRecP->blocksS, &block);
	if (!err) {
		err = paramErr;
	} else {
		err = GetPhysicalBlock(volRecP, volRecP->blocksS - 1, &block);
		if (err) {
			volRecP->blocksS = 800 << 1;
			err = GetPhysicalBlock(volRecP, volRecP->blocksS - 1, &block);
			if (err) {
				volRecP->blocksS = 720 << 1;
				err = GetPhysicalBlock(volRecP, volRecP->blocksS - 1, &block);
			}
		}
	}
	
	if (err) {
		ReportErrorStr(
			err, 
			"Disk size not supported.  Only 720K, 800K and "	\
			"1.4MB floppies supported");
	} else {
		err = SetPhysicalBlock(volRecP, volRecP->blocksS - 1, &block);
		volRecP->lockedB = err != noErr;
		err = noErr;
	}
	
	return err;
}

OSErr	GetVolRec(short driveNumS, PhysVolumeRec *volRecP)
{
	OSErr		err = noErr;
	
	volRecP->driveNumS	= driveNumS;
	
	if (GetSystemVers() < 0x1000) {
		DrvQElPtr	driveP;
		
		err = MoreUTFindDriveQ(driveNumS, &driveP);
		
		if (!err) {
			ulong		sizeL = (driveP->dQDrvSz2 << 16) + driveP->dQDrvSz;
			
			volRecP->driveRefNumS	= driveP->dQRefNum;
			volRecP->blocksS		= sizeL >> 9;	//	div by 512
		}
	} else {
		volRecP->driveRefNumS	= 0;	//	do something creative
	}
	
	return err;
}

/*
o FSCopyDiskIDForVolume to get the BSD device ID
o FSUnmountVolumeSync
o open /dev/deviceID and use read/write to access the data, CDROMSample
   shows how to do this
   <http://developer.apple.com/samplecode/Sample_Code/Devices_and_Hardware/
    CD_ROM.htm>
o when you're done, close the file descriptor
o use FSMountLocalVolumeSync to remount

DiskArbitratration 
CFBundleGetFunctionPointerForName 


On traditional Mac OS you can use all of the standard techniques, you 
just have to get access to the appropriate library entry points and 
headers.  For example, if you call FindSymbol on UnmountVol in 
InterfaceLib, you'll get the version that doesn't always eject.

*/


static	OSErr		UnmountFloppy(short driveNumS)
{
	OSErr		err = noErr;
	
	if (GetSystemVers() < 0x1000) {
		#ifndef __68k__
			err	= UnmoutVolFunc(NULL, driveNumS);
		#else
			err = UnmountVol(NULL, driveNumS);
		#endif
	} else {
		err = nsDrvErr;
	}
	
	return err;
}

static Boolean  HasExtendedDIFunctions(void)
{
	long response;
	
	if (Gestalt(gestaltFSAttr, &response) == noErr) {
		return (response & (1L << gestaltHasExtendedDiskInit)) != 0;
	} else {
		return FALSE;
	}
}

static OSErr		ZeroDirectory(PhysVolumeRec *volRecP, FSType osType)
{
	return 1;
}

OSErr		FormatFloppy(PhysVolumeRec *volRecP, FSType osType)
{
	OSErr			err = noErr;
	
	if (GetSystemVers() < 0x1000) {
		#ifndef __68k__
			err = DIFormatFunc(volRecP->driveNumS);
			if (!err) err = DIVerifyFunc(volRecP->driveNumS);
		#else
			err = DIFormat(volRecP->driveNumS);
			if (!err) err = DIVerify(volRecP->driveNumS);
		#endif
		
		if (!err) err = ZeroDirectory(volRecP, osType);
	} else {
		err = nsDrvErr;
	}

	return err;
}

/*	
		VolumeParam			pb;
		Str255				nameStr;
		long				freeBytes;
		
		memclr(&pb, sizeof(VolumeParam));
		
		pb.ioNamePtr	= nameStr;
		
		#ifndef __68k__
			err = GetVInfoFunc(volRecP->driveNumS, nameStr, &pb.ioVRefNum, &freeBytes);
			err = PBGetVInfoSyncFunc((ParmBlkPtr)&pb);
		#else
			err = GetVInfo(volRecP->driveNumS, nameStr, &pb.ioVRefNum, &freeBytes);
			err = PBGetVInfoSync((ParmBlkPtr)&pb);
		#endif
		
		if (!err) {
			if (pb.ioVAlBlkSiz != sizeof(Gen_Block)) {
				err = 1;
				
				ReportErrorStr(-1, 
					"Please format this disk on OS 9 or earlier, "	\
					"using MacOS Standard (not 'Extended'), then try again.");
			} else if (pb.ioVNmAlBlks > 65535L) {
				err = 1;
				
				ReportErrorStr(-1, 
					"This disk is too big to be formatted as "	\
					"a ProDOS or Pascal volume.");
			} else {
				Gen_BlockNum	maxBlocks = (Gen_BlockNum)pb.ioVNmAlBlks;
				
				err = 1;
			}
		}
*/


OSErr		MountPhysicalFloppy(short driveNumS, OSErr mountErr)
{
	OSErr			err = noErr;

	if (GetSystemVers() < 0x1000) {
		#ifndef __68k__
			err = GetInterfaceLibFuncs();
		#endif
		
		if (!err) {

			if (!mountErr) {
				SetStandardCursor(watchCursor);
				err = UnmountFloppy(driveNumS);
				SetStandardCursor(watchCursor);
				HandleOneEvent(1);
			}

			if (!err && !IsInsertDiskDialogUp(driveNumS)) {
				PhysVolumeRec	volRec;

				err = GetVolRec(driveNumS, &volRec);

				//	see if we can read from the hardware
				//	also sets lock bit
				if (!err) err = GetVolBlocks(&volRec);
				if (err == readErr) {
					ReportErrorStr(err, 
						"Couldn't access floppy hardware.  "	\
						"Perhaps you're running in Classic?");
				}
				
				//	if so, we're good to go
				if (!err) {
					DiskDeviceRec	*deviceP;
					
					err = NewDeviceRec(
						DiskType_onDisk_Physical, &volRec, &deviceP);
					
					if (!err) {
					
						if (!mountErr) {
							err = gDesktop->MountDisk(deviceP);
							
							//	even if just one mounts, don't eject the disk
							if (deviceP->refCountS != 0) {
								err = noErr;
							}
						}
						
						/*
						until my format works
						if (mountErr || err == IC_Err_UNSUPPORTED_FILE_SYSTEM) {
							err = DoFormatDiskDialog(&device->vol.phys);
							
							if (!err) {
								err = gDesktop->MountDisk(deviceP);
							}
						}
						*/
						
						if (err && deviceP->refCountS != 0) {
							TrackDisposePtr((Ptr)deviceP);
						}
					}
				}
				
				if (err) EjectFloppy(&volRec);
			}
		}
	} else {
		err = nsDrvErr;
	}
		
	return err;
}

#ifndef __68k__
OSStatus	VolMountEventRefToRec(EventRef eventRef, EventRecord *eventRec)
{
	OSStatus			errL = noErr;
	
	if (!IsInForeground()) {
		//	don't handle disk insert events if in background
		errL = 1;
	} else {
		OSType				actualType;
		FSVolumeRefNum		volRefNum;
		ulong				actualSizeL;
		short				driveNumber;
		
		errL = GetEventParameter(
			eventRef, 
			kEventParamDirectObject, 
			typeFSVolumeRefNum, 
			&actualType, 
			sizeof(FSVolumeRefNum), 
			&actualSizeL, 
			&volRefNum);
		
		if (!errL)  errL = FSGetVolDriveNum(volRefNum, &driveNumber);
		
		if (!errL) {
			eventRec->what		= diskEvt;
			eventRec->message	= driveNumber;
		}
	}
	
	return errL;
}
#endif

static	OSErr	GetSetPhysicalChunk(
	Boolean			getB, 
	PhysVolumeRec	*volRecP, 
	ulong			startBlockL, 
	ulong			numBlocksL, 
	void			*blockP)
{
	OSErr			err = noErr;

	if (GetSystemVers() < 0x1000) {
		ParamBlockRec	paramBlock;

		memclr(&paramBlock, sizeof(paramBlock));
		paramBlock.ioParam.ioVRefNum	= volRecP->driveNumS;
		paramBlock.ioParam.ioRefNum		= volRecP->driveRefNumS;
		paramBlock.ioParam.ioBuffer		= (char *)blockP;
		paramBlock.ioParam.ioReqCount	= numBlocksL * Gen_kBytesPerBlock;
		paramBlock.ioParam.ioPosMode	= fsFromStart;
		paramBlock.ioParam.ioPosOffset	= startBlockL * Gen_kBytesPerBlock;

		#ifndef __68k__
			if (getB) {
				err = PBReadSyncFunc(&paramBlock);
			} else {
				err = PBWriteSyncFunc(&paramBlock);
			}
		#else
			if (getB) {
				err = PBReadSync(&paramBlock);
			} else {
				err = PBWriteSync(&paramBlock);
			}
		#endif
	} else {
		err = nsDrvErr;
	}
		
	return err;
}

OSErr		GetPhysicalBlock(PhysVolumeRec *volRecP, ulong blockL, void *blockP)
{
	return GetSetPhysicalChunk(TRUE, volRecP, blockL, 1, blockP);
}

OSErr		SetPhysicalBlock(PhysVolumeRec *volRecP, ulong blockL, void *blockP)
{
	return GetSetPhysicalChunk(FALSE, volRecP, blockL, 1, blockP);
}

OSErr		GetPhysicalTrack(PhysVolumeRec *volRecP, ulong blockL, void *trackP)
{
	return GetSetPhysicalChunk(TRUE, volRecP, blockL, kFloppy_BlocksPerTrack, trackP);
}

OSErr		SetPhysicalTrack(PhysVolumeRec *volRecP, ulong blockL, void *trackP)
{
	return GetSetPhysicalChunk(FALSE, volRecP, blockL, kFloppy_BlocksPerTrack, trackP);
}
