//	CDeskTop.c

#include "MemUtils.h"
#include "CDiskDos.h"
#include "CDiskPro.h"
#include "CDiskPas.h"
//#include "CDiskUrl.h"
#include "PasStructs.h"
#include "CDiskCpm.h"
#include "CpmStructs.h"

#include "Nibblizer.h"
#include "DosCatalog.h"

#include "IC_FileIO.h"
#include "IC_Errors.h"
#include "ADFS_LogFile.h"
#include "FSUtils.h"
#include "CDesktopWindow.h"
#include "ADFS_Prefs.h"

#include "CDeskTop.h"

CDesktop	*gDesktop = NULL;

/**************************************************/
Boolean		CDesktop_IDesktop(void)
{
	OSErr		err = noErr;
	
	if (NewObject(gDesktop, CDesktop, err)) {
		err = gDesktop->IDesktop();
	}
	
	if (err) {
		CDesktop_Dispose();
		return FALSE;
	} else {
		return TRUE;
	}
}

void		CDesktop_Dispose(void)
{
	if (gDesktop) {
		gDesktop->Dispose();
		gDesktop = NULL;
	}
}


	
/**************************************************/
OSErr				CDesktop::IDesktop(void)
{
	OSErr	err = noErr;
	
	i_cOutline			= NULL;
	i_window			= NULL;
	i_pending_flushB	= FALSE;
		
	if (NewObject(i_cOutline, O_COutline, err)) {
		err = i_cOutline->O_InitOutline();
		
		if (!err) {
			O_TopicRef				topicRef;
			DiskLocSpecUnion		emptyLoc = { 0 };
			CFolder					*folderP = new CFolder;
			
			topicRef.cOutline	= i_cOutline;
			if (!err) err		= i_cOutline->O_GetRoot(&topicRef.cTopic);
			
			folderP->IFolder(NULL, NULL, emptyLoc, 0, 0);
			
			if (!err) i_window = i_desktopWindow = MakeNewDesktopWindow(&topicRef);
			if (!i_window) ReportError(err = IC_Err_OUT_OF_MEMORY);
		}
		
		if (!err) {
			
			#ifdef __ROSE__
				i_window->SetTitle("Rose Servers");
				i_window->SetAttribute(kWindowCloseBoxAttribute);
			#else
				i_window->Show(TRUE);
			#endif
		}
	}
	
	return err;
}



void				CDesktop::Dispose(void)
{
	if (i_window) {
		i_window->Dispose();
	}
	
	if (i_cOutline) {
		i_cOutline->O_DisposeOutline();
	}
	
	delete this;
}

