//	CDiskPas.c

#include "CDialogCopy.h"
#include "Utils.h"
#include "IC_FileIO.h"
#include "ADFS_O_Callbacks.h"
#include "ADFS_Icons.h"
#include "ProStructs.h"
#include "PasStructs.h"
#include "CFolderPas.h"
#include "CFilePas.h"
#include "CDiskPas.h"

OSErr		CDiskPas::IDiskPas(
	CDesktop		*desktop, 
	DiskImageRec	*imageRec
) {
	OSErr			err		= noErr;
	
	i_blockBuf			= NULL;
	i_recentBlockNum	= Pas_kNoBlock;
	i_blocksInVolume	= Pas_kBlocksPerDisk;	//	at least

	if (!err) err = _inherited::IDisk(desktop, imageRec);

	if (!err) {
		i_blocksInVolume	= GetTotalBlocks();
		
		if (i_blocksInVolume == 0) {
			err = IC_Err_CANT_READ_PASCAL_BLOCK;
		}
	}

	if (!err) err = Pas_CacheDirectory();

	if (!err) {
		if (NewObject(i_rootDir.pas, CFolderPas, err)) {
			err = i_rootDir.pas->IFolderPas(
				this, Pas_kDirStartBlock);
		}
	}
	
	return err;
}

void		CDiskPas::Dispose(void)
{
	_inherited::Dispose();
}

Pas_DirEntry	*CDiskPas::GetMyEntry(void)
{
	Pas_DirEntry		*entryP = NULL;
	
	if (i_rootDir.pas) {
		entryP = i_rootDir.pas->GetEntry(Pas_kDirStartBlock, 0);
	} else {
		OSErr		err = GetBlock(Pas_kDirStartBlock, (Pas_Block **)&entryP);
		
		if (err) {
			entryP = NULL;
		}
	}
	
	return entryP;
}

OSErr		CDiskPas::BuildFileTypeMenu(void)
{
	OSErr		err = noErr;
	
	i_fileTypeMenu = GetMenuHandle(208);
	
	if (!i_fileTypeMenu) {
		i_fileTypeMenu = GetMenu(208);
		if (!i_fileTypeMenu) {
			ReportError(err = IC_Err_COULDNT_LOAD_DLG_ITEM);
		}
		
		if (!err) {
		//	Dos_BuildFileTypeMenu(i_fileTypeMenu);
		}
	}
	
	return err;
}

short		CDiskPas::MenuItemToFileType(short menuItem, ushort *auxType)
{
	return 0;
}

char		*CDiskPas::GetName(char *buf)
{
	_inherited::GetName(buf);
	
	if (buf[0] == 0) {
		Pas_DirEntry	*entryP = GetMyEntry();
		
		buf[0] = 0;
		
		if (entryP) {
			sprintf(buf, "%#s", entryP->entryType.volume.volumeID);
		}
	}
	
	return buf;
}

void		CDiskPas::SetName(char *buf)
{
	Pas_DirEntry		*entryP = GetMyEntry();
	
	if (entryP) {
		Pas_SanitizeName(buf, Pas_kVolIDLength);
		
		CopyCStringToPascal(buf, entryP->entryType.volume.volumeID);
		
		_inherited::SetName(buf);		
		Pas_FlushDirectory();
	}
}


OSErr		CDiskPas::SetGenericBlock(ushort blockNumS, Gen_Block *blockP)
{
	OSErr				err			= noErr;
	Pas_Block			*prevBlockP = i_blockBuf;
	Pas_BlockNum		prevBlockS	= i_recentBlockNum;
	
	i_recentBlockNum	= blockNumS;
	i_blockBuf			= (Pas_Block *)blockP;

	err = SetBlock();

	i_recentBlockNum	= prevBlockS;
	i_blockBuf			= prevBlockP;

	return err;
}

OSErr		CDiskPas::GetGenericBlock(ushort blockNumS, Gen_Block **blockP)
{
	return GetBlock(blockNumS, (Pas_Block **)blockP);
}

