//	Zinc Application Framework - Z_PRST.CPP
//	COPYRIGHT (C) 1990-1999.  All Rights Reserved.
//	Zinc Software Incorporated.  Pleasant Grove, Utah  USA

#include <z_data.hpp>
#include <z_string.hpp>
#include <z_stdio.hpp>
#if defined(ZAF_PERSIST_ALL)
#	include <z_datall.hpp>
#endif
#define ZAF_DATA_PERSISTENCE_INFO
#include "gbl_def.cpp"

#if defined(USE_ZAF_PERSISTENCE)
static ZafIChar _objectDataName[] = ZAF_ITEXT("objectData");

// ----- ZafDataPersistence -------------------------------------------------

ZafDataPersistence::ZafDataPersistence(ZafFileSystem *fileSystem,
	DataConstructor *tDataConstructor) :
	error(ZAF_ERROR_NONE), subSystem(), currentEntry(0), currentLanguage(0)
{
	dataConstructor = ZAF_NULLP(DataConstructor);
	SetDataConstructors(tDataConstructor);
	for (int i = 0; i < ZAF_MAXFILES; i++)
	{
		entry[i].level = 0;
		entry[i].type = ZAF_PERSIST_ROOT_DIRECTORY;
		entry[i].className = ZAF_NULLP(ZafClassNameChar);
		entry[i].classID = 0;
		entry[i].file = ZAF_NULLP(ZafFile);
		entry[i].fileSystem = ZAF_NULLP(ZafFileSystem);
		entry[i].restorePath[0] = '\0';

		language[i] = ZAF_NULLP(ZafIChar);
	}

	// Add the file system.
	if (fileSystem)
		subSystem.Add(fileSystem);
}

ZafDataPersistence::ZafDataPersistence(const DataConstructor &copy) :
	error(ZAF_ERROR_NONE), subSystem(), currentEntry(0), currentLanguage(0)
{
	dataConstructor = ZAF_NULLP(DataConstructor);
	AddDataConstructor(copy.className, copy.classID, copy.constructor);

	for (int i = 0; i < ZAF_MAXFILES; i++)
	{
		entry[i].level = 0;
		entry[i].type = ZAF_PERSIST_ROOT_DIRECTORY;
		entry[i].className = ZAF_NULLP(ZafClassNameChar);
		entry[i].classID = 0;
		entry[i].file = ZAF_NULLP(ZafFile);
		entry[i].fileSystem = ZAF_NULLP(ZafFileSystem);
		entry[i].restorePath[0] = '\0';

		language[i] = ZAF_NULLP(ZafIChar);
	}
}

ZafDataPersistence::ZafDataPersistence(const ZafDataPersistence &copy) :
	error(ZAF_ERROR_NONE), subSystem(copy.subSystem), currentEntry(0),
	currentLanguage(0)
{
	dataConstructor = ZAF_NULLP(DataConstructor);
	SetDataConstructors(copy.dataConstructor);

	for (int i = 0; i < ZAF_MAXFILES; i++)
	{
		entry[i].level = 0;
		entry[i].type = ZAF_PERSIST_ROOT_DIRECTORY;
		entry[i].className = ZAF_NULLP(ZafClassNameChar);
		entry[i].classID = 0;
		entry[i].file = ZAF_NULLP(ZafFile);
		entry[i].fileSystem = ZAF_NULLP(ZafFileSystem);
		entry[i].restorePath[0] = '\0';

		language[i] = ZAF_NULLP(ZafIChar);
	}
}

ZafDataPersistence::~ZafDataPersistence(void)
{
	// Clear the tables.
	ClearDataConstructors();
	ClearFileSystems();
	for (int i = 0; i < ZAF_MAXFILES; i++)
		if (language[i])
			delete language[i];
}

ZafDataPersistence::DataConstructor *ZafDataPersistence::AddDataConstructor(ZafClassName className, ZafClassID classID, ZafDataConstructor constructor)
{
	// Check for an existing entry.
	int offset = 0;
	if (dataConstructor)
	{
		// Try to find a matching class.
		for ( ; dataConstructor[offset].classID != ID_END; offset++)
			if (!streq(className, dataConstructor[offset].className))
			{
				// Already in there.  Return the entry.  Do NOT write over!
				return (&dataConstructor[offset]);
			}

		// Reallocate the array.
		if (((offset + 1) % ZAF_DYNAMIC_ARRAY_INCREMENT) == 0)
		{
			DataConstructor *tempArray = dataConstructor;
			dataConstructor = new DataConstructor[offset + ZAF_DYNAMIC_ARRAY_INCREMENT + 1];
			for (int i = 0; i < offset; i++)
			{
				dataConstructor[i].classID = tempArray[i].classID;
				dataConstructor[i].className = tempArray[i].className;
				dataConstructor[i].constructor = tempArray[i].constructor;
			}
			delete []tempArray;
		}
	}
	else
		dataConstructor = new DataConstructor[ZAF_DYNAMIC_ARRAY_INCREMENT];

	// Set the read function.
	dataConstructor[offset].classID = classID;
	dataConstructor[offset].className = strdup(className);
	dataConstructor[offset].constructor = constructor;

	// Set the end-of-array.
	offset++;
	dataConstructor[offset].classID = ID_END;
	dataConstructor[offset].className = ZAF_NULLP(ZafClassNameChar);
	dataConstructor[offset].constructor = ZAF_NULLF(ZafDataConstructor);

	// Return the read function.
	return (&dataConstructor[offset]);
}

