//	IC_FileIO.c

#include <stdio.h>
#include "string.h"
#include "IC_Errors.h"
#include "MemUtils.h"
#include "IC_ImageTypes.h"
#include "Nibblizer.h"
#include "FSUtils.h"
#include "ProStructs.h"
#include "DosStructs.h"
#include "CDialogNewDisk.h"
#include "ADFS_LogFile.h"
#include "PhysicalFloppy.h"

#include "IC_FileIO.h"

/******************************/
//	WARNING: first 5 entries of 
//	FSType and IC_FileDescIndexType
//	** MUST MATCH **

IC_FileTypeArrayRec		IC_FileTypeArray = {
	{	'????',			FSType_UNK, ADFS_Creator,		".???"	},
	{	'****',			FSType_GEN, ADFS_Creator,		".gen"	},
	{	'DSK5',			FSType_DOS, ADFS_Creator,		".dsk"	},
	{	'DISK',  		FSType_PRO, ADFS_Creator,		".po"	}, 
	{	'aPas',  		FSType_PAS, ADFS_Creator,		".pas"	}, 
	{	'aCpm',  		FSType_CPM, ADFS_Creator,		".cpm"	}, 
	{	0x70F70118, 	FSType_C2P, ADFS_Creator,		".img"	}, 
	{	'DOS3',  		FSType_DOS, ADFS_Creator,		".do"	}, 
	{	'A2D5',  		FSType_DOS, ADFS_Creator,		".ctkg"	}, 
	{	'PDOS',		 	FSType_PRO, ADFS_Creator,		".disk"	}, 
	{	0x70F70000, 	FSType_C2P, ADFS_Creator,		".img"	}, 
	{	'VDSK',  		FSType_PRO, ADFS_Creator,		".vdsk"	}, 
	{	'hdrv',  		FSType_PRO, ADFS_Creator,		".hdv"	}, 
	{	'dImg', 		FSType_PRO, ADFS_Creator,		".dImg"	}, 
	{	'2img', 		FSType_GEN, ADFS_Creator,		".2mg"	},
	{	'NIBL', 		FSType_C2P, ADFS_Creator,		".nib"	}
};

/******************************/

IC_FileDescIndexType	GetDescIndex(
	ND_DiscFormatRec	*formatP, 
	ulong				bytesOnDiskL)
{
	IC_FileDescIndexType	descIndex;

	switch (formatP->diskFileType) {

		case ND_Format_RAW: {
			if (formatP->nibblizedB) {
				descIndex = IC_FileDescIndex_NIBL;
			} else {
				switch (bytesOnDiskL) {
					case (Pro_kBlocksPerDisk400		* Pro_kBytesPerBlock):
					case (Pro_kBlocksPerDisk800		* Pro_kBytesPerBlock):
					case ((long)Pro_kBlocksPerDisk1440	* (long)Pro_kBytesPerBlock): {
						descIndex = IC_FileDescIndex_VDSK;
						break;
					}
					
					default: {
						if (bytesOnDiskL > Gen_kBytesPerDisk) {
							descIndex = IC_FileDescIndex_hdv;
						} else {
							descIndex = (IC_FileDescIndexType)formatP->sectorOrder;
						}
					}
				}
			}
			break;
		}

		case ND_Format_2IMG: {
			descIndex = IC_FileDescIndex_2mg;
			break;
		}

		case ND_Format_DISK_COPY: {
			descIndex = IC_FileDescIndex_dImg;
			break;
		}
	}
	
	return descIndex;
}

OSErr		UpdateFileType(
	DiskImageRec		*imageRecP, 
	ND_DiscFormatRec	*formatP, 
	ulong				bytesOnDiskL)
{
	OSErr					err = noErr;
	FInfo					fInfo;
	IC_FileDescIndexType	descIndex = GetDescIndex(formatP, bytesOnDiskL);
	
	err = FSpGetFInfo(&ImageRec_FileSpec(imageRecP), &fInfo);
	if (!err) {
		fInfo.fdType	= IC_FileTypeArray[descIndex].fileType;
		fInfo.fdCreator	= IC_FileTypeArray[descIndex].fileCreator;

		err = FSpSetFInfo(&ImageRec_FileSpec(imageRecP), &fInfo);
	}
	
	if (!err) {
		Str255		newNameStr;
		char		*strC = (char *)newNameStr;
		char		*dotP;
		short		len;
		
		CopyPascalStringToC(ImageRec_FileSpec(imageRecP).name, strC);
		dotP = strrchr(strC, '.');
		if (dotP) {
			*dotP = 0;
		}
		
		dotP = strrchr(strC, '(');
		if (dotP) {
			*dotP = 0;
		}
		
		len = strlen(strC);
		if (strC[len - 1] == ' ') {
			strC[len - 1] = 0;
		}
		
		strcat_OrderAndExt(
			formatP->nibblizedB, 
			(FSType)formatP->sectorOrder, 
			descIndex, 
			strC);
		
		CopyCStringToPascal(strC, newNameStr);

		err = FSpRename(&ImageRec_FileSpec(imageRecP), newNameStr);
		if (!err) {
			CopyString(newNameStr, ImageRec_FileSpec(imageRecP).name);
		}
	}
	
	return err;
}

