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

#include <z_env.hpp>
#if defined(ZAF_MSWINDOWS) || defined(ZAF_MSDOS)
#	if !defined(__IBMCPP__)
#		include <direct.h>
#		include <dos.h>
#		include <errno.h>
#	endif
#	if !defined(ZAF_WIN32)
#		if !defined(__WATCOMC__)
#			include <dirent.h>
#		endif
#		include <sys/stat.h>
#	endif
#elif defined(ZAF_VXWORKS)
#	include <dirent.h>
#	include <ioLib.h>
#	include <iosLib.h>
#elif defined(ZAF_PSOS)
#	include <phile.h>
extern int GetDriveNames(ZafIChar **getDriveName[]);
#elif defined(ZAF_MACINTOSH)
#	include "a_utils.hpp"
#	define ZAF_MACINTOSH_READ_CACHE
#else
#	include <dirent.h>
#	include <sys/stat.h>
#endif

#include <z_cset.hpp>
#include <z_ctype.hpp>
#include <z_dskfil.hpp>
#include <z_stdlib.hpp>
#include <z_string.hpp>
#include <z_unistd.hpp>
#include <z_utils.hpp>

#define ZAF_TEMP_NAME_PREFIX		ZAF_ITEXT("ZTMP")
#define ZAF_TEMP_NAME_EXTENSION		ZAF_ITEXT(".$$$")


// ----- Drive Error Handling -----------------------------------------------

#if defined(ZAF_MSDOS)
#	define DRIVE_ABORT 		2

#	if defined(__BCPLUSPLUS__) | defined(__TCPLUSPLUS__)
static int CriticalErrorHandler(int, int, int, int)
{
	errno = ENODEV;
#	if !defined(DOS16PP) && !defined(DOS32PP)
	_hardresume(_HARDERR_FAIL);
#	endif
	return 2; // ABORT
}

#	elif defined(__ZTC__) && !defined(__SC__)
int _cdecl DOS::CriticalErrorHandler(int *ax, int *di)
{
	errno = ENODEV;
	*ax = _HARDERR_FAIL;
	return 2; // ABORT
}

#	elif defined(_MSC_VER)
#		if defined(DOS386)
void CriticalErrorHandler(unsigned, unsigned, unsigned *)
#		else
void CriticalErrorHandler(unsigned, unsigned, unsigned far *)
#		endif
{
#		if !defined(DOS386)
	_hardresume(_HARDERR_FAIL);
#		endif
}

#	elif defined(__WATCOMC__)
int CriticalErrorHandler(unsigned deverr, unsigned errcode, unsigned far *)
{
	_hardresume(_HARDERR_FAIL);
	return 2; // ABORT
}
#	endif
#endif

// ----- ZafDiskFileSystem --------------------------------------------------

void ZafDiskFileSystem::ChangeExtension(ZafIChar *pathName,
	const ZafIChar *newExtension)
{
	int i = (int)(strlen(pathName) - 1);
	while (i >= 0 && pathName[i] != '.' && pathName[i] != ZafOSDirSepStr()[0])
		--i;

	if (i >= 0 && pathName[i] == '.')
		pathName[i] = '\0';
	if (newExtension)
	{
		if (newExtension[0] != '.' && pathName[strlen(pathName)-1] != '.')
			strcat(pathName, ZAF_ITEXT("."));
		strcat(pathName, newExtension);
	}
}

void ZafDiskFileSystem::MakeFullPath(ZafIChar *fullPath,
	const ZafIChar *pathName, const ZafIChar *fileName,
	const ZafIChar *extension)
{
	// Check for quicker method of passing in the same pointer for fullPath and pathName.
	if (pathName != fullPath)
		strcpy(fullPath, pathName);
	int i = (int)strlen(fullPath);
#if defined(ZAF_INDEPENDENT) || defined(ZAF_MSWINDOWS) || defined(ZAF_OS2)
	if (i && fullPath[i-1] != ZafOSDirSepStr()[0] && fullPath[i-1] != ':')
#else
	if (i && fullPath[i-1] != ZafOSDirSepStr()[0])
#endif
		strcat(fullPath, ZafOSDirSepStr());

	if (fileName)
		strcat(fullPath, fileName);
	if (extension)
		ChangeExtension(fullPath, extension);
}

