/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * This is a container for the Parsons Engineering FocusDrive.
 *
 * The format was reverse-engineered by Ranger Harke.
 */
#include "StdAfx.h"
#include "DiskImgPriv.h"


const int kBlkSize = 512;
const int kPartMapBlock = 0;	// partition map lives here
const int kMaxPartitions = 30;	// max allowed partitions on a drive
const int kPartNameStart = 1;	// partition names start here (2 blocks)
const int kPartNameLen = 32;	// max length of partition name

static const char* kSignature = "Parsons Engin.";
const int kSignatureLen = 14;

/*
 * Format of partition map.  It resides in the first 256 bytes of block 0.
 * All values are in little-endian order.
 *
 * We also make space here for the partition names, which live on blocks 1+2.
 */
typedef struct DiskFSFocusDrive::PartitionMap {
	unsigned char	signature[kSignatureLen];
	unsigned char	unknown1;
	unsigned char	partCount;		// could be ushort, combined w/unknown1
	unsigned char	unknown2[16];
	struct Entry {
		unsigned long	startBlock;
		unsigned long	blockCount;
		unsigned long	unknown1;
		unsigned long	unknown2;
		unsigned char	name[kPartNameLen+1];
	} entry[kMaxPartitions];
} PartitionMap;


/*
 * Figure out if this is a FocusDrive partition.
 *
 * The "imageOrder" parameter has no use here, because (in the current
 * version) embedded parent volumes are implicitly ProDOS-ordered.
 */
/*static*/ DIError
DiskFSFocusDrive::TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder)
{
	DIError dierr = kDIErrNone;
	unsigned char blkBuf[kBlkSize];
	int partCount;

	/*
	 * See if block 0 is a FocusDrive partition map.
	 */
	dierr = pImg->ReadBlockSwapped(kPartMapBlock, blkBuf, imageOrder,
				DiskImg::kSectorOrderProDOS);
	if (dierr != kDIErrNone)
		goto bail;

	if (memcmp(blkBuf, kSignature, kSignatureLen) != 0) {
		WMSG0(" FocusDrive partition signature not found in first part block\n");
		dierr = kDIErrFilesystemNotFound;
		goto bail;
	}

	partCount = blkBuf[0x0f];
	if (partCount == 0 || partCount > kMaxPartitions) {
		WMSG1(" FocusDrive partition count looks bad (%d)\n", partCount);
		dierr = kDIErrFilesystemNotFound;
		goto bail;
	}

	// success!
	WMSG1(" Looks like FocusDrive with %d partitions\n", partCount);

bail:
	return dierr;
}


/*
 * Unpack a partition map block into a partition map data structure.
 */
/*static*/ void
DiskFSFocusDrive::UnpackPartitionMap(const unsigned char* buf,
	const unsigned char* nameBuf, PartitionMap* pMap)
{
	const unsigned char* ptr;
	const unsigned char* namePtr;
	int i;

	memcpy(pMap->signature, &buf[0x00], kSignatureLen);
	pMap->unknown1 = buf[0x0e];
	pMap->partCount = buf[0x0f];
	memcpy(pMap->unknown2, &buf[0x10], 16);

	ptr = &buf[0x20];
	namePtr = &nameBuf[kPartNameLen];	// not sure what first 32 bytes are
	for (i = 0; i < kMaxPartitions; i++) {
		pMap->entry[i].startBlock = GetLongLE(ptr);
		pMap->entry[i].blockCount = GetLongLE(ptr+4);
		pMap->entry[i].unknown1 = GetLongLE(ptr+8);
		pMap->entry[i].unknown2 = GetLongLE(ptr+12);

		memcpy(pMap->entry[i].name, namePtr, kPartNameLen);
		pMap->entry[i].name[kPartNameLen] = '\0';

		ptr += 0x10;
		namePtr += kPartNameLen;
	}

	assert(ptr == buf + kBlkSize);
}

/*
 * Debug: dump the contents of the partition map.
 */