//	these are files that you can see in the open dialog
void	FillInTypeList(OSType *theTypeList, short *numTypes)
{
	short		descIndex;
	
	*numTypes = IC_FileDescIndex_NUMTYPES;

	for (
		descIndex = 0; 
		descIndex < IC_FileDescIndex_NUMTYPES; 
		descIndex++
	) {
		theTypeList[descIndex] = IC_FileTypeArray[descIndex].fileType;
	}
}

/******************************/

OSErr	FSpGetDiskImageOrder(FSSpecPtr fileSpecP, FSType *curOrder)
{
	OSErr	err = noErr;
	FInfo	finfo;
	
	if (!err) err = FSpGetFInfo(fileSpecP, &finfo);
	
	if (!err) {
		char			fileNameAC[256];
		ExtensionStr	extensionAC = { 0 };
		short			descIndex;
		char			*dotP;
		short			strLenS;
		
		CopyPascalStringToC(fileSpecP->name, fileNameAC);
		
		dotP = strrchr(fileNameAC, '.');
		
		//	no dot? you're binary buddy!
		
		if (dotP == NULL) {
			extensionAC[0] = 0;
		} else {
			strLenS	= strlen(dotP);
			
			if (strLenS > IC_kExtensionLen - 1) {
				extensionAC[0] = 0;
			} else {
				strcpy(extensionAC, dotP);
			}
		}
		
		for (
			descIndex = 0; 
			descIndex < IC_FileDescIndex_NUMTYPES; 
			descIndex++
		) {
			if (
				finfo.fdType == IC_FileTypeArray[descIndex].fileType
				|| memcmp(extensionAC, IC_FileTypeArray[descIndex].fileExtensionAC, strlen(IC_FileTypeArray[descIndex].fileExtensionAC)) == 0
			) {
				*curOrder = IC_FileTypeArray[descIndex].sectorOrder;
				break;
			}
		}
		
		if (descIndex == IC_FileDescIndex_NUMTYPES) {
			*curOrder	= FSType_GEN;
			//err			= IC_Err_UNSUPPORTED_IMAGE_TYPE;
		}
	}
	
	return err;
}

/******************************/

//#define	BEEP_ON_READ

OSErr	ReadChunk(
	DiskImageRec	*imageRecP,
	void			*buf, 
	long			start, 
	long			count)
{
	OSErr		err = noErr;
	
	#ifdef BEEP_ON_READ
		SysBeep(1);
		if (check_key_down(option_key)) {
			DebugStr("\pExiting because of programmer break.");
		}
	#endif
	
	if (IS_ImageRec_PHYSICAL(imageRecP)) {
		ulong	blockL = start >> 9;
		
		ASSERT((ulong)(start & 0xFFFFFE00) == (ulong)start);
		
		if (count == Pro_kBytesPerBlock) {
			err = GetPhysicalBlock(&ImageRec_VolRec(imageRecP).phys, blockL, buf);
		} else  if (count == Dos_kBytesPerSector) {
			char	blockA[Gen_kBytesPerBlock];
			
			err = GetPhysicalBlock(&ImageRec_VolRec(imageRecP).phys, blockL >> 1 , blockA);
			
			if (blockL & 0x01) {
				//	if odd
				memcpy(buf, &blockA[Dos_kBytesPerSector], Dos_kBytesPerSector);
			} else {
				//	else even
				memcpy(buf, &blockA[0], Dos_kBytesPerSector);
			}
		} else {
			char	bufAC[256];
			
			sprintf(bufAC, "Trying to read %d bytes (must be 256 or 512)", (int)count);
			ReportErrorStr(err = -1, bufAC);
		}
	} else {
		short		*refNumP	= &ImageRec_VolRec(imageRecP).image.refNum;
		Boolean		isOpenB		= *refNumP != 0;
		
		if (!isOpenB) {
			err = FSpOpenDF(&ImageRec_FileSpec(imageRecP), fsCurPerm, refNumP);
		}
		
		if (!err) {
			err = SetFPos(*refNumP, fsFromStart, start);
			if (!err) err = FSRead(*refNumP, &count, buf);

			if (!isOpenB) {
				OSErr	err2;
			
				err2 = FSClose(*refNumP);
				if (!err) err = err2;
				*refNumP = 0;
			}
		}
	}

	return err;
}