void ZafDiskFileSystem::StripFullPath(const ZafIChar *fullPath,
	ZafIChar *pathName, ZafIChar *fileName, ZafIChar *objectName,
	ZafIChar *objectPathName)
{
	// Determine the path/file split area.
	const ZafIChar *endPath = strrchr(fullPath, ZafOSDirSepStr()[0]);
#if defined(ZAF_INDEPENDENT) || defined(ZAF_MSWINDOWS) || defined(ZAF_OS2)
	if (!endPath)
		endPath = strrchr(fullPath, ':');
#endif
	if (!endPath)
		endPath = fullPath;
	if (endPath == fullPath && *endPath == ZafOSDirSepStr()[0])
		endPath++;
#if defined(ZAF_INDEPENDENT) || defined(ZAF_MSWINDOWS) || defined(ZAF_OS2)
	if (*endPath == ':')
		endPath++;
	if (*endPath == ZafOSDirSepStr()[0] && endPath-fullPath == 2 && endPath[-1] == ':')
		endPath++;
#elif defined(ZAF_MOTIF)
	if (endPath == fullPath && *endPath == '~')
		endPath++;
#endif

	// Determine the file/object split area.
	const ZafIChar *endFile = strchr(endPath, ZAF_DIRECTORY_SEPARATOR);
	if (!endFile || endFile == fullPath)
		endFile = &endPath[strlen(endPath)];

	// find the end of the object path name
	const ZafIChar *endOPath = strrchr(endFile, ZAF_DIRECTORY_SEPARATOR);
	if (!endOPath || endOPath == fullPath)
		endOPath = &endFile[strlen(endFile)];

	// Construct the path name.
	if (pathName)
	{
		size_t i = (size_t)(endPath - fullPath);
		strncpy(pathName, fullPath, i);
		pathName[i] = '\0';
	}
	if (*endPath == ZafOSDirSepStr()[0])
		endPath++;

	// Construct the file name.
	if (fileName)
	{
		size_t i = (size_t)(endFile - endPath);
		strncpy(fileName, endPath, i);
		fileName[i] = '\0';
	}

	// Construct the object path name.
	if (objectPathName)
	{
		size_t i = (size_t)(endOPath - endFile);
		strncpy(objectPathName, endFile, i);
		objectPathName[i] = '\0';
	}
	if (*endOPath == ZafOSDirSepStr()[0])
		endOPath++;

	// Construct the object name.
	if (objectName)
		strcpy(objectName, endOPath);
}

void ZafDiskFileSystem::TempName(ZafIChar *tempName)
{
#if defined(__IBMCPP__)
	tmpnam(tempName);
#else
	ZafIChar pathName[ZAF_MAXPATHLEN];
	strcpy(pathName, tempName);

	int index = 0;
	do
	{
		ZafIChar fileName[16];
		sprintf(fileName, ZAF_ITEXT("%s%d%s"), ZAF_TEMP_NAME_PREFIX, index++,
			ZAF_TEMP_NAME_EXTENSION);
		MakeFullPath(tempName, pathName, fileName);
	} while (ValidName(tempName, false));
#endif
}

ZafDiskFileSystem::ZafDiskFileSystem(void) :
	ZafFileSystem()
{
#if defined(ZAF_MSDOS)
	// hook up critical error handling.
#	if defined(__BCPLUSPLUS__) | defined(__TCPLUSPLUS__)
#		if !defined(DOS16PP) && !defined(DOS32PP)
   	harderr(CriticalErrorHandler);
#		endif

#	elif defined(__ZTC__) && !defined(__SC__)
	_cerror_handler = CriticalErrorHandler;
	cerror_open();

#	elif defined(_MSC_VER)
#		if !defined(DOS386)
	_harderr(CriticalErrorHandler);
#		endif

#	elif defined(__WATCOMC__)
	_harderr(CriticalErrorHandler);
#	endif
#endif
}

