//	FileSystemTypes.c

#include "MemUtils.h"
#include "stdio.h"
#include "GenStructs.h"
#include "CDialogCopy.h"
#include "ADFS_Prefs.h"
#include "Open_Save.h"
#include "CDiskPro.h"
#include "CDiskDos.h"
#include "CDiskPas.h"
#include "CDiskCpm.h"
#include "CDialogDeleteFile.h"
#include "CFolderPro.h"
#include "CFolderCpm.h"
#include "CFolderPas.h"
#include "CDialogNewDisk.h"
#include "CDesktop.h"
#include "IC_ImageTypes.h"
#include "IC_FileIO.h"
#include "CCopyTree.h"
#include "ADFS_LogFile.h"

char	*strcat_FSType(FSType osType, char *buf)
{
	switch (osType) {

		default: {
			strcat(buf, "Strange");
			break;
		}

		case FSType_UNK: {
			strcat(buf, "Unknown");
			break;
		}

		case FSType_HYB: {
			strcat(buf, "Hybrid");
			break;
		}

		case FSType_GEN: {
			strcat(buf, "Generic");
			break;
		}

		case FSType_C2P: {
			strcat(buf, "Copy ][+");
			break;
		}

		case FSType_PAS: {
			strcat(buf, "Pascal");
			break;
		}

		case FSType_CPM: {
			strcat(buf, "CP/M");
			break;
		}

		case FSType_DOS: {
			strcat(buf, "DOS 3.3");
			break;
		}

		case FSType_PRO: {
			strcat(buf, "ProDOS");
			break;
		}
	}
	
	return buf;
}

static	OSErr	LoadDiskImage(Gen_Disk ***imageHP, short res_idS)
{
	OSErr		err = noErr;
	ulong		neededMem = sizeof(Gen_Disk);
	
	PurgeMem((long)neededMem);
	CompactMem((long)neededMem);

	*imageHP = (Gen_Disk **)GetResource('disk', res_idS);
	err = ResError();
	
	if (!err) {
		TrackDetachResource("load blank 140k disk image", (Handle)*imageHP);
		MoveHHi((Handle)*imageHP);
		HLock((Handle)*imageHP);
	} else {
		ReportFreeSpace(neededMem);
	}
		
	return err;
}

void			FillInImageRecHeader(
	unsigned char			*fileNameP, 
	long					fileSize, 
	Boolean					nibblizedB, 
	DiskImageRec			*imageRecP)
{
	if (IS_ImageRec_2img(imageRecP)) {
		Disk_2IMG_Header	*tmgP = &ImageRec_VolRec(imageRecP).image.header.twoimg;
		
		tmgP->fileType		= IMG_FileType;	//	'2IMG'
		tmgP->fileCreator	= ADFS_Creator;	//	ADFS_Creator for my app
		tmgP->headerLen		= 52;			//	52
		tmgP->version		= 1;			//	1
		
		if (nibblizedB) {
			tmgP->format = IMG_Format_NIB;
		} else switch (ImageRec_OrigOrder(imageRecP)) {
			case FSType_DOS:
				tmgP->format = IMG_Format_DOS;
				break;
			case FSType_PRO:
				tmgP->format = IMG_Format_PRO;
				break;
			case FSType_C2P:
				tmgP->format = IMG_Format_C2P;
				break;
			case FSType_CPM:
				tmgP->format = IMG_Format_CPM;
				break;
			default:
				ASSERT(0);
				break;
		}
		
		tmgP->img_start_offset	= sizeof(Disk_2IMG_Header);
		tmgP->img_len			= fileSize;

		if (imageRecP->osType == FSType_DOS) {
			tmgP->flags = 
				IMG_Flag_HAS_DOS_VOL | Dos_GetVolNum(imageRecP);
		} else {
			tmgP->flags = 0;
		}
		
		if (imageRecP->osType == FSType_PRO) {
			if (nibblizedB) {
				tmgP->blocks = Pro_kBlocksPerDisk;
			} else {
				tmgP->blocks = fileSize >> 9;
			}
		} else {
			tmgP->blocks = 0;
		}
	} else if (IS_ImageRec_DiskCopy(imageRecP)) {
		Disk_DiskCopy_Header	*dcP = &ImageRec_VolRec(imageRecP).image.header.diskCopy;
		
		ASSERT(ImageRec_OrigOrder(imageRecP) == FSType_PRO);
		ASSERT(imageRecP->curOrder == FSType_PRO);
		ASSERT(imageRecP->osType == FSType_PRO);
		ASSERT(!nibblizedB);

		CopyString(fileNameP, dcP->diskName);
		dcP->dataSize		= fileSize;		//	bytes in file
		dcP->tagSize		= 0;			//	set to zero
		dcP->dataChecksum	= 0;			//	checksum of all user data
		dcP->tagChecksum	= 0;			//	checksum of tag data (set to zero)
		dcP->diskFormat		= DiskCopy_DiskFormat_800;					//	set to UNK
		dcP->formatByte		= DiskCopy_FormatByte_GreaterThan400;		//	typically > 400
		dcP->privateData	= 0x0100;		//	set to 0x0100
	}		
}