/*static*/ void
DiskFSFocusDrive::DumpPartitionMap(const PartitionMap* pMap)
{
	int i;

	WMSG1(" FocusDrive partition map (%d partitions):\n", pMap->partCount);
	for (i = 0; i < pMap->partCount; i++) {
		WMSG4("  %2d: %8ld %8ld '%s'\n", i, pMap->entry[i].startBlock,
			pMap->entry[i].blockCount, pMap->entry[i].name);
	}
}


/*
 * Open up a sub-volume.
 */
DIError
DiskFSFocusDrive::OpenSubVolume(long startBlock, long numBlocks,
	const char* name)
{
	DIError dierr = kDIErrNone;
	DiskFS* pNewFS = nil;
	DiskImg* pNewImg = nil;
	//bool tweaked = false;

	WMSG2("Adding %ld +%ld\n", startBlock, numBlocks);

	if (startBlock > fpImg->GetNumBlocks()) {
		WMSG2("FocusDrive start block out of range (%ld vs %ld)\n",
			startBlock, fpImg->GetNumBlocks());
		return kDIErrBadPartition;
	}
	if (startBlock + numBlocks > fpImg->GetNumBlocks()) {
		WMSG2("FocusDrive partition too large (%ld vs %ld avail)\n",
			numBlocks, fpImg->GetNumBlocks() - startBlock);
		fpImg->AddNote(DiskImg::kNoteInfo,
			"Reduced partition from %ld blocks to %ld.\n",
			numBlocks, fpImg->GetNumBlocks() - startBlock);
		numBlocks = fpImg->GetNumBlocks() - startBlock;
		//tweaked = true;
	}

	pNewImg = new DiskImg;
	if (pNewImg == nil) {
		dierr = kDIErrMalloc;
		goto bail;
	}

	dierr = pNewImg->OpenImage(fpImg, startBlock, numBlocks);
	if (dierr != kDIErrNone) {
		WMSG3(" FocusDriveSub: OpenImage(%ld,%ld) failed (err=%d)\n",
			startBlock, numBlocks, dierr);
		goto bail;
	}

	//WMSG2("  +++ CFFASub: new image has ro=%d (parent=%d)\n",
	//	pNewImg->GetReadOnly(), pImg->GetReadOnly());

	/* figure out what the format is */
	dierr = pNewImg->AnalyzeImage();
	if (dierr != kDIErrNone) {
		WMSG1(" FocusDriveSub: analysis failed (err=%d)\n", dierr);
		goto bail;
	}

	/* we allow unrecognized partitions */
	if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown ||
		pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown)
	{
		WMSG2(" FocusDriveSub (%ld,%ld): unable to identify filesystem\n",
			startBlock, numBlocks);
		DiskFSUnknown* pUnknownFS = new DiskFSUnknown;
		if (pUnknownFS == nil) {
			dierr = kDIErrInternal;
			goto bail;
		}
		//pUnknownFS->SetVolumeInfo((const char*)pMap->pmParType);
		pNewFS = pUnknownFS;
	} else {
		/* open a DiskFS for the sub-image */
		WMSG2(" FocusDriveSub (%ld,%ld) analyze succeeded!\n", startBlock, numBlocks);
		pNewFS = pNewImg->OpenAppropriateDiskFS(true);
		if (pNewFS == nil) {
			WMSG0(" FocusDriveSub: OpenAppropriateDiskFS failed\n");
			dierr = kDIErrUnsupportedFSFmt;
			goto bail;
		}
	}
	pNewImg->AddNote(DiskImg::kNoteInfo, "Partition name='%s'.", name);

	/* we encapsulate arbitrary stuff, so encourage child to scan */
	pNewFS->SetScanForSubVolumes(kScanSubEnabled);

	/*
	 * Load the files from the sub-image.  When doing our initial tests,
	 * or when loading data for the volume copier, we don't want to dig
	 * into our sub-volumes, just figure out what they are and where.
	 *
	 * If "initialize" fails, the sub-volume won't get added to the list.
	 * It's important that a failure at this stage doesn't cause the whole
	 * thing to fall over.
	 */
	InitMode initMode;
	if (GetScanForSubVolumes() == kScanSubContainerOnly)
		initMode = kInitHeaderOnly;
	else
		initMode = kInitFull;
	dierr = pNewFS->Initialize(pNewImg, initMode);
	if (dierr != kDIErrNone) {
		WMSG1(" FocusDriveSub: error %d reading list of files from disk", dierr);
		goto bail;
	}

	/* add it to the list */
	AddSubVolumeToList(pNewImg, pNewFS);
	pNewImg = nil;
	pNewFS = nil;