OSErr			CDesktop::GetDiskImage(DiskImageRec *imageRec)
{
	OSErr		err			= noErr;
	long		fileSize	= 0;
	
	ASSERT(sizeof(Disk_DiskCopy_Header) == 84);
	
	if (IS_DiskType_PHYSICAL(imageRec->deviceP->diskType)) {
		imageRec->deviceP->vol.image.skipCheckSumB		= TRUE;

		imageRec->osType				= FSType_UNK;
		ImageRec_OrigOrder(imageRec)	= FSType_PRO;	
		imageRec->image.gen				= NULL;

		structclr(imageRec->deviceP->vol.image.header);
		
		ADFS_Log("It is a physical floppy volume\n");
	} else if (IS_DiskType_URL(imageRec->deviceP->diskType)) {
		imageRec->osType				= FSType_GEN;
		ImageRec_OrigOrder(imageRec)	= FSType_GEN;	
		imageRec->image.gen				= NULL;

		structclr(imageRec->deviceP->vol.image.header);
		ADFS_Log("I just said it was a URL right?\n");
	} else {
		ADFS_Log("Getting finder info\n");
		FOSE(FSpGetDiskImageOrder(&imageRec->deviceP->vol.image.fileSpec, &imageRec->curOrder));

		ADFS_Log("Getting file size\n");
		FOSE(FSpGetEOF(&imageRec->deviceP->vol.image.fileSpec, &fileSize))

		imageRec->deviceP->vol.image.skipCheckSumB			= TRUE;
		imageRec->osType				= FSType_UNK;	
		ImageRec_OrigOrder(imageRec)	= imageRec->curOrder;	
		imageRec->image.gen				= NULL;
		
		if (fileSize == kNib_DiskSize) {
			ADFS_Log("It's a NIB file\n");
			imageRec->deviceP->diskType = DiskType_inMemory_Nib;
		} else {
			if (fileSize < sizeof(Gen_Disk)) {
				ReportError(err = IC_Err_UNSUPPORTED_IMAGE_TYPE);
			} else if (fileSize > sizeof(Gen_Disk)) {
				//	could be a 2img disk, a DiskCopy disk, or a large raw ProDOS volume
				ADFS_Log("Reading file header\n");
				FOSE(ReadChunk(
					imageRec, 
					&imageRec->deviceP->vol.image.header, 
					0, sizeof(DiskHeaderUnion)));
				
				ByteSwap2IMG(&imageRec->deviceP->vol.image.header.twoimg);
				
				if (imageRec->deviceP->vol.image.header.twoimg.fileType == IMG_FileType) {
					ADFS_Log("It's a 2img file, ");
				
					switch (imageRec->deviceP->vol.image.header.twoimg.format) {

						case IMG_Format_DOS: {
							ImageRec_OrigOrder(imageRec) = FSType_DOS;
							break;
						}

						case IMG_Format_PRO: {
							ImageRec_OrigOrder(imageRec) = FSType_PRO;
							break;
						}

						case IMG_Format_NIB: {
							ADFS_Log("nibblized, ");
							ImageRec_OrigOrder(imageRec) = FSType_UNK;
							break;
						}

						case IMG_Format_C2P: {
							ImageRec_OrigOrder(imageRec) = FSType_C2P;
							break;
						}

						case IMG_Format_CPM: {
							ImageRec_OrigOrder(imageRec) = FSType_CPM;
							break;
						}

						default: {
							ImageRec_OrigOrder(imageRec) = FSType_UNK;
							break;
						}
					}

					ADFS_Log("in ");
					ADFS_Log_FSType(ImageRec_OrigOrder(imageRec));
					ADFS_Log(" sector order\n");

					imageRec->curOrder = ImageRec_OrigOrder(imageRec);

					if (
						imageRec->deviceP->vol.image.header.twoimg.img_len == sizeof(Gen_Disk)
						|| imageRec->deviceP->vol.image.header.twoimg.img_len == sizeof(Nib_Disk)
					) {
						ADFS_Log("It's in memory\n");
						imageRec->deviceP->diskType = DiskType_inMemory_2img;
					} else {
						ADFS_Log("It's on disk\n");
						imageRec->deviceP->diskType = DiskType_onDisk_2img;
					}
				} else {
					ByteSwap2IMG(&imageRec->deviceP->vol.image.header.twoimg);
									
					if (
						imageRec->deviceP->vol.image.header.diskCopy.dataSize 
							+ imageRec->deviceP->vol.image.header.diskCopy.tagSize 
							+ sizeof(Disk_DiskCopy_Header) == fileSize
					) {
						//	it's a DiskCopy image!
						
						ADFS_Log("It's a DiskCopy image, ");
											
						if (imageRec->deviceP->vol.image.header.diskCopy.dataSize == sizeof(Gen_Disk)) {
							ADFS_Log("in memory\n");
							imageRec->deviceP->diskType = DiskType_inMemory_DiskCopy;
						} else {
							ADFS_Log("on disk\n");
							imageRec->deviceP->diskType = DiskType_onDisk_DiskCopy;
						}
					} else {
						structclr(imageRec->deviceP->vol.image.header);
						
						//	must be a large raw ProDOS volume
						ADFS_Log("It must be a large raw ProDOS volume\n");
						imageRec->deviceP->diskType = DiskType_onDisk_Raw;
					}
				}
			} else {
				ADFS_Log("It's a raw 140k image\n");
			}
		}
	}

	if (!err) {
		if (IS_ImageRec_IN_MEMORY(imageRec)) {
			imageRec->image.gen	= (Gen_Disk *)TrackNewPtrClear("140k disk", sizeof(Gen_Disk));
			FE(imageRec->image.gen == NULL, IC_Err_OUT_OF_MEMORY);
			FOSE(ReadImage(imageRec));
		} else if (!IS_ImageRec_URL(imageRec)) {
			imageRec->image.proBlock = (Pro_Block *)TrackNewPtrClear("pro block", sizeof(Pro_Block));
			FE(imageRec->image.proBlock == NULL, IC_Err_OUT_OF_MEMORY);
		}
	}
	
	if (!err) {
		//	special case for hybrid disk
		if (IS_ImageRec_IN_MEMORY(imageRec) && imageRec->partition.idS == 1) {
			if (!Pro_IsPro(imageRec)) {
				err = IC_Err_UNSUPPORTED_FILE_SYSTEM;
			}
		} else if (!IS_ImageRec_URL(imageRec)) {
			if (!(
				Dos_IsDos(imageRec)
				|| Pro_IsPro(imageRec)
				|| Pas_IsPas(imageRec)
				|| Cpm_IsCpm(imageRec))
			) {
				ReportError(err = IC_Err_UNSUPPORTED_FILE_SYSTEM);
			}
		}
	}

	if (err) {
		error:		
		if (imageRec->image.gen) {
			TrackDisposePtr((Ptr)imageRec->image.gen);
			imageRec->image.gen = NULL;
		}
	}
	
	return err;
}