/******************************/
OSErr	WriteChunk(
	DiskImageRec	*imageRecP,
	void			*buf, 
	long			start, 
	long			count)
{
	OSErr		err = noErr;

	if (IS_ImageRec_PHYSICAL(imageRecP)) {
		ulong	blockL = start >> 9;
		
		ASSERT((ulong)(start & 0xFFFFFE00) == (ulong)start);
		ASSERT(count == Pro_kBytesPerBlock);
		
		if (count != Pro_kBytesPerBlock) {
			ReportErrorStr(count, "Trying to write wrong number of bytes / block");
			err = 1;
		}
		
		if (!err) {
			err = SetPhysicalBlock(&ImageRec_VolRec(imageRecP).phys, blockL, (Pro_Block *)buf);
		}
	} else {
		Boolean		isOpenB = ImageRec_VolRec(imageRecP).image.refNum != 0;
		
		if (!isOpenB) {
			err = FSpOpenDF(
				&ImageRec_FileSpec(imageRecP), fsRdWrPerm, 
				&ImageRec_VolRec(imageRecP).image.refNum);
			
			if (err) {
				ImageRec_VolRec(imageRecP).image.refNum = 0;
			}
		}
		
		if (!err) {
			err = SetFPos(ImageRec_VolRec(imageRecP).image.refNum, fsFromStart, start);
			if (!err) err = FSWrite(ImageRec_VolRec(imageRecP).image.refNum, &count, buf);

			if (!isOpenB) {
				OSErr	err2;

				if (!err) err = FSFlushFile(ImageRec_VolRec(imageRecP).image.refNum);
				err2 = FSClose(ImageRec_VolRec(imageRecP).image.refNum);
				if (!err) err = err2;

				ImageRec_VolRec(imageRecP).image.refNum = 0;
			}
		}
	}
	
	return err;
}

OSErr	ReadImage(DiskImageRec *imageRecP)
{
	OSErr		err					= noErr;
	ulong		img_start_offset	= 0;
	
	if (IS_ImageRec_2img(imageRecP)) {
		img_start_offset = ImageRec_VolRec(imageRecP).image.header.twoimg.img_start_offset;
	} else if (IS_ImageRec_DiskCopy(imageRecP)) {
		img_start_offset = sizeof(Disk_DiskCopy_Header);
	}
		
	if (IS_ImageRec_NIB(imageRecP)) {
		Nib_Disk		*nibDiskP = (Nib_Disk *)TrackNewPtrClear("nib disk", sizeof(Nib_Disk));
		
		if (!nibDiskP) {
			ReportError(err = IC_Err_OUT_OF_MEMORY);
		} else {
			ASSERT(IS_ImageRec_IN_MEMORY(imageRecP));
			
			ADFS_Log("Reading NIB file\n");
			err = ReadChunk(
				imageRecP, nibDiskP, img_start_offset, sizeof(Nib_Disk));
			
			if (!Nib_DeNibblize(nibDiskP, imageRecP)) {
				ReportError(err = IC_Err_CANT_READ_NIB_DATA);
			}

			TrackDisposePtr((Ptr)nibDiskP);
		}
	} else {
		ADFS_Log("Reading Raw file\n");
		err = ReadChunk(
			imageRecP, imageRecP->image.gen, 
			img_start_offset, sizeof(Gen_Disk));
	}
	
	return err;
}