ZafFileSystem *ZafDataPersistence::AddFileSystem(ZafFileSystem *fileSystem, ZafFileSystem *position)
{
	// Add the file system.
	if (position)
		subSystem.Add(fileSystem, position);	// position insert.
	else
		subSystem.Add(fileSystem);				// compareFunction insert.

	// Return a pointer to the object.
	return (fileSystem);
}

ZafError ZafDataPersistence::AllocateFile(const ZafIChar *name, ZafFileMode mode)
{
	// Reset the file system.
	if (!subSystem.First())
	{
		entry[currentEntry+1].file = entry[currentEntry].file;
		entry[currentEntry+1].fileSystem = ZAF_NULLP(ZafFileSystem);
		entry[currentEntry].restorePath[0] = '\0';
		SetError(ZAF_ERROR_FILE_OPEN);
		return (ZAF_ERROR_NONE);
	}

	// Check for a valid search name.
	entry[currentEntry].file = ZAF_NULLP(ZafFile);
	entry[currentEntry].fileSystem = ZAF_NULLP(ZafFileSystem);
	if (!name || !name[0])
		name = entry[currentEntry].className;

	// Initialize the complete language identifier for the object.
	ZafIChar languageName[ZAF_MAXPATHLEN];
	if (entry[currentEntry].type == ZAF_PERSIST_ROOT_DIRECTORY ||
		entry[currentEntry].type == ZAF_PERSIST_ROOT_FILE ||
		entry[currentEntry].type == ZAF_PERSIST_DIRECTORY)
	{
		if (language[currentLanguage])
			sprintf(languageName, ZAF_ITEXT("%s.%s"), _objectDataName, language[currentLanguage]);
		else
			strcpy(languageName, _objectDataName);
	}
	else
	{
		if (language[currentLanguage])
			sprintf(languageName, ZAF_ITEXT("%s.%s"), name, language[currentLanguage]);
		else
			strcpy(languageName, name);
	}

	bool repeat = true;
	bool firstPass = true;

	while (!entry[currentEntry].file && repeat)
	{
		if (!firstPass || !(mode & ZAF_FILE_CREATE))
		{
			repeat = false;
			firstPass = false;
		}

		// Try to find a valid path entry from the sub-systems.
		for (ZafElement *element = subSystem.First(); !entry[currentEntry].file && element; element = element->Next())
		{
			ZafFileSystem *fileSystem = DynamicPtrCast(element, ZafFileSystem);

			// Try to open a file.
			if (entry[currentEntry].type == ZAF_PERSIST_ROOT_DIRECTORY ||
				entry[currentEntry].type == ZAF_PERSIST_ROOT_FILE)
			{
				// Move to the root directory.
				fileSystem->GetCWD(entry[currentEntry].restorePath, ZAF_MAXPATHLEN);
				ZafIChar classDir[ZAF_MAXPATHLEN];
				strcpy(classDir, ZafFileSystem::rootDirectoryName);
				strcat(classDir, entry[currentEntry].className);
				if (fileSystem->ChDir(classDir))
				{
					if (mode & ZAF_FILE_READ || firstPass)
						continue; // continue for read-only requests.
					fileSystem->MkDir(classDir);
					fileSystem->ChDir(classDir);
				}

				// Allocate a named directory.
				if (entry[currentEntry].type == ZAF_PERSIST_ROOT_DIRECTORY && fileSystem->ChDir(name))
				{
					fileSystem->MkDir(name, entry[currentEntry].className, entry[currentEntry].classID);
					fileSystem->ChDir(name);
				}
				else if (entry[currentEntry].type == ZAF_PERSIST_ROOT_DIRECTORY &&
					(mode & ZAF_FILE_CREATE))
				{
					fileSystem->ChDir(ZafFileSystem::parentDirectoryName);
					fileSystem->RmDir(name, true);
					fileSystem->MkDir(name, entry[currentEntry].className, entry[currentEntry].classID);
					fileSystem->ChDir(name);
				}

				// Move to the proper sub-directory.
				// printf("push root %s\n", name);
				entry[currentEntry].file = fileSystem->Open(languageName, mode);
				if (!entry[currentEntry].file)
					fileSystem->ChDir(entry[currentEntry].restorePath);
			}
			else if (entry[currentEntry].type == ZAF_PERSIST_DIRECTORY)
			{
				// Copy the restore path.
				strcpy(entry[currentEntry].restorePath, ZafFileSystem::parentDirectoryName);

				// Allocate a named directory.
				if (fileSystem->ChDir(name, entry[currentEntry].className, entry[currentEntry].classID))
				{
					if (mode & ZAF_FILE_READ || firstPass)
						continue; // continue for read-only requests.
					else if (fileSystem->Error() == ZAF_ERROR_INVALID_ID)
						fileSystem->RmDir(name, true);
					fileSystem->MkDir(name, entry[currentEntry].className, entry[currentEntry].classID);
					fileSystem->ChDir(name);
				}
				else if (mode & ZAF_FILE_CREATE)
				{
					fileSystem->ChDir(ZafFileSystem::parentDirectoryName);
					fileSystem->RmDir(name, true);
					fileSystem->MkDir(name, entry[currentEntry].className, entry[currentEntry].classID);
					fileSystem->ChDir(name);
				}

				// Move to the proper sub-directory.
				// printf("push directory %s\n", name);
				entry[currentEntry].file = fileSystem->Open(languageName, mode);
			}
			else
			{
				// Set an empty restore path.
				entry[currentEntry].restorePath[0] = '\0';

				// Move to the proper sub-directory.
				// printf("push file %s\n", name);
				entry[currentEntry].file = fileSystem->Open(languageName, mode, entry[currentEntry].className, entry[currentEntry].classID);
			}

			// Check for a valid file system.
			if (entry[currentEntry].file)
				entry[currentEntry].fileSystem = fileSystem;
		}

		firstPass = false;
	}

	// Keep the error code.
	if (!entry[currentEntry].file)
		SetError(ZAF_ERROR_FILE_OPEN);
	else if (entry[currentEntry].file->Error() != ZAF_ERROR_NONE)
		SetError(entry[currentEntry].file->Error());
	else
		SetError(ZAF_ERROR_NONE);
	return (error);
}