OSErr		CDiskPas::GetBlock(Pas_BlockNum blockNum, Pas_Block **blockP)
{
	OSErr		err = noErr;
	
	*blockP = NULL;
	
	if (blockNum >= i_blocksInVolume) {
		err = IC_Err_READ_ILLEGAL_FILE_BLOCK;
		ReportError(err);
	} else {
		if (i_recentBlockNum != blockNum) {
			Pas_Block		*blockBufP;

			err = Pas_GetBlock(i_imageRec, blockNum, &blockBufP);
			i_blockBuf = blockBufP;

			if (!err) {
				i_recentBlockNum	= blockNum;
			} else {
				ReportError(err);
				i_blockBuf			= NULL;
				i_recentBlockNum	= Pas_kNoBlock;
			}
		}

		if (!err) {
			*blockP	= i_blockBuf;
		}
	}
	
	return err;
}

OSErr		CDiskPas::SetBlock(void)
{
	OSErr		err = noErr;

	if (IS_ImageRec_IN_MEMORY(i_imageRec)) {
		if (i_flushMemLevel == 1) {
			err = WriteImage(i_imageRec);
		}
	} else {
		ulong		image_offsetUL = 0;
		
		switch (ImageRec_DiskType(i_imageRec)) {

			case DiskType_onDisk_DiskCopy: {
				image_offsetUL = sizeof(Disk_DiskCopy_Header);
				break;
			}

			case DiskType_onDisk_2img: {
				image_offsetUL = ImageRec_VolRec(i_imageRec).image.header.twoimg.img_start_offset;
				break;
			}
		}
		
		if (i_recentBlockNum != Pas_kNoBlock) {
			err = WriteChunk(
				i_imageRec, i_blockBuf, 
				image_offsetUL + (i_recentBlockNum * sizeof(Pas_Block)), 
				sizeof(Pas_Block)
			);
		}
	}
	
	if (err) {
		ReportError(err);
	}
	
	return err;
}

ADFS_IconType		CDiskPas::GetIconType(void)
{
	short	icon;

	if (IS_ImageRec_IN_MEMORY(i_imageRec)) {
		icon = ADFS_Icon_PAS_SPRO + GetSectorOrderForIcon();
	} else {
		ulong	volSize = GetVolumeSize();
		
		if (volSize <= kPro_Disk800_Size) {
			icon = ADFS_Icon_PRO_800;
		} else if (volSize <= kPro_Disk1440_Size) {
			icon = ADFS_Icon_PRO_1440;
		} else {
			icon = ADFS_Icon_PRO_HARD_DISK;
		}
	}
	
	return icon;
}

char		*CDiskPas::GetDescription(char *buf)
{
	strcpy(buf, "Pascal Disk");
	return buf;
}

DateTimeRec		*CDiskPas::GetModifiedTime(DateTimeRec *dt)
{
	Pas_DirEntry	*entryP = GetMyEntry();

	if (entryP) {
		Pas_PasToMacDate(&entryP->entryType.volume.lastBoot, dt);
	}

	return dt;
}

ulong		CDiskPas::GetTotalBlocks(void)
{
	ulong			blocks	= 0;
	Pas_DirEntry	*entryP = GetMyEntry();

	if (entryP) {
		blocks = GetRboShort(entryP->entryType.volume.endOfVolume);
	}

	return blocks;	
}

ulong		CDiskPas::GetVolumeSize(void)
{
	return i_blocksInVolume * Pas_kBytesPerBlock;
}

typedef struct {
	Pas_BlockNum	testBlock;
	Boolean			isFreeB;
} Pas_IsBlockFreeData;