static	OSErr	MakeNewDiskImageRec(
	FSSpecPtr				fileSpecP, 
	char					*diskNameZ, 
	FSType					osType, 
	FSType					curOrder, 
	FSType					origOrder, 
	Boolean					nibblizedB, 
	IC_FileDescIndexType	descIndex, 
	Gen_Disk				*imageP, 
	DiskImageRec			*imageRecP)
{
	OSErr				err		= noErr;
	long				fileSize;
	Str255				diskNameStr;
	
	CopyCStringToPascal(diskNameZ, diskNameStr);

	fileSize	= sizeof(Gen_Disk);

	if (!err) err = FSpCreate(
		fileSpecP, 
		IC_FileTypeArray[descIndex].fileCreator, 
		IC_FileTypeArray[descIndex].fileType, 
		smSystemScript);
	
	//	overwrite if already exists
	if (err == -48) err = noErr;
	if (err) return err;
	
	structclr(*imageRecP);

	err = NewDeviceRec(
		DiskType_inMemory_Raw, fileSpecP, &imageRecP->deviceP);
	if (err) return err;

	imageRecP->partition.writeLengthL				= fileSize;
	ImageRec_VolRec(imageRecP).image.skipCheckSumB	= TRUE;

	imageRecP->image.gen			= imageP;
	ImageRec_OrigOrder(imageRecP)	= origOrder;
	imageRecP->curOrder				= curOrder;
	imageRecP->osType				= osType;

	switch (descIndex) {
		
		default: {
			if (nibblizedB) {
				ImageRec_DiskType(imageRecP)	= DiskType_inMemory_Nib;
			} else {
				ImageRec_DiskType(imageRecP)	= DiskType_inMemory_Raw;
			}
			break;
		}
	
		case IC_FileDescIndex_NIBL: {
			ASSERT(nibblizedB);
			ImageRec_DiskType(imageRecP)	= DiskType_inMemory_Nib;
			break;
		}
		
		case IC_FileDescIndex_dImg: {
			ImageRec_DiskType(imageRecP)	= DiskType_inMemory_DiskCopy;
			
			FillInImageRecHeader(
				diskNameStr, fileSize, nibblizedB, imageRecP);
			break;
		}
		
		case IC_FileDescIndex_2mg: {
			ImageRec_DiskType(imageRecP)	= DiskType_inMemory_2img;
			
			FillInImageRecHeader(
				diskNameStr, fileSize, nibblizedB, imageRecP);
			break;
		}
	}
	
	return err;
}

ADFS_ProgStr		gProgStrs[ADFS_NewDiskProg_NUMTYPES] = {
	"Saving Prefs", 
	"Loading original 140k disk image", 
	"Creating new disk image record", 
	"Writing 140k disk image", 
	"Mounting saved disk image", 
	"Saving boot files from disk image", 
	"Deleting boot files from disk image", 
	"Zeroing unused areas of disk image", 
	"Expanding disk image to new size", 
	"Restoring boot files to disk image", 
	"Updating the Macintosh file type"
};

