/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * DiskFS base class.
 */
#include "StdAfx.h"
#include "DiskImgPriv.h"


/*
 * ===========================================================================
 *		A2File
 * ===========================================================================
 */

/*
 * Set the quality level (a/k/a damage level) of a file.
 *
 * Refuse to "improve" the quality level of a file.
 */
void
A2File::SetQuality(FileQuality quality)
{
	if (quality == kQualityGood &&
		(fFileQuality == kQualitySuspicious || fFileQuality == kQualityDamaged))
	{
		assert(false);
		return;
	}
	if (quality == kQualitySuspicious && fFileQuality == kQualityDamaged) {
		//assert(false);
		return;
	}

	fFileQuality = quality;
}

/*
 * Reset the quality level after making repairs.
 */
void
A2File::ResetQuality(void)
{
	fFileQuality = kQualityGood;
}


/*
 * ===========================================================================
 *		DiskFS
 * ===========================================================================
 */

/*
 * Set the DiskImg pointer.  We add or subtract from the DiskImg's ref count
 * so that it can be sure there are no DiskFS objects left dangling when the
 * DiskImg is deleted.
 */
void
DiskFS::SetDiskImg(DiskImg* pImg)
{
	if (pImg == nil && fpImg == nil) {
		WMSG0("SetDiskImg: no-op (both nil)\n");
		return;
	} else if (fpImg == pImg) {
		WMSG0("SetDiskImg: no-op (old == new)\n");
		return;
	}

	if (fpImg != nil)
		fpImg->RemoveDiskFS(this);
	if (pImg != nil)
		pImg->AddDiskFS(this);
	fpImg = pImg;
}

/*
 * Flush changes to disk.
 */
DIError
DiskFS::Flush(DiskImg::FlushMode mode)
{
	SubVolume* pSubVol = GetNextSubVolume(nil);
	DIError dierr;

	while (pSubVol != nil) {

		// quick sanity check
		assert(pSubVol->GetDiskFS()->GetDiskImg() == pSubVol->GetDiskImg());

		dierr = pSubVol->GetDiskFS()->Flush(mode);	// recurse
		if (dierr != kDIErrNone)
			return dierr;

		pSubVol = GetNextSubVolume(pSubVol);
	}

	assert(fpImg != nil);

	return fpImg->FlushImage(mode);
}

/*
 * Set the "read only" flag on our DiskImg and those of our sub-volumes.
 */
void
DiskFS::SetAllReadOnly(bool val)
{
	SubVolume* pSubVol = GetNextSubVolume(nil);

	/* put current volume in read-only mode */
	if (fpImg != nil)
		fpImg->SetReadOnly(val);

	/* handle our kids */
	while (pSubVol != nil) {
		// quick sanity check
		assert(pSubVol->GetDiskFS()->GetDiskImg() == pSubVol->GetDiskImg());

		//pSubVol->GetDiskImg()->SetReadOnly(val);
		pSubVol->GetDiskFS()->SetAllReadOnly(val);	// recurse

		pSubVol = GetNextSubVolume(pSubVol);
	}
}


/*
 * The file list looks something like this:
 *
 * volume-dir
 * file1
 * file2
 * subdir1
 * subdir1:file1
 * subdir1:file2
 * subdir1:subsub1
 * subdir1:subsub1:file1
 * subdir1:subsub2
 * subdir1:subsub2:file1
 * subdir1:subsub2:file2
 * subdir1:file3
 * file3
 *
 * Everything contained within a subdir comes after the subdir entry and
 * before any entries from later subdirs at the same level.
 *
 * It's unclear whether a linear list or a hierarchical tree structure is
 * the most appropriate way to hold the data.  The tree is easier to update,
 * but the linear list corresponds to the primary view in CiderPress, and
 * lists are simpler and easier to manage.  For now I'm sticking with a list.
 *
 * The files MUST be in the order in which they came from the disk.  This
 * doesn't matter most of the time, but for Pascal volumes it's essential
 * for ensuring that the Write command doesn't run over the next file.
 */

/*
 * Add a file to the end of our list.
 */
void
DiskFS::AddFileToList(A2File* pFile)
{
	assert(pFile->GetNext() == nil);

	if (fpA2Head == nil) {
		assert(fpA2Tail == nil);
		fpA2Head = fpA2Tail = pFile;
	} else {
		pFile->SetPrev(fpA2Tail);
		fpA2Tail->SetNext(pFile);
		fpA2Tail = pFile;
	}
}

/*
 * Insert a file into its appropriate place in the list, based on a file
 * hierarchy.
 *
 * Pass in the thing to be added ("pFile") and the previous entry ("pPrev").
 * An empty hierarchic filesystem will have an entry for the volume dir, so
 * we should never have an empty list or a NULL pPrev.
 *
 * The part where things go pear-shaped happens if "pPrev" is a subdirectory.
 * If so, we need to come after all of the subdir's entries, including any
 * entries for sub-subdirs.  There's no graceful way to go about this in a
 * linear list.
 *
 * (We'd love to be able to find the *next* entry and then back up one,
 * but odds are that there isn't a "next" entry if we're busily creating
 * files.)
 */