//	static
static	OSErr	Pas_IsBlockFreeCB(
	CFolderPas			*thiz, 
	Pas_DirEntry		*entry, 
	Pas_BlockNum		blockNum, 
	Pas_EntryIndex		entryIndex, 
	Boolean				*done, 
	void				*data)
{
	OSErr					err			= noErr;
	Pas_IsBlockFreeData		*freeData	= (Pas_IsBlockFreeData *)data;
	
	//	not deleted
//	if (!Pas_IsEmptyEntry(entry)) {
		if (
			freeData->testBlock >= GetRboShort(entry->firstBlock)
			&& freeData->testBlock < GetRboShort(entry->lastBlockPlusOne)
		) {
			freeData->isFreeB = FALSE;
			*done = TRUE;
		}
//	}
	
	return err;
}

OSErr		CDiskPas::IsBlockFree(Pas_BlockNum curBlock, Boolean *isFreeB)
{
	OSErr					err = noErr;
	Pas_IsBlockFreeData		freeData;
	
	freeData.testBlock	= curBlock;
	freeData.isFreeB	= TRUE;
	
	err = i_rootDir.pas->Pas_ForEachEntry(Pas_IsBlockFreeCB, &freeData);
	
	if (!err) {
		*isFreeB = freeData.isFreeB;
	}
	
	return err;
}

OSErr			CDiskPas::Pas_ForEachBitmapBlock(
	Pas_ForEachBitmapBlockCB	ForEachBitmapBlockUserCB, 
	void						*data)
{
	OSErr				err = noErr;
	Pas_BlockNum		curBlock, maxBlocks = i_blocksInVolume;
	Boolean				isFreeB;
	Boolean				doneB = FALSE;
	
	for (
		curBlock = 0; 
		!err && !doneB && curBlock < maxBlocks; 
		curBlock++
	) {
		err = IsBlockFree(curBlock, &isFreeB);
		
		if (!err) err = (*ForEachBitmapBlockUserCB)(
			this, curBlock, &isFreeB, &doneB, data);
	}
	
	return err;
}

enum	{
	Pas_VBM_Select_NONE, 
	Pas_VBM_Select_GET, 
	Pas_VBM_Select_COUNT, 
	Pas_VBM_Select_NUMTYPES
};
typedef	short	Pas_VBM_SelectType;

typedef struct {
	Pas_VBM_SelectType		selectType;
	Pas_BlockNum			block;
	Boolean					free;
} Pas_FreeBlockCBData;

static	OSErr	Pas_FreeBlockCB(
	CDiskPas		*thiz, 
	Pas_BlockNum	block, 
	Boolean			*free, 
	Boolean			*done, 
	void			*data
) {
	OSErr					err = noErr;
	Pas_FreeBlockCBData		*cbData = (Pas_FreeBlockCBData *)data;
	
	switch (cbData->selectType) {

		case Pas_VBM_Select_GET: {
			if (*free) {
				cbData->free	= TRUE;
				cbData->block	= block;
				*done			= TRUE;
			}
			break;
		}

		case Pas_VBM_Select_COUNT: {
			if (*free == FALSE) {
				cbData->block++;
			}
			break;
		}

		default: {
			ReportErrorStr(-1, "Huh?");
			err = 1;
			break;
		}
	}

	return err;
}

ulong		CDiskPas::GetVolumeBytesUsed(void)
{
	OSErr					err = noErr;
	Pas_FreeBlockCBData		cbData;
	
	cbData.selectType	= Pas_VBM_Select_COUNT;
	cbData.block		= 0;
	cbData.free			= FALSE;
	
	err = Pas_ForEachBitmapBlock(Pas_FreeBlockCB, &cbData);
	
	return cbData.block * sizeof(Pas_Block);
}

OSErr		CDiskPas::GetFreeBlock(Pas_BlockNum *freeBlock)
{
	OSErr					err = noErr;
	Pas_FreeBlockCBData		cbData;
//	Pas_BlockNum			blockNum = 0;
	
	cbData.selectType	= Pas_VBM_Select_GET;
	cbData.free			= FALSE;
	cbData.block		= 0;

	*freeBlock			= 0;
	
	err = Pas_ForEachBitmapBlock(Pas_FreeBlockCB, &cbData);

	if (err == noErr) {
		if (cbData.free) {
			*freeBlock = cbData.block;
		} else {
			err = IC_Err_DISK_FULL;
		}
	}
	
	return err;
}