/*
	this is tricky.  A CopyTree's destination can be:
	1) a folder in the Mac file system
	2) a folder in the ADFS file system
	3) another copy tree! (which is separate and 
		distinct from the original copy tree)
	
	When you get a new copy tree, you initialize it with it's
	destination folder (always a "folder".  a tree is a kind of folder)
	
	What we're doing here is:
	
	1) copy 2 files off an ADFS disk into memory (a tree)
	2) expand the disk
	3) copy those 2 files back to the ADFS disk
	
	so:
	In step 1 and 3, we need a copy tree to make the xfer.
	
	create the tree (copyDest) to be used in step 3. 
	the source is itself, the dest is the disk
	
	create the tree (copyTree) to be used in step 1,
	the source is the disk, the dest is the tree created above
	
	add the sources (from the disk) to copyTree
	copy! (this copies files from the disk into copyDest tree)
	now, copyDest is filled in with sources, 
		which are actual files in memory
	
	dispose the copyTree, we're done with it
	
	expand the disk
	
	now, copyDest->Copy()!
	this copies files from itself to the disk
*/
Boolean		NewDisk_CB(ADFS_NewDiskRec *newDiskRecP)
{
	Boolean					successB = FALSE;
	FSSpec					documentFSSpec;
	char					defaultNameAC[256], diskNameAC[256];
	unsigned char			*defNameP = (unsigned char *)defaultNameAC;
	IC_FileDescIndexType	descIndex;
		
	ASSERT(
		newDiskRecP->format.sectorOrder != FSType_PAS
		&& newDiskRecP->format.sectorOrder > FSType_GEN
		&& newDiskRecP->format.sectorOrder < FSType_NUMTYPES);
	
	/*
		/		divider of choices, invisible
		[]		pick one choice between dividers, 
		{}		optional
		""		literal
		else	literal
		
		[ProDOS/DOS/PAS/CPM] {Non}Boot ([po/do/ctpo/cpmo{"/nib"}]).[extension]
	*/
	
	defaultNameAC[0] = 0;
	
	strcat_FSType((FSType)newDiskRecP->osType, defaultNameAC);
	strcat(defaultNameAC, " ");
	
	if (newDiskRecP->bootableB) {
		strcat(defaultNameAC, "Boot");
	} else {
		strcat(defaultNameAC, "Data");
	}
	
	strcpy(diskNameAC, defaultNameAC);

	descIndex = GetDescIndex(&newDiskRecP->format, newDiskRecP->bytesOnDiskL);

	strcat_OrderAndExt(
		newDiskRecP->format.nibblizedB, 
		(FSType)newDiskRecP->format.sectorOrder, 
		descIndex, defaultNameAC);

	if (newDiskRecP->format.nibblizedB) {
		ASSERT(newDiskRecP->osType != FSType_HYB);
		
		newDiskRecP->format.sectorOrder = newDiskRecP->osType;
	}
	
	{
		char		sizeAC[64];
		char		strAC2[32];
		
		ADFS_Log(defaultNameAC);
		sprintf(sizeAC, ", size: %s\n", FormatSize(newDiskRecP->bytesOnDiskL, strAC2));
		ADFS_Log(sizeAC);
	}
	
	CopyCStringToPascal(defaultNameAC, defNameP);

	Boolean		good_to_goB = FALSE, doneB = FALSE;
	
	do {
		if (NavSaveFile(
			c2p("Save Disk Image As..."), 
			defNameP, &documentFSSpec)
		) {
			if (gDesktop->IsAlreadyMounted(&documentFSSpec)) {
				ReportErrorStr(-1, "You have a disk mounted with "
					"that name.  Please choose another name.");
			} else {
				good_to_goB = TRUE;
				doneB		= TRUE;
			}
		} else {
			doneB = TRUE;
		}
	} while (!doneB);
	
	if (good_to_goB) {
		Gen_Disk		**diskH;
		OSErr			err = noErr;
		short			resID;
		CDialogCopy		*copyDialogP = ShowCopyDialog();
		short			maxProgressS = ADFS_NewDiskProg_NUMTYPES;
		
		if (!newDiskRecP->bootableB) maxProgressS -= 2;
		if (newDiskRecP->bytesOnDiskL == Gen_kBytesPerDisk) maxProgressS -= 3;
		if (!(!newDiskRecP->bootableB || newDiskRecP->bytesOnDiskL > Gen_kBytesPerDisk)) maxProgressS -= 1;
	
		ADFS_Log("Creating new disk image\n");
		copyDialogP->SetTitle("Creating new disk image");
		
		copyDialogP->SetAsProgress(FALSE, -maxProgressS);
		copyDialogP->IncrementProgress(FALSE, gProgStrs[ADFS_NewDiskProg_SAVE_PREF]);

		(**gPrefsH).newDiskRec					= *newDiskRecP;
		(**gPrefsH).newDiskRec.diskP			= NULL;
		(**gPrefsH).newDiskRec.disk2imgB		= FALSE;
		(**gPrefsH).newDiskRec.NewDiskDoneCB	= NULL;
		SavePrefs();
		
		switch (newDiskRecP->osType) {

			case FSType_HYB: {
				resID = 5;
				break;
			}
			
			case FSType_DOS: {
				if (newDiskRecP->bootableB) {
					resID = 0;
				} else {
					resID = 4;
				}
				break;
			}

			case FSType_PRO: {
				resID = 1;
				break;
			}

			case FSType_PAS: {
				resID = 2;
				break;
			}
			
			case FSType_CPM: {
				resID = 3;
				break;
			}

			default: {
				ASSERT(0);
				break;
			}
		}
		
		copyDialogP->IncrementProgress(FALSE, gProgStrs[ADFS_NewDiskProg_LOAD_140K]);

		err = LoadDiskImage(&diskH, resID);
			
		if (!err) {
			DiskImageRec	imageRec;
			CDisk			*diskP = NULL;
			
			copyDialogP->IncrementProgress(FALSE, gProgStrs[ADFS_NewDiskProg_CRE_IMG_REC]);
			
			//	this is only used to write the disk image
			if (!err) err = MakeNewDiskImageRec(
				&documentFSSpec, 
				diskNameAC, 
				(FSType)newDiskRecP->osType,
				ND_OSToSector((FSType)newDiskRecP->osType),	//	cur order
				(FSType)newDiskRecP->format.sectorOrder,	//	orig order
				newDiskRecP->format.nibblizedB, 
				descIndex, *diskH, 
				&imageRec);
			
			if (err) {
				ReportErrorStr(err, "Error creating new disk image record.");
				goto ERROR;
			}
			
			copyDialogP->IncrementProgress(FALSE, gProgStrs[ADFS_NewDiskProg_WRITE_140K]);

			if (!err) err = WriteImage(&imageRec);

			if (err) {
				ReportErrorStr(err, "Error writing disk image.");
				goto ERROR;
			} else {
				TrackDisposePtr((Ptr)imageRec.deviceP);
				err = MemError();
				if (err) {
					ReportErrorStr(err, "Error disposing imageRec device.");
					goto ERROR;
				}
			}
			
			HUnlock((Handle)diskH);
			err = MemError();			
			if (err) {
				ReportErrorStr(err, "Error HUnlocking diskH.");
				goto ERROR;
			}

			if (!err) {
				TrackDisposeHandle((Handle)diskH);
				err = MemError();
				if (err) {
					ReportErrorStr(err, "Error Disposing diskH.");
					goto ERROR;
				}
			}
			
			copyDialogP->IncrementProgress(FALSE, gProgStrs[ADFS_NewDiskProg_MOUNT]);
			
			if (!err) {
				DiskDeviceRec	*deviceP;
				
				err = NewDeviceRec(
					DiskType_inMemory_Raw, &documentFSSpec, &deviceP);
				
				if (!err) err = gDesktop->MountDisk(deviceP);
				
				if (err) {
					if (deviceP->refCountS == 0) {
						TrackDisposePtr((Ptr)deviceP);
					}
				} else {
					ADFS_NewDiskCompletionRec	completionRec;
					short						partitionS;
					
					completionRec.newDiskRecP	= newDiskRecP;
					completionRec.copyDialogP	= copyDialogP;
					completionRec.diskNameZ		= diskNameAC;
					
					for (partitionS = 0; !err && partitionS < deviceP->refCountS; partitionS++) {
						err = gDesktop->GetDiskPartition(deviceP, partitionS, &diskP);
						
						if (!err) {
							if (partitionS == 0) {
								//	used when converting disk->image
								newDiskRecP->diskP = diskP;
							}
			
							err = diskP->NewDisk_Completion(&completionRec);
						}
					}
				}
			}
				
			if (err) {
				ReportErrorStr(err, "Error mounting disk image.");
				goto ERROR;
			}
		}
		
		if (err) {
			ReportErrorStr(err, "Some other kind of New Disk error");
		}
		
		goto GOOD;
		ERROR:
		ADFS_Log("Did NOT have ");
		
		GOOD:
		copyDialogP->HideCopyDialog();
		successB = TRUE;
		
		ADFS_Log("success creating new disk image!\n");
	} else {
		ADFS_Log("canceled creating new disk image!\n");
	}
	
	return successB;
}