void
DiskFS::InsertFileInList(A2File* pFile, A2File* pPrev)
{
	assert(pFile->GetNext() == nil);

	if (fpA2Head == nil) {
		assert(pPrev == nil);
		fpA2Head = fpA2Tail = pFile;
		return;
	} else if (pPrev == nil) {
		// create two entries on DOS disk, delete first, add new file
		pFile->SetNext(fpA2Head);
		fpA2Head = pFile;
		return;
	}

	/*
	 * If we're inserting after the parent (i.e. we're the very first thing
	 * in a subdir) or after a plain file, just drop it in.
	 *
	 * If we're inserting after a subdir, go fish.
	 */
	if (pPrev->IsDirectory() && pFile->GetParent() != pPrev) {
		pPrev = SkipSubdir(pPrev);
	}

	pFile->SetNext(pPrev->GetNext());
	pPrev->SetNext(pFile);
}

/*
 * Skip over all entries in the subdir we're pointing to.
 *
 * The return value is the very last entry in the subdir.
 */
A2File*
DiskFS::SkipSubdir(A2File* pSubdir)
{
	if (pSubdir->GetNext() == nil)
		return pSubdir;		// end of list reached -- subdir is empty

	A2File* pCur = pSubdir;
	A2File* pNext = nil;

	assert(pCur != nil);	// at least one time through the loop

	while (pCur != nil) {
		pNext = pCur->GetNext();
		if (pNext == nil)				// end of list reached
			return pCur;

		if (pNext->GetParent() != pSubdir)	// end of dir reached
			return pCur;
		if (pNext->IsDirectory())
			pCur = SkipSubdir(pNext);	// get last entry in dir
		else
			pCur = pNext;				// advance forward one
	}

	/* should never get here */
	assert(false);
	return pNext;
}

/*
 * Delete a member from the list.
 *
 * We're currently singly-linked, making this rather expensive.
 */
void
DiskFS::DeleteFileFromList(A2File* pFile)
{
	if (fpA2Head == pFile) {
		/* delete the head of the list */
		fpA2Head = fpA2Head->GetNext();
		delete pFile;
	} else {
		A2File* pCur = fpA2Head;
		while (pCur != nil) {
			if (pCur->GetNext() == pFile) {
				/* found it */
				A2File* pNextNext = pCur->GetNext()->GetNext();
				delete pCur->GetNext();
				pCur->SetNext(pNextNext);
				break;
			}
			pCur = pCur->GetNext();
		}

		if (pCur == nil) {
			WMSG0("GLITCH: couldn't find element to delete!\n");
			assert(false);
		}
	}
}


/*
 * Access the "next" pointer.
 *
 * Because we apparently can't declare an anonymous class as a friend
 * in MSVC++6.0, this can't be an inline function.
 */
A2File*
DiskFS::GetNextFile(A2File* pFile) const
{
	if (pFile == NULL)
		return fpA2Head;
	else
		return pFile->GetNext();
}

/*
 * Return the #of elements in the linear file list.
 *
 * Right now the only code that calls this is the disk info panel in
 * CiderPress, so we don't need it to be efficient.
 */
long
DiskFS::GetFileCount(void) const
{
	long count = 0;

	A2File* pFile = fpA2Head;
	while (pFile != nil) {
		count++;
		pFile = pFile->GetNext();
	}

	return count;
}

/*
 * Delete all entries in the list.
 */
void
DiskFS::DeleteFileList(void)
{
	A2File* pFile;
	A2File* pNext;

	pFile = fpA2Head;
	while (pFile != nil) {
		pNext = pFile->GetNext();
		delete pFile;
		pFile = pNext;
	}
}

/*
 * Dump file list.
 */
void
DiskFS::DumpFileList(void)
{
	A2File* pFile;

	WMSG0("DiskFS file list contents:\n");

	pFile = GetNextFile(nil);
	while (pFile != nil) {
		WMSG1(" %s\n", pFile->GetPathName());
		pFile = GetNextFile(pFile);
	}
}

/*
 * Run through the list of files and find one that matches (case-insensitive).
 *
 * This does not attempt to open files in sub-volumes.  We could, but it's
 * likely that the application has "decorated" the name in some fashion,
 * e.g. by prepending the sub-volume's volume name to the filename.  May
 * be best to let the application dig for the sub-volume.
 */
A2File*
DiskFS::GetFileByName(const char* fileName, StringCompareFunc func)
{
	A2File* pFile;

	if (func == nil)
		func = ::strcasecmp;

	pFile = GetNextFile(nil);
	while (pFile != nil) {
		if ((*func)(pFile->GetPathName(), fileName) == 0)
			return pFile;

		pFile = GetNextFile(pFile);
	}

	return nil;
}