#define		MakeDisk(type, err)							\
do {													\
	CDisk##type		*disk##type;						\
														\
	if (NewObject(disk##type, CDisk##type, err)) {		\
		err = disk##type->IDisk##type(this, imageRecP);	\
		if (!err) {										\
			diskP = disk##type;							\
		}												\
	}													\
} while (0)

OSErr		NewDeviceRec(DiskType diskType, void *data, DiskDeviceRec **devicePP)
{
	OSErr		err = noErr;
	
	*devicePP = (DiskDeviceRec *)TrackNewPtrClear("device ptr", sizeof(DiskDeviceRec));
	if (*devicePP == NULL) err = memFullErr;
	
	if (!err) {
		(**devicePP).diskType = diskType;
		
		switch (diskType) {
		
			case DiskType_onDisk_Physical: {
				(**devicePP).vol.phys		= *(PhysVolumeRec *)data;
				break;
			}
			
			case DiskType_URL: {
				(**devicePP).vol.url.itemP	= (Gen_Item *)data;
				break;
			}
			
			default: {
				(**devicePP).vol.image.fileSpec	= *(FSSpec *)data;
				break;
			}
		}
	}
	
	return err;
}

void		CDesktop::SetPendingFlush(void)
{
	i_pending_flushB = TRUE;
}

void		CDesktop::DoPendingFlush(void)
{
	if (i_pending_flushB) {
		Err				err		= Err_NONE;
		CEntryArray		*diskA	= i_desktopWindow->GetAllDisks();
		
		SetStandardCursor(watchCursor);
		i_pending_flushB = FALSE;
		
		if (diskA) {
			long			index;
			CEntry			*curEntry;
			CDisk			*diskP;

			CE_FOR_EACH(diskA, index, curEntry, err) {
				diskP = (CDisk *)curEntry;
				
				if (diskP->i_pending_flushB) {
					(void)diskP->Flush();
				}
			} CE_END_EACH();
			
			diskA->Dispose();
		}
	}
}


OSErr		CDesktop::Purge(Boolean criticalB)
{
	Err				err		= Err_NONE;
	CEntryArray		*diskA	= i_desktopWindow->GetAllDisks();
	
	if (diskA) {
		long			index;
		CEntry			*curEntry;
		CDisk			*diskP;

		CE_FOR_EACH(diskA, index, curEntry, err) {
			diskP = (CDisk *)curEntry;
			
			err = diskP->Purge(criticalB);
		} CE_END_EACH();
		
		diskA->Dispose();
	}
	
	return err;
}

OSErr		CDesktop::GetDiskPartition(
	DiskDeviceRec	*deviceP, 
	short			partitionS, 
	CDisk			**diskPP)
{
	Err				err		= Err_NONE;
	CEntryArray		*diskA	= i_desktopWindow->GetAllDisks();
	long			index;
	CEntry			*curEntry;
	CDisk			*diskP;
	
	*diskPP = NULL;
	
	if (diskA) {
		CE_FOR_EACH(diskA, index, curEntry, err) {
			diskP = (CDisk *)curEntry;
			
			if (
				diskP->i_imageRec->deviceP == deviceP
				&& diskP->i_imageRec->partition.idS == partitionS
			) {
				*diskPP	= diskP;
				break;
			}
		} CE_END_EACH();
		
		if (!err && *diskPP == NULL) err = IC_Err_ENTRY_NOT_FOUND;
		
		diskA->Dispose();
	}
	
	return err;
}

Boolean			CDesktop::IsAlreadyMounted(FSSpec *fsSpecP)
{
	OSErr			err			= noErr;
	Boolean			mountedB	= FALSE;
	CEntryArray		*diskA		= i_desktopWindow->GetAllDisks();
	long			index;
	CEntry			*curEntry;
	CDisk			*diskP;
	
	if (diskA) {
		CE_FOR_EACH(diskA, index, curEntry, err) {
			diskP = (CDisk *)curEntry;
			
			if (!IS_ImageRec_PHYSICAL(diskP->i_imageRec)) {
				
				if (FSpEqual(
					&ImageRec_FileSpec(diskP->i_imageRec), 
					fsSpecP)
				) {
					mountedB = TRUE;
					break;
				}
			}
		} CE_END_EACH();
		
		diskA->Dispose();
	}
	
	return mountedB;
}


/*
	you must have already allocated a deviceP and passed it in.
	
	if it's a new deviceP, it should have a 0 refcount
	if it's not new, a higher refcount is fine
	
	deviceP->refCountS will always return the number of partitions mounted
	
*/
OSErr		CDesktop::MountDisk(
	DiskDeviceRec	*deviceP)
{
	OSErr			err = noErr;
	
	if (!IS_DiskType_PHYSICAL(deviceP->diskType)) {
		if (IsAlreadyMounted(&deviceP->vol.image.fileSpec)) {
			ReportErrorStr(-1, "Can't mount a disk that's already mounted.");
			err = 1;
		}
	}
	
	if (!err) {
		CDisk			*diskP = NULL;
		PartitionRec	partitionRec;

		if (i_desktopWindow->i_deselectB) {
			i_desktopWindow->DeSelectAll();
		}

		structclr(partitionRec);

		err = MountPartition(deviceP, &partitionRec, &diskP);
		
		if (!err) {
			//	right now, only DOS 3.3 disk partitions supported
			if (diskP->i_imageRec->osType == FSType_DOS) {
				CDiskDos	*diskDosP = (CDiskDos *)diskP;

				partitionRec.idS++;
				
				if (diskP->i_imageRec->partition.writeLengthL == sizeof(Dos_Disk)) {
					partitionRec.offsetL		= 0;
				
					//	cuz a hybrid disk may have ProDOS as 2nd partition
					err = MountPartition(deviceP, &partitionRec, &diskP);
					
					if (!err) {
						if (diskP->i_imageRec->osType == FSType_PRO) {
							CDiskPro	*diskProP = (CDiskPro *)diskP;
					
							//	must adjust the start & lengths of both partitions on a hybrid disk
							diskProP->i_imageRec->partition.writeStartL		= 0;
							diskProP->i_imageRec->partition.writeLengthL	= Dos_kBitMapTrack * Dos_kBytesPerTrack;

							diskDosP->i_imageRec->partition.writeStartL		= Dos_kBitMapTrack * Dos_kBytesPerTrack;
							diskDosP->i_imageRec->partition.writeLengthL	= (Dos_kTracksPerDisk - Dos_kBitMapTrack) * Dos_kBytesPerTrack;
						} else {
							diskP->Dispose();
						}
					} else {
						err = noErr;
					}
				} else if (diskP->i_imageRec->partition.writeLengthL == sizeof(Dos_Disk400)) {
					partitionRec.offsetL		= sizeof(Dos_Disk400);
					partitionRec.writeStartL	= sizeof(Dos_Disk400);
					partitionRec.writeLengthL	= sizeof(Dos_Disk400);

					//	 or a UniDOS disk may have DOS 3.3 as 2nd partition
					err = MountPartition(deviceP, &partitionRec, &diskP);
					err = noErr;
				}
			}
		}
	}
		
	return err;
}

OSErr		CDesktop::MountPartition(
	DiskDeviceRec	*deviceP, 
	PartitionRec	*partitionRecP, 
	CDisk			**diskPP)
{
	OSErr			err = noErr;
	DiskImageRec	*imageRecP = (DiskImageRec *)TrackNewPtrClear("disk image rec", sizeof(DiskImageRec));
	
	if (imageRecP == NULL) err = memFullErr;
	
	if (!err) {
		char			nameStrC[32];
		
		imageRecP->deviceP		= deviceP;
		imageRecP->partition	= *partitionRecP;
		
		SetStandardCursor(watchCursor);

		ADFS_Log("------------------------\n");
		ADFS_Log("About to mount partition #");
		ADFS_Log_Number(imageRecP->partition.idS);
		
		if (IS_ImageRec_PHYSICAL(imageRecP)) {
			ADFS_Log(", from a physical floppy in drive ");
			ADFS_Log_Number(ImageRec_VolRec(imageRecP).phys.driveNumS);
		} else if (IS_ImageRec_URL(imageRecP)) {
			ADFS_Log(", from a URL");
		} else {
			ADFS_Log(", from a disk image called '");
			CopyPascalStringToC(ImageRec_VolRec(imageRecP).image.fileSpec.name, nameStrC);
			ADFS_Log(nameStrC);
			ADFS_Log("'");
		}
		ADFS_Log(".\n");
		
		err = GetDiskImage(imageRecP);
		
		if (err) {
			TrackDisposePtr((Ptr)imageRecP);
		} else {
			CDisk	*diskP = NULL;

			i_window->InvalWindow(WindowRect_HEADER);
			i_window->InvalWindow(WindowRect_INTERIOR);

			ADFS_Log("Got the disk image or URL, about to create the disk object.\n");

			switch (imageRecP->osType) {

				case FSType_DOS: {
					MakeDisk(Dos, err);
					//Dos_Catalog(&imageRec);
					break;
				}
				
				case FSType_PRO: {
					MakeDisk(Pro, err);
					//Pro_Catalog(&imageRec);
					break;
				}
				
				case FSType_PAS: {
					MakeDisk(Pas, err);
					//Pas_Catalog(&imageRec);
					break;
				}
				
				case FSType_CPM: {
					MakeDisk(Cpm, err);
					//Cpm_Catalog(&imageRec);
					break;
				}

				case FSType_GEN: {
					#ifdef __ROSE__
					MakeDisk(Url, err);
					#endif
					//Cpm_Catalog(&imageRec);
					break;
				}
			}
			
			if (!err) {
				*diskPP								= diskP;
				imageRecP->partition.writeLengthL	= diskP->GetPhysicalSize();
				imageRecP->deviceP->refCountS		++;
				
				if (imageRecP->osType != FSType_GEN) {
					diskP->Select(TRUE, FALSE);
				}
			}
		}
	}
			
	if (err) {
		ADFS_Log("error mounting the disk!\n");
	} else {
		ADFS_Log("Success mounting the disk!\n");
	}
	
	return err;
}

long	CDesktop::GetSpaceUsed(void)
{
	return 0;
}

char	*CDesktop::GetSpaceUsedStr(char *buf)
{
	long	used = GetSpaceUsed();
	
	sprintf(buf, "%ld KB", used);
	return buf;
}