ZafDiskFileSystem::~ZafDiskFileSystem(void)
{
}

int ZafDiskFileSystem::FindFirst(ZafFileInfoStruct &fileInfo,
	const ZafIChar *searchName, ZafStringID searchStringID,
	ZafNumberID searchNumberID)
{
	// Initialize the info information.
	fileInfo.name = ZAF_NULLP(ZafIChar);
	fileInfo.stringID = ZAF_NULLP(ZafIChar);
	fileInfo.numberID = 0;
	fileInfo.length = -1;
	fileInfo.attributes = ZAF_FATTRIB_NONE;
	fileInfo.internalHandle = ZAF_NULLP(void);

	// Find the file.
	return (FindNext(fileInfo, searchName, searchStringID, searchNumberID));
}

int ZafDiskFileSystem::GetCWD(ZafIChar *pathName, int pathLength)
{
	// Get the current working directory.
#if defined(ZAF_MACINTOSH)
	// Macintosh does not have the concept of a current directory.
#	pragma unused(pathName)
#	pragma unused(pathLength)
	return (-1);
#else
	return (getcwd(pathName, pathLength) ? 0 : -1);
#endif
}

void ZafDiskFileSystem::Close(ZafFile *file)
{
	ZafDiskFile *diskFile = (ZafDiskFile *)file;
	delete diskFile;
}

void ZafDiskFileSystem::DeleteDriveNames(ZafIChar *driveName[])
{
	if (driveName)
	{
		for (int index = 0; driveName[index]; index++)
			delete []driveName[index];
		delete []driveName;
	}
}

ZafFile *ZafDiskFileSystem::Open(const ZafIChar *fileName,
	const ZafFileMode mode, ZafStringID, ZafNumberID)
{
	ZafDiskFile *file = new ZafDiskFile(fileName, mode);
	return file;
}

int ZafDiskFileSystem::Remove(const ZafIChar *name)
{
	char osName[ZAF_MAXPATHLEN];
	ZafConvertToOSString(name, osName, false);

#if defined(ZAF_MACINTOSH)
	return (HDelete(0, 0L, c2pstr(osName)) == noErr ? 0 : -1);
#else
	return (remove(osName));
#endif
}

int ZafDiskFileSystem::Rename(const ZafIChar *oldName, const ZafIChar *newName,
	ZafStringID, ZafNumberID)
{
	char osOldName[ZAF_MAXPATHLEN], osNewName[ZAF_MAXPATHLEN];
	ZafConvertToOSString(oldName, osOldName, false);
	ZafConvertToOSString(newName, osNewName, false);

#if defined(ZAF_MACINTOSH)
	return (HRename(0, 0L, c2pstr(osOldName), c2pstr(osNewName)) == noErr ? 0 : -1);
#else
	return (::rename(osOldName, osNewName));
#endif
}

// ----- ZafDiskFile --------------------------------------------------------