static	OSErr	Pas_ZeroBlockCB(
	CDiskPas		*thiz, 
	Pas_BlockNum	block, 
	Boolean			*free, 
	Boolean			*done, 
	void			*data)
{
	OSErr				err = noErr;
	
	if (*free) {
		Pas_Block		*blockP;
		
		err = thiz->GetBlock(block, &blockP);

		if (!err) {
			memfill(blockP, 0, sizeof(Pas_Block));
			err = thiz->SetBlock();
		}
	}

	return err;
}

OSErr		CDiskPas::ZeroUnused(void)
{
	OSErr	err = noErr, err2;
	
	(void)FlushMemDisk(FALSE);
	err = Pas_ForEachBitmapBlock(Pas_ZeroBlockCB, NULL);
	
	//	also, run thru dir entries and zero all the empty ones
	
	err2 = FlushMemDisk(TRUE);
	if (!err) err = err2;
	
	return err;
}

ulong		CDiskPas::GetVolumeMaxFileSize(ushort pro_fileTypeS)
{
	return _inherited::GetVolumeMaxFileSize(pro_fileTypeS);
}

ulong		CDiskPas::CalcBytesUsedByFile(ulong fileSize)
{
	return fileSize;
}

void			CDiskPas::SetTwirled(Boolean twirledB)
{
	Pas_DirEntry	*entryP = GetMyEntry();

	if (entryP) {
		entryP->entryType.volume.loadTime.low &= 0xFE;
		entryP->entryType.volume.loadTime.low |= twirledB;
		(void)Pas_FlushDirectory();
	}
	
	_inherited::SetTwirled(twirledB);
}

Boolean			CDiskPas::GetTwirled(void)
{
	Boolean			twirledB = FALSE;
	Pas_DirEntry	*entryP = GetMyEntry();

	if (entryP) {
		twirledB = entryP->entryType.volume.loadTime.low & 0x01;
	}

	return twirledB;
}

OSErr			CDiskPas::Pas_CacheDirectory(void)
{
	OSErr				err = noErr;
	Pas_Block			*blockP;
	Pas_BlockNum		curBlockIndex;
//	Boolean				doneB = FALSE;
	
	for (
		curBlockIndex = Pas_kDirStartBlock;
		!err && curBlockIndex < Pas_kDirEndBlockPlusOne;
		curBlockIndex++
	) {
		err = i_cDisk.pas->GetBlock(curBlockIndex, &blockP);
		if (!err) {
			i_directory.blocks[curBlockIndex - Pas_kDirStartBlock] = *blockP;
		}
	}
	
	return err;
}

OSErr			CDiskPas::Pas_FlushDirectory(void)
{
	OSErr				err = noErr, err2;
	Pas_Block			*blockP;
	Pas_BlockNum		curBlockIndex;
//	Boolean				doneB = FALSE;
	
	if (IS_ImageRec_IN_MEMORY(i_imageRec)) {
		for (
			curBlockIndex = Pas_kDirStartBlock;
			!err && curBlockIndex < Pas_kDirEndBlockPlusOne;
			curBlockIndex++
		) {
			blockP = &(i_imageRec->image.pas->block[curBlockIndex]);
			*blockP = i_directory.blocks[curBlockIndex - Pas_kDirStartBlock];
		}

		err = SetBlock();
	} else {
		err = FlushMemDisk(FALSE);
		
		if (!err) {
			for (
				curBlockIndex = Pas_kDirStartBlock;
				!err && curBlockIndex < Pas_kDirEndBlockPlusOne;
				curBlockIndex++
			) {
				i_recentBlockNum	= curBlockIndex;
				*i_blockBuf			= i_directory.blocks[curBlockIndex - Pas_kDirStartBlock];

				err = SetBlock();
			}
		}
		
		err2 = FlushMemDisk(TRUE);
		if (!err) err = err2;
	}
	
	return err;
}