void		ByteSwap2IMG(Disk_2IMG_Header *headerP)
{
	headerP->fileType			= OSSwapBigToHostInt32(headerP->fileType);
	headerP->fileCreator		= OSSwapBigToHostInt32(headerP->fileCreator);

	#if TARGET_RT_BIG_ENDIAN
		headerP->headerLen			= GetRboLong(*(Rbo4Byte *)&headerP->headerLen);
		headerP->version			= GetRboLong(*(Rbo4Byte *)&headerP->version);
		headerP->format				= GetRboLong(*(Rbo4Byte *)&headerP->format);
		headerP->flags				= GetRboLong(*(Rbo4Byte *)&headerP->flags);
		headerP->blocks				= GetRboLong(*(Rbo4Byte *)&headerP->blocks);
		headerP->img_start_offset	= GetRboLong(*(Rbo4Byte *)&headerP->img_start_offset);
		headerP->img_len			= GetRboLong(*(Rbo4Byte *)&headerP->img_len);
		headerP->comment			= GetRboLong(*(Rbo4Byte *)&headerP->comment);
		headerP->comment_len		= GetRboLong(*(Rbo4Byte *)&headerP->comment_len);
		headerP->creator_data		= GetRboLong(*(Rbo4Byte *)&headerP->creator_data);
		headerP->creator_data_len	= GetRboLong(*(Rbo4Byte *)&headerP->creator_data_len);
	#endif
}