ZafDiskFile::ZafDiskFile(const ZafIChar *fileName, ZafFileMode newMode) :
	ZafFile(newMode),
	file(0)
{
	// Set the pathname.
	if (fileName)
#if defined(ZAF_MOTIF)
	{
		ZafIChar *tilde = strchr(fileName, '~');
		if (tilde)
		{
			int tildeIndex = tilde - fileName;
			ZafIChar *home = getenv(ZAF_ITEXT("HOME"));
			pathName = new ZafIChar[strlen(fileName) + strlen(home)];
			strncpy(pathName, fileName, tildeIndex);
			pathName[tildeIndex] = 0;

			ZafIChar *appendPath = tilde + 1;
			if (*appendPath == ZafOSDirSepStr()[0])
				appendPath++;
			ZafDiskFileSystem::MakeFullPath(pathName, pathName, home);
			ZafDiskFileSystem::MakeFullPath(pathName, pathName, appendPath);
		}
		else
			pathName = strdup(fileName);
	}
#else
		pathName = strdup(fileName);
#endif
	else
		pathName = ZAF_NULLP(ZafIChar);

	ZafIChar tStringID[ZAF_MAXNAMELEN];
	ZafDiskFileSystem::StripFullPath(pathName, ZAF_NULLP(ZafIChar), tStringID);
	SetStringID(tStringID);

	// Check the mode.
	if (DerivedAccess())
		return;
	else if (!fileName)
	{
		error = ZAF_ERROR_INVALID_NAME;
		return;
	}

#if defined(ZAF_MACINTOSH)
	readCacheBuffer = NULL;
	readCacheLength = 0;
	readCachePosition = 0;
	file = MacOpen(pathName, fileCreator, fileType, mode);
#elif defined(ZAF_PSOS)
	// Call create_f first to make sure the file exists (create_f fails if
	// the file already exists).
	create_f(pathName, 256, 0);
	open_f(&file, pathName, 0);
#else
	// Determine the mode.
	ZafIChar openMode[6];
	if (OpenCreate())
		strcpy(openMode, ZAF_ITEXT("a+"));
	else if (Create())
		strcpy(openMode, ZAF_ITEXT("w+"));
	else if (ReadWrite())
		strcpy(openMode, ZAF_ITEXT("r+"));
	else
		strcpy(openMode, ZAF_ITEXT("r"));

	if (BinaryMode())
		strcat(openMode, ZAF_ITEXT("b"));
	else if (TextMode())
		strcat(openMode, ZAF_ITEXT("t"));

	// Try first for a direct open.
	file = fopen(pathName, openMode);
#endif

	// Next, try a path open.
#if defined(USE_ZAF_SEARCH_PATH)
	if (!file && zafSearchPath && !strchr(pathName, ':') && !strchr(pathName,
		ZafOSDirSepStr()[0]) && !Create())
	{
		ZafIChar fullPath[ZAF_MAXPATHLEN];
		for (ZafPathElement *element = zafSearchPath->First(); !file && element; element = element->Next())
		{
			ZafDiskFileSystem::MakeFullPath(fullPath, element->Path(), fileName);
#			if defined(ZAF_MACINTOSH)
			file = MacOpen(fullPath, fileCreator, fileType, mode);
#			elif defined(ZAF_PSOS)
			open_f(&file, fullPath, 0);
#			else
			file = fopen(fullPath, openMode);
#			endif
			if (file)
			{
				delete []pathName;
				pathName = strdup(fullPath);
			}
		}
	}
#endif

	// Temporary mode setting functions.
#	if defined(ZAF_MOTIF)
	if (file && (Create() || OpenCreate()))
	{
#		if defined(ZAF_UNICODE)
		char *osPath = ZafConvertToOSString(pathName);
		chmod(osPath, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
		delete []osPath;
#		else
		chmod(pathName, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
#		endif
	}
#	endif

#if defined(ZAF_MACINTOSH) && defined(ZAF_MACINTOSH_READ_CACHE)
	if (file && ReadOnly())
	{
		long readCacheCount = 0;
		GetEOF(file, &readCacheCount);
		if (readCacheCount > 0)
			readCacheBuffer = new char[readCacheCount];
		if (readCacheBuffer)
		{
			FSRead(file, &readCacheCount, readCacheBuffer);
			readCacheLength = readCacheCount;
		}
	}
#endif

	// Set the error based on the file status.
	if (!file)
		error = ZAF_ERROR_FILE_OPEN;
}

ZafDiskFile::~ZafDiskFile(void)
{
	// Delete the file.
	if (!DerivedAccess() && file)
#if defined(ZAF_MACINTOSH)
		FSClose(file);
#elif defined(ZAF_PSOS)
		close_f(file);
#else
		fclose(file);
#endif

#if defined(ZAF_MACINTOSH)
	delete []readCacheBuffer;
#endif

	// Delete the pathname.
	if (pathName)
		delete []pathName;
}