static	OSErr 	Pas_CB_S_AscendingDirSort(
	void			*refconPV, 
	CEntryArray		*entriesP, 
	const void		*a,
	const void		*b, 
	int				*resultS)
{
	CDiskPas	*thiz = (CDiskPas *)refconPV;
	
	*resultS = 0;
	
	return thiz->Pas_CB_AscendingDirSort(
		*(CFilePas **)a, 
		*(CFilePas **)b, 
		resultS);
}

OSErr 	CDiskPas::Pas_CB_AscendingDirSort(
	CFilePas 	*a, 
	CFilePas 	*b, 
	int			*resultS)
{
	OSErr			err			= noErr;
	Pas_DirEntry	*entryAP	= a->GetMyEntry();
	Pas_DirEntry	*entryBP	= b->GetMyEntry();
	
	if (entryAP == NULL || entryBP == NULL) {
		ReportError(err = IC_Err_ENTRY_NOT_FOUND);
	} else {
		ASSERT(GetRboShort(entryAP->firstBlock) != GetRboShort(entryBP->firstBlock));
		*resultS = GetRboShort(entryAP->firstBlock) < GetRboShort(entryBP->firstBlock) ? -1 : 1;
	}
	
	return err;
}

OSErr			CDiskPas::Optimize(void)
{
	OSErr			err			= noErr;
	CEntryArray		*entriesP	= i_rootDir.pas->GetDirectory();
	
	if (!entriesP) {
		ReportErrorStr(-1, "Couldn't allocate dir array");
		err = IC_Err_OS;
	} else {
//		err = entriesP->Sort(Pas_CB_S_AscendingDirSort, this);

		if (!err) err = i_cDisk.pas->FlushMemDisk(FALSE);

		if (!err) {
			OSErr			err2;
			Pas_BlockNum	firstFreeBlock;

			err = GetFreeBlock(&firstFreeBlock);
			if (!err) err = entriesP->Iterate(ADFS_TA_Iterate_MOVE_FILE_DOWN, &firstFreeBlock);

			err2 = i_cDisk.pas->FlushMemDisk(TRUE);
			if (!err) err = err2;
		}

		entriesP->Dispose();
	}
	
	return err;
}

/****************************************************/
ushort		CDiskPas::GetSectorsPerBlock(void)
{
	return Pas_kSectorsPerBlock;
}

ushort		CDiskPas::GetBlocksPerTrack(void)
{
	return Pas_kBlocksPerTrack;
}

typedef struct {
	Boolean			getAsBlocksB;
	Pas_BlockNum	*numFreeSP;
	Pas_BlockNum	*blockA0;
} Pas_GetBlockMapRec;

static	OSErr	Pas_GetBlockMap(
	CDiskPas		*thiz, 
	Pas_BlockNum	block, 
	Boolean			*free, 
	Boolean			*done, 
	void			*data
) {
	Pas_GetBlockMapRec		*blockMapP = (Pas_GetBlockMapRec *)data;
	
	if (*free) {
		if (blockMapP->blockA0) {
			blockMapP->blockA0[*(blockMapP->numFreeSP)] = block;
		}
		
		(*(blockMapP->numFreeSP))++;
	}
	
	return noErr;
}

#define		GetEmptyBlocks(_max, _list)		GetUnAllocBlocks(getAsBlocksB, _max, _list)

OSErr		CDiskPas::GetUnAllocBlocks(
	Boolean			getAsBlocksB,
	Pas_BlockNum	*maxEmptyS, 
	Pas_BlockNum	*blockListA)
{
	OSErr					err = noErr;
	Pas_GetBlockMapRec		blockMapRec;
	
	blockMapRec.getAsBlocksB	= getAsBlocksB;
	blockMapRec.numFreeSP		= maxEmptyS;
	blockMapRec.blockA0			= blockListA;
	err = Pas_ForEachBitmapBlock(Pas_GetBlockMap, &blockMapRec);
	
	return err;
}