/*
	Initialize checksum to zero
	For each data REVERSE WORD:
		Add the data REVERSE WORD to the checksum
		Rotate the 32-bit checksum right one bit (wrapping bit 0 to bit 31)
*/

#define		ROR(_V)	{					\
	Boolean		carryB	= (_V) & 0x01;	\
										\
	(_V) >>= 1;							\
	if (carryB) {						\
		(_V) |= 0x80000000;				\
	}									\
}

void		CalcDiskCopyChecksum(DiskImageRec *imageRecP)
{
	Pro_BlockNum		maxBlocks, curBlockIndex;
	ushort				*curShortP, curShortIndex;
	CDialogCopy			*copyDialogP = NULL;
	Boolean				wasShowingCopyB = FALSE;//GetCopyWindow() != NULL;
	ulong				checksum = 0;
	OSErr				err = noErr;
	Pro_Block			blockBuf, *origBuf = imageRecP->image.proBlock;
	Boolean				replaceProBlockB = FALSE;
	Rect				origProgRect;

	ASSERT(IS_ImageRec_DiskCopy(imageRecP));
	ASSERT((ImageRec_VolRec(imageRecP).image.header.diskCopy.dataSize & 0x01FF) == 0);	

	maxBlocks = ImageRec_VolRec(imageRecP).image.header.diskCopy.dataSize >> 9;
	
	if (imageRecP->osType == FSType_PRO) {		
		copyDialogP		= ShowCopyDialog();
		
		replaceProBlockB = !IS_ImageRec_IN_MEMORY(imageRecP);
		
		if (copyDialogP) {
			char		buf[256];
			Rect		theRect;
			
			CopyPascalStringToC(ImageRec_VolRec(imageRecP).image.header.diskCopy.diskName, buf);
			
			copyDialogP->SetItemRemain(0);
			copyDialogP->SetMaxProgress(maxBlocks);
			copyDialogP->SetProgStrings("Checksum", buf, "Disk Copy Header");
			copyDialogP->HideDlogItem(kCopy_STOP_BUTTON);
			copyDialogP->GetDlogItemRect(kCopy_PROGRESS_BAR, &origProgRect);
			theRect = origProgRect;
			theRect.right = kCopyWindowWidth - theRect.left;
			copyDialogP->SetDlogItemRect(kCopy_PROGRESS_BAR, &theRect);
			
			strcpy(
				copyDialogP->i_itemStrTable[DCpy_ItemStr_ITEM_REMAIN_STAT].itemStr, 
				"Disks remaining to Checksum:");

			strcpy(
				copyDialogP->i_itemStrTable[DCpy_ItemStr_COPYING_STAT].itemStr, 
				"Calculating:");

			strcpy(
				copyDialogP->i_itemStrTable[DCpy_ItemStr_BYTES_COPIED_STAT].itemStr, 
				"Blks Chksmd:");

			copyDialogP->SetTitle("Calculating Disk Copy Checksum");
		}
	} else {
		ASSERT(IS_ImageRec_IN_MEMORY(imageRecP));
	}

	for (
		curBlockIndex = 0; 
		!err && curBlockIndex < maxBlocks; 
		curBlockIndex++
	) {
		if (replaceProBlockB) {
			imageRecP->image.proBlock = &blockBuf;
		}

		err = Pro_GetBlock(
			imageRecP, 
			curBlockIndex, 
			(Pro_Block **)&curShortP);

		if (replaceProBlockB) {
			imageRecP->image.proBlock = origBuf;
		}

		curShortIndex = Pro_kShortsPerBlock;
		
		do {
			checksum += (ulong)*curShortP;
			ROR(checksum);
			curShortP++;
		} while (--curShortIndex);

		if (copyDialogP) {
			//	can't cancel
			//	only every 256 blocks
			if ((curBlockIndex & 0xFF) == 0) {
				(void)copyDialogP->SetProgress(curBlockIndex);
			}
		}
	}
	
	if (wasShowingCopyB) {
		copyDialogP->SetDlogItemRect(kCopy_PROGRESS_BAR, &origProgRect);
		copyDialogP->ShowDlogItem(kCopy_STOP_BUTTON);
	} else {
		copyDialogP->HideCopyDialog();
	}

	if (!err) {
		ImageRec_VolRec(imageRecP).image.header.diskCopy.dataChecksum = checksum;
	}
}

void		ByteSwapDiskCopy(Disk_DiskCopy_Header *headerP)
{
	ASSERT(0);	//	never used
	headerP->dataSize			= GetRboLong(*(Rbo4Byte *)&headerP->dataSize);
	headerP->tagSize			= GetRboLong(*(Rbo4Byte *)&headerP->tagSize);
	headerP->dataChecksum		= GetRboLong(*(Rbo4Byte *)&headerP->dataChecksum);
	headerP->tagChecksum		= GetRboLong(*(Rbo4Byte *)&headerP->tagChecksum);
	headerP->privateData		= GetRboShort(*(RboShort *)&headerP->privateData);
}