bail:
	delete pNewFS;
	delete pNewImg;
	return dierr;
}

/*
 * Check to see if this is a FocusDrive volume.
 */
/*static*/ DIError
DiskFSFocusDrive::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
	DiskImg::FSFormat* pFormat, FSLeniency leniency)
{
	if (pImg->GetNumBlocks() < kMinInterestingBlocks)
		return kDIErrFilesystemNotFound;
	if (pImg->GetIsEmbedded())		// don't look for partitions inside
		return kDIErrFilesystemNotFound;

	/* assume ProDOS -- shouldn't matter, since it's embedded */
	if (TestImage(pImg, DiskImg::kSectorOrderProDOS) == kDIErrNone) {
		*pFormat = DiskImg::kFormatFocusDrive;
		*pOrder = DiskImg::kSectorOrderProDOS;
		return kDIErrNone;
	}

	WMSG0("  FS didn't find valid FocusDrive\n");
	return kDIErrFilesystemNotFound;
}


/*
 * Prep the FocusDrive "container" for use.
 */
DIError
DiskFSFocusDrive::Initialize(void)
{
	DIError dierr = kDIErrNone;

	WMSG1("FocusDrive initializing (scanForSub=%d)\n", fScanForSubVolumes);

	/* seems pointless *not* to, but we just do what we're told */
	if (fScanForSubVolumes != kScanSubDisabled) {
		dierr = FindSubVolumes();
		if (dierr != kDIErrNone)
			return dierr;
	}

	/* blank out the volume usage map */
	SetVolumeUsageMap();

	return dierr;
}


/*
 * Find the various sub-volumes and open them.
 */
DIError
DiskFSFocusDrive::FindSubVolumes(void)
{
	DIError dierr = kDIErrNone;
	unsigned char buf[kBlkSize];
	unsigned char nameBuf[kBlkSize*2];
	PartitionMap map;
	int i;

	dierr = fpImg->ReadBlock(kPartMapBlock, buf);
	if (dierr != kDIErrNone)
		goto bail;
	dierr = fpImg->ReadBlock(kPartNameStart, nameBuf);
	if (dierr != kDIErrNone)
		goto bail;
	dierr = fpImg->ReadBlock(kPartNameStart+1, nameBuf+kBlkSize);
	if (dierr != kDIErrNone)
		goto bail;
	UnpackPartitionMap(buf, nameBuf, &map);
	DumpPartitionMap(&map);

	for (i = 0; i < map.partCount; i++) {
		dierr = OpenVol(i, map.entry[i].startBlock, map.entry[i].blockCount,
					(const char*)map.entry[i].name);
		if (dierr != kDIErrNone)
			goto bail;
	}

bail:
	return dierr;
}

/*
 * Open the volume.  If it fails, open a placeholder instead.  (If *that*
 * fails, return with an error.)
 */
DIError
DiskFSFocusDrive::OpenVol(int idx, long startBlock, long numBlocks,
	const char* name)
{
	DIError dierr;

	dierr = OpenSubVolume(startBlock, numBlocks, name);
	if (dierr != kDIErrNone) {
		if (dierr == kDIErrCancelled)
			goto bail;
		DiskFS* pNewFS = nil;
		DiskImg* pNewImg = nil;

		WMSG1(" FocusDrive failed opening sub-volume %d\n", idx);
		dierr = CreatePlaceholder(startBlock, numBlocks, name, NULL,
					&pNewImg, &pNewFS);
		if (dierr == kDIErrNone) {
			AddSubVolumeToList(pNewImg, pNewFS);
		} else {
			WMSG1("  FocusDrive unable to create placeholder (err=%d)\n",
				dierr);
			// fall out with error
		}
	}

bail:
	return dierr;
}