/*
 * Add a sub-volume to the end of our list.
 *
 * Copies some parameters from "this" into pDiskFS, such as whether to
 * scan for sub-volumes and the various DiskFS parameters.
 *
 * Note this happens AFTER the disk has been scanned.
 */
void
DiskFS::AddSubVolumeToList(DiskImg* pDiskImg, DiskFS* pDiskFS)
{
	SubVolume* pSubVol;

	/*
	 * Check the arguments.
	 */
	if (pDiskImg == nil || pDiskFS == nil) {
		WMSG2(" DiskFS bogus sub volume ptrs %08lx %08lx\n",
			(long) pDiskImg, (long) pDiskFS);
		assert(false);
		return;
	}
	if (pDiskImg == fpImg || pDiskFS == this) {
		WMSG0(" DiskFS attempt to add self to sub-vol list\n");
		assert(false);
		return;
	}
	if (pDiskFS->GetDiskImg() == nil) {
		WMSG0(" DiskFS lacks a DiskImg pointer\n");
		assert(false);
		return;
	}
	pSubVol = fpSubVolumeHead;
	while (pSubVol != nil) {
		if (pSubVol->GetDiskImg() == pDiskImg ||
			pSubVol->GetDiskFS() == pDiskFS)
		{
			WMSG0(" DiskFS multiple adds on diskimg or diskfs\n");
			assert(false);
			return;
		}
		pSubVol = pSubVol->GetNext();
	}

	assert(pDiskFS->GetDiskImg() == pDiskImg);

	/*
	 * Looks good.  Add it.
	 */
	pSubVol = new SubVolume;
	if (pSubVol == nil)
		return;

	pSubVol->Create(pDiskImg, pDiskFS);

	if (fpSubVolumeHead == nil) {
		assert(fpSubVolumeTail == nil);
		fpSubVolumeHead = fpSubVolumeTail = pSubVol;
	} else {
		pSubVol->SetPrev(fpSubVolumeTail);
		fpSubVolumeTail->SetNext(pSubVol);
		fpSubVolumeTail = pSubVol;
	}

	/* make sure inheritable stuff gets copied */
	CopyInheritables(pDiskFS);
}

/*
 * Copy parameters to a sub-volume.
 */
void
DiskFS::CopyInheritables(DiskFS* pNewFS)
{
	for (int i = 0; i < (int) NELEM(fParmTable); i++)
		pNewFS->fParmTable[i] = fParmTable[i];

	pNewFS->fScanForSubVolumes = fScanForSubVolumes;

#if 0
	/* copy scan progress update stuff */
	pNewFS->fpScanProgressCallback = fpScanProgressCallback;
	pNewFS->fpScanProgressCookie = fpScanProgressCookie;
	pNewFS->fpScanCount = -1;
	strcpy(pNewFS->fpScanMsg, "HEY");
#endif
}

/*
 * Access the "next" pointer.
 *
 * Because we apparently can't declare an anonymous class as a friend
 * in MSVC++6.0, this can't be an inline function.
 */
DiskFS::SubVolume*
DiskFS::GetNextSubVolume(const SubVolume* pSubVol) const
{
	if (pSubVol == NULL)
		return fpSubVolumeHead;
	else
		return pSubVol->GetNext();
}

/*
 * Delete all entries in the list.
 */
void
DiskFS::DeleteSubVolumeList(void)
{
	SubVolume* pSubVol;
	SubVolume* pNext;

	pSubVol = fpSubVolumeHead;
	while (pSubVol != nil) {
		pNext = pSubVol->GetNext();
		delete pSubVol;
		pSubVol = pNext;
	}
}


/*
 * Get a parameter.
 */
long
DiskFS::GetParameter(DiskFSParameter parm)
{
	assert(parm > kParmUnknown && parm < kParmMax);
	return fParmTable[parm];
}

/*
 * Set a parameter.
 *
 * The setting propagates to all sub-volumes.
 */
void
DiskFS::SetParameter(DiskFSParameter parm, long val)
{
	assert(parm > kParmUnknown && parm < kParmMax);
	fParmTable[parm] = val;

	SubVolume* pSubVol = GetNextSubVolume(nil);
	while (pSubVol != nil) {
		pSubVol->GetDiskFS()->SetParameter(parm, val);
		pSubVol = GetNextSubVolume(pSubVol);
	}
}


/*
 * Scan for damaged or suspicious files.
 */
void
DiskFS::ScanForDamagedFiles(bool* pDamaged, bool* pSuspicious)
{
	A2File* pFile;

	*pDamaged = *pSuspicious = false;

	pFile = GetNextFile(nil);
	while (pFile != nil) {
		if (pFile->GetQuality() == A2File::kQualityDamaged)
			*pDamaged = true;
		if (pFile->GetQuality() != A2File::kQualityGood)
			*pSuspicious = true;
		pFile = GetNextFile(pFile);
	}
}