OSErr	WriteImage(DiskImageRec *imageRecP)
{
	FSType		curType				= imageRecP->curOrder;
	OSErr		err					= noErr;
	ulong		img_start_offset	= 0;
	ulong		img_end_extraBytes	= 0;
	long		fileSize			= 0;
	
	if (IS_ImageRec_2img(imageRecP)) {
		//	sector order/nib can change
		//	volume number can change
		//	lock bit can change
		//	num of blocks/image_len can change 
		//		(only when creating new large ProDOS volume)
		
		img_start_offset = ImageRec_VolRec(imageRecP).image.header.twoimg.img_start_offset;
		
		ByteSwap2IMG(&ImageRec_VolRec(imageRecP).image.header.twoimg);

		err = WriteChunk(
			imageRecP, &ImageRec_VolRec(imageRecP).image.header.twoimg,
			0, sizeof(Disk_2IMG_Header));

		ByteSwap2IMG(&ImageRec_VolRec(imageRecP).image.header.twoimg);

		fileSize			= ImageRec_VolRec(imageRecP).image.header.twoimg.img_len;
		img_end_extraBytes	= ImageRec_VolRec(imageRecP).image.header.twoimg.comment_len
			+ ImageRec_VolRec(imageRecP).image.header.twoimg.creator_data_len;
	} else if (IS_ImageRec_DiskCopy(imageRecP)) {
		//	disk name can change
		//	checksum can change, 
		
		img_start_offset = sizeof(Disk_DiskCopy_Header);
		
		if (!ImageRec_VolRec(imageRecP).image.skipCheckSumB) {
			CalcDiskCopyChecksum(imageRecP);
		}
		
		err = WriteChunk(
			imageRecP, &ImageRec_VolRec(imageRecP).image.header.diskCopy,
			0, img_start_offset);
		
		fileSize			= ImageRec_VolRec(imageRecP).image.header.diskCopy.dataSize;
		img_end_extraBytes	= ImageRec_VolRec(imageRecP).image.header.diskCopy.tagSize;
	}
	
	if (!err) {
		if (IS_ImageRec_NIB(imageRecP)) {
			Nib_Disk		*nibDiskP = (Nib_Disk *)TrackNewPtrClear("nib disk (format change)", sizeof(Nib_Disk));
			
			if (!nibDiskP) {
				ReportError(err = IC_Err_OUT_OF_MEMORY);
			} else {
				ASSERT(IS_ImageRec_IN_MEMORY(imageRecP));
				
				//	don't support hybrid NIB disks yet?
				ASSERT(imageRecP->deviceP->refCountS == 1);
				
				ConvertImage(imageRecP, FSType_C2P);
				Nib_Nibblize(imageRecP, nibDiskP);

				fileSize = sizeof(Nib_Disk);

				err = WriteChunk(
					imageRecP, 
					nibDiskP,
					img_start_offset, 
					fileSize);
				
				TrackDisposePtr((Ptr)nibDiskP);
				ConvertImage(imageRecP, curType);
			}
		} else if (IS_ImageRec_IN_MEMORY(imageRecP)) {
			ConvertImage(imageRecP, ImageRec_OrigOrder(imageRecP));
		
			fileSize	= sizeof(Gen_Disk);

			err = WriteChunk(
				imageRecP, 
				&((char *)imageRecP->image.gen)[imageRecP->partition.writeStartL],
				img_start_offset + imageRecP->partition.writeStartL, 
				imageRecP->partition.writeLengthL);

			ConvertImage(imageRecP, curType);
		} else if (
			ImageRec_DiskType(imageRecP) == DiskType_onDisk_Raw
			|| IS_ImageRec_PHYSICAL(imageRecP)
		) {
			fileSize = 0;
		}
	}
		
	if (!err && fileSize) {
		long	finalFileSize = img_start_offset + fileSize + img_end_extraBytes;
		
		/*
			yeah it's strange, i'm setting the EOF of the file
			from 2 different places, i've got the real one in
			CDisk (where I think this all belongs)
			and another one here.
			
			I don't think it goes here.
			
			but everyone gets set here except the onDisk_Raw
			type, which is set in CDisk
		*/

		if (ImageRec_VolRec(imageRecP).image.refNum) {
			err = SetEOF(ImageRec_VolRec(imageRecP).image.refNum, finalFileSize);
		} else {
			err = FSpSetEOF(&ImageRec_FileSpec(imageRecP), finalFileSize);
		}
	}
			
	if (err == opWrErr) {
		ReportErrorStr(-1, "Your changes were NOT saved.  The disk image is "
			"currently open with write permission.  Perhaps you have it mounted "
			"in an emulator or another program?");
	}

	return err;
}

char	*strcat_OrderAndExt(
	Boolean					nibblizedB, 
	FSType					sectorOrder, 
	IC_FileDescIndexType	descIndex, 
	char					*defaultNameAC)
{
	if (!nibblizedB) {
	
		switch (sectorOrder) {
			
			default: {
				strcat(defaultNameAC, " (??)");
				break;
			}
			
			case FSType_DOS: {
				strcat(defaultNameAC, " (do)");
				break;
			}
			
			case FSType_PAS:
			case FSType_PRO: {
				strcat(defaultNameAC, " (po)");
				break;
			}

			case FSType_C2P: {
				strcat(defaultNameAC, " (ctpo)");
				break;
			}

			case FSType_CPM: {
				strcat(defaultNameAC, " (cpmo)");
				break;
			}
		}
	} else {
		if (descIndex != IC_FileDescIndex_NIBL) {
			strcat(defaultNameAC, " (nib)");
		}
	}
	
	strcat(defaultNameAC, IC_FileTypeArray[descIndex].fileExtensionAC);
	
	return defaultNameAC;
}