OSErr		CDiskPas::GetEntryAlloc(
	Boolean			getAsBlocksB, 
	Gen_EntryAlloc	**sectorListH)
{
	OSErr			err = noErr;
	Pas_BlockNum	*blockNumP, curBlockS, maxEmptyS;
	
	*sectorListH	= (Gen_EntryAlloc *)TrackNewPtrClear(
		"entry sectors, for disk", sizeof(Gen_EntryAlloc));
	
	if (*sectorListH == NULL) err = memFullErr;
	
	if (!err) {		
		(**sectorListH).allocSize = getAsBlocksB ? Gen_AllocSize_SHORT : Gen_AllocSize_SECTORS;
		
		blockNumP = (Pas_BlockNum *)TrackNewPtrClear(
			"entry sectors, boot blocks", 
			sizeof(Pas_BlockNum) * Pas_kNumBootBlocks);
		
		if (blockNumP == NULL) err = memFullErr;
	}
	
	if (!err) {
		(**sectorListH).type[Gen_Alloc_BOOT].totalS				= Pas_kNumBootBlocks;
		(**sectorListH).type[Gen_Alloc_BOOT].u.short_blocksA	= blockNumP;
		for (curBlockS = 0; curBlockS < Pas_kNumBootBlocks; curBlockS++) {
			blockNumP[curBlockS] = curBlockS;
		}

		blockNumP = (Pas_BlockNum *)TrackNewPtrClear(
			"entry sectors, directory blocks", 
			sizeof(Pas_BlockNum) * Pas_kNumDirBlocks);

		if (blockNumP == NULL) err = memFullErr;
	}
	
	if (!err) {
		(**sectorListH).type[Gen_Alloc_DIRECTORY].totalS			= Pas_kNumDirBlocks;
		(**sectorListH).type[Gen_Alloc_DIRECTORY].u.short_blocksA	= blockNumP;
		for (curBlockS = 0; curBlockS < Pas_kNumDirBlocks; curBlockS++) {
			blockNumP[curBlockS] = curBlockS + Pas_kDirStartBlock;
		}
	}
	
	//	no volume bitmap in a Pas disk, so skip right to empty blocks

	maxEmptyS = 0;
	if (!err) err = GetEmptyBlocks(&maxEmptyS, NULL);
			
	if (!err && maxEmptyS) {
		blockNumP = (Pas_BlockNum *)TrackNewPtrClear(
			"entry sectors, free blocks", 
			sizeof(Pas_BlockNum) * maxEmptyS);
		
		if (blockNumP == NULL) err = memFullErr;

		if (!err) {
			(**sectorListH).type[Gen_Alloc_NONE].totalS				= maxEmptyS;
			(**sectorListH).type[Gen_Alloc_NONE].u.short_blocksA	= blockNumP;

			maxEmptyS = 0;
			if (!err) err = GetEmptyBlocks(&maxEmptyS, blockNumP);
		}
	}

	if (!err && !getAsBlocksB) {
		err = EntryBlocksToSectors(*sectorListH);
	}
	
	if (err) {
		DisposeEntryAlloc(*sectorListH);
		*sectorListH = NULL;
	}
	
	return err;
}

OSErr		CDiskPas::NewDisk_Completion(ADFS_NewDiskCompletionRec *recP)
{
	OSErr		err = noErr;
	
	err = _inherited::NewDisk_Completion(recP);
	if (!err) {
		CFolderPas	*folderP	= i_rootDir.pas;

		if (!recP->newDiskRecP->bootableB) {
			CEntry		*entryP;
			
			recP->copyDialogP->IncrementProgress(FALSE, gProgStrs[ADFS_NewDiskProg_DELETE_BOOT]);
			
			entryP = folderP->GetIndEntry(1);
			if (entryP) entryP->Delete();
			
			entryP = folderP->GetIndEntry(0);
			if (entryP) entryP->Delete();
			
			recP->copyDialogP->IncrementProgress(FALSE, gProgStrs[ADFS_NewDiskProg_ZERO]);
			ZeroUnused();
		}
	}
	
	return err;
}