void ZafDataPersistence::ClearDataConstructors(void)
{
	// Check for a valid persist table.
	if (dataConstructor)
	{
		// Remove the data constructor names.
		for (int i = 0; dataConstructor[i].classID != ID_END; i++)
			delete [](ZafIChar *)dataConstructor[i].className;

		// Delete the array.
		delete []dataConstructor;
		dataConstructor = ZAF_NULLP(DataConstructor);
	}
}

void ZafDataPersistence::ClearFileSystems(void)
{
	// Do not delete the file systems, just clear the list.
	while (subSystem.First())
		subSystem.Subtract(subSystem.First());
}

ZafClassID ZafDataPersistence::CurrentClassID(void)
{
	// Check for a valid level.
	if (currentEntry < 0)
		return (ID_END);

	// Find a valid entry.
	ZafClassID matchID = entry[currentEntry].classID;
	if (!matchID && entry[currentEntry].className)
	{
		for (int i = 0; dataConstructor[i].classID != ID_END; i++)
			if (!streq(dataConstructor[i].className, entry[currentEntry].className))
			{
				matchID = dataConstructor[i].classID;
				break;
			}
	}

	// Return the class name.
	return (matchID);
}

ZafClassName ZafDataPersistence::CurrentClassName(void)
{
	// Check for a valid level.
	if (currentEntry < 0)
		return (ZAF_NULLP(ZafDataNameChar));

	// Find a valid entry.
	ZafClassName matchName = entry[currentEntry].className;
	if (!matchName && entry[currentEntry].classID)
	{
		for (int i = 0; dataConstructor[i].classID != ID_END; i++)
			if (dataConstructor[i].classID == entry[currentEntry].classID)
			{
				matchName = dataConstructor[i].className;
				break;
			}
	}

	// Return the class name.
	return (matchName);
}

ZafDataConstructor ZafDataPersistence::GetDataConstructor(ZafClassID classID, ZafClassName className)
{
	// Check for a valid table.
	if (!dataConstructor)
		return (ZAF_NULLF(ZafDataConstructor));

	// Try to find a matching entry.
	for (int i = 0; dataConstructor[i].classID != ID_END; i++)
		if ((classID && classID == dataConstructor[i].classID) ||
			(className == dataConstructor[i].className) ||
			(className && dataConstructor[i].className && !streq(className, dataConstructor[i].className)))
			return (dataConstructor[i].constructor);

	// Read function not found.
	return (ZAF_NULLF(ZafDataConstructor));
}

bool ZafDataPersistence::Merge(ZafDataPersistence &copy)
{
	// Merge the data files.
	for (ZafElement *element = copy.subSystem.First(); element; element = copy.subSystem.First())
	{
		copy.subSystem.Subtract(element);
		AddFileSystem(DynamicPtrCast(element, ZafFileSystem));
	}

	// Merge the data tables.
	for (int i = 0; copy.dataConstructor[i].classID != ID_END; i++)
		AddDataConstructor(copy.dataConstructor[i].className,
			copy.dataConstructor[i].classID,
			copy.dataConstructor[i].constructor);

	// Return success.
	return (true);
}

int ZafDataPersistence::PopLanguage(void)
{
	// Make sure we can pop the language.
	if (currentLanguage <= 0)
		return (currentLanguage);

	// Delete the currentEntry language.
	if (language[currentLanguage])
	{
		delete []language[currentLanguage];
		language[currentLanguage] = ZAF_NULLP(ZafIChar);
	}
	return (--currentLanguage);
}

ZafDataPersistence &ZafDataPersistence::PopLevel(void)
{
	// Remove the file.
	if (--entry[currentEntry].level > 0)
		return (*this);

	// Decrement the level count.
	ZafFileSystem *fileSystem = entry[currentEntry].fileSystem;
	if (fileSystem)
	{
		// Delete the file and keep the error code.
		if (entry[currentEntry].file)
		{
			if (entry[currentEntry].file->Error() != ZAF_ERROR_NONE)
				SetError(entry[currentEntry].file->Error());
			fileSystem->Close(entry[currentEntry].file);
			entry[currentEntry].file = ZAF_NULLP(ZafFile);
		}

		// Restore the directory.
		// printf("pop %s\n", entry[currentEntry].restorePath);
		if (entry[currentEntry].restorePath[0])
			fileSystem->ChDir(entry[currentEntry].restorePath);
	}
	if (currentEntry)
		currentEntry--;
	return (*this);
}

int ZafDataPersistence::PushLanguage(const ZafIChar *newLanguage)
{
	// Make sure we can push the language.
	if (currentLanguage >= ZAF_MAXFILES)
		return (currentLanguage);

	// Push a new language.
	language[++currentLanguage] = newLanguage ? strdup(newLanguage) : ZAF_NULLP(ZafIChar);
	return (currentLanguage);
}

ZafDataPersistence &ZafDataPersistence::PushLevel(ZafClassName className, ZafClassID classID, ZafPersistEntryType type)
{
	// Check for a new entry.
	if (entry[currentEntry].file)
	{
		currentEntry++;
		entry[currentEntry].level = 1;
		entry[currentEntry].file = ZAF_NULLP(ZafFile);
		entry[currentEntry].restorePath[0] = '\0';
	}
	else
		entry[currentEntry].level++;

	// Check for entry update.
	if (entry[currentEntry].level == 1)
	{
		entry[currentEntry].type = type;
		entry[currentEntry].className = className;
		entry[currentEntry].classID = classID;
	}

	return (*this);
}

bool ZafDataPersistence::SetDataConstructors(DataConstructor *tDataConstructor)
{
	// Clear the old data constructors.
	ClearDataConstructors();

	// Add the new table.
	if (tDataConstructor)
	{
		for (int i = 0; tDataConstructor[i].classID != ID_END; i++)
			AddDataConstructor(tDataConstructor[i].className,
				tDataConstructor[i].classID,
				tDataConstructor[i].constructor);
	}

	// Return success.
	return (true);
}

bool ZafDataPersistence::SubtractDataConstructor(ZafClassID classID, ZafClassName className)
{
	// Check for a valid table.
	if (!dataConstructor)
		return (false);

	// Try to find a matching entry.
	for (int i = 0; dataConstructor[i].classID != ID_END; i++)
		if ((classID && classID == dataConstructor[i].classID) ||
			(className && !streq(className, dataConstructor[i].className)))
		{
			// Check for an empty array.
			if (i == 0 && dataConstructor[i+1].classID == 0)
			{
				delete []dataConstructor;
				dataConstructor = ZAF_NULLP(DataConstructor);
			}
			else
			{
				// Remove the data from the notification array.
				for (int j = i; dataConstructor[j].classID; j++)
					dataConstructor[j] = dataConstructor[j+1];
			}

			// Entry found.
			return (true);
		}

	// Entry not found.
	return (false);
}

ZafFileSystem *ZafDataPersistence::SubtractFileSystem(ZafFileSystem *fileSystem)
{
	// Make sure the object can be removed.
	if (subSystem.Index(fileSystem) == -1)
		return (ZAF_NULLP(ZafFileSystem));
	return ((ZafFileSystem *)subSystem.Subtract(fileSystem));
}
#endif
