/** SGX

The   sss    gggg x   x   
     s      g      x x    Engine
      sss   g  gg   x
         s  g   g  x x          Copyright 1995-2009.
     ssss    gggg x   x         Steven Goodwin. All Rights Reserved.
    
This version has been released under the GNU Public License v2.0.

Please see the license files for details.

File: sgx/src/sgx/filesystem/filesys.cpp

*/

#include "sgx/core/core.hpp"
#include "sgx/filesystem/filesystem.hpp"

CSGXFileSystem *CSGXFileSystem::sm_pSingleton = NULL;

CSGXFileSystem::CSGXFileSystem()
{
    m_CurrentPath = "/";

    m_pRoot = new CSGXFileEntry("/");
    m_pRoot->m_Data.m_Name = "/";
}


CSGXFileSystem *CSGXFileSystem::get()
{
    if (sm_pSingleton == NULL) {
        sm_pSingleton = new CSGXFileSystem();
    }

    return sm_pSingleton;
}

CSGXFileSystem::~CSGXFileSystem()
{
	delete m_pRoot;
    delete sm_pSingleton;
}

tCHAR CSGXFileSystem::getNativeDirectoryDelimiter() const {
	return '/';
}

tBOOL
CSGXFileSystem::refreshFile(const sgxString &filename) {
	
	sgxString parent;
	getParent(parent, filename);
    CSGXFileEntry *pParentEntry = getFileEntry(parent);

	if (pParentEntry) {
		return pParentEntry->m_pFSDevice->refreshFile(filename);
	}
	return FALSE;
}

CSGXDevice *CSGXFileSystem::createDevice(const sgxString &path, const sgxString &access) {
	sgxError("You should derive platform-specific versions for createDevice");
	SGX_UNUSED(path);
	SGX_UNUSED(access);
	return NULL;
}


CSGXFileEntry *CSGXFileSystem::getChildEntry(CSGXFileEntry *pCurr, const sgxString &filename) const
{
    sgxAssert(pCurr);
    if (!pCurr) {
        return NULL;
    }

    CSGXFileEntry *ptr = pCurr->m_pFirstChild;

    while(ptr) {
        if (sgxStricmp(ptr->m_Data.m_Name, filename)==0) {
            return ptr;
        }

        ptr = ptr->m_pNextSibling;
    }

    return NULL;
}

CSGXFileEntry *CSGXFileSystem::addNewFile(const CSGXFileSysData &FileData)
{
    sgxString basename;
    sgxString parent;

    sgxFilename::getBaseName(basename, FileData.m_Name);
    getParent(parent, FileData.m_Name);

    CSGXFileEntry *pNewEntry = createFileEntry(parent, basename);

    if (!pNewEntry) {
        return NULL;
    }
    // Return error at lowest level, report at the highest
    // true in call cases; but xp code may have more error levels of indirection at
    // the bottom end
    CSGXFileEntry *pParentEntry = getFileEntry(parent);

    pParentEntry->m_pFSDevice->addNewFile(pNewEntry);//, FileData.m_Name);//, pParentState);
    pNewEntry->m_pParent = pParentEntry;
    pNewEntry->m_Data = FileData;
    pNewEntry->m_Data.m_Name = basename;

    return pNewEntry;
}


CSGXFileEntry *CSGXFileSystem::createFileEntry(CSGXFileEntry *pParent, const sgxString &name)
{
    CSGXFileEntry *pThis = new CSGXFileEntry(name);

    // Append latest entry to start of list
    pThis->m_pNextSibling = pParent->m_pFirstChild;
    pParent->m_pFirstChild = pThis;
    pThis->m_pParent = pParent;

    return pThis;
}

CSGXFileEntry *CSGXFileSystem::createFileEntry(const sgxString &parent, const sgxString &name)
{
    CSGXFileEntry *pParent = getFileEntry(parent);

    if (!pParent) {
        // Can't create file, since parent doesn't exist
        return NULL;
    }
	return createFileEntry(pParent, name);
}

CSGXFileEntry *CSGXFileSystem::getFileEntry(const sgxString &path) const
{
    // Returns the pFileEntry for the file/directory specified.
    // or NULL if the file doesn't exist.
    sgxString fullpath;

    // Easy early out case
    if (path == "/") {
        return m_pRoot;
    }

    getFullPath(fullpath, path);

    CSGXFileEntry *pCurr = m_pRoot;
    CSGXFileEntry *pNextEntry;

	tBOOL bRunning = TRUE;
    do {
        // Retrieve next name in path
        // current path always begins with a /
        sgxString name;
        sgxString::size_type pos;

        pos = fullpath.find_first_of('/', 1);
        if (pos == sgxString::npos) {
            name = fullpath.substr(1);
        } else {
            name = fullpath.substr(1, pos-1);
        }

		// Replace any environment variables. e.g. $LANG with their equivalents
		if (sgxStrchr(name, '$')) {
			sgxString replacement;
			sgxString varName;
			const char *p = name.c_str();
			tBOOL parsingEnv = FALSE;
			do {
				if (parsingEnv) {
					if (sgxIsAlNum(*p)) {
						varName += *p;
					} else {	// variable name over. find a replacement
						sgxString val;
						CConfiguration::get()->getOption(val, varName);
						replacement += val;
						parsingEnv = FALSE;
					}					
				} 
				if (*p == '$') {
					parsingEnv = TRUE;
				} else if (!parsingEnv) {
					replacement += *p;
				}
			} while(*++p);
			//
			name = replacement;
		}

        // Look for this name in the current layer
        // of the hierarchy
        pNextEntry = getChildEntry(pCurr, name);
        if (!pNextEntry) {
            return NULL;
        }

        // The entry exists, and (if there's no more path to
        // search) we've found our man!
        if (pos == sgxString::npos) {
            return pNextEntry;
        }

        // We have an entry but it may be a file, or 
		// a directory, but we can only recurse down
		// into directories. So check for that...
        if (pNextEntry->isFile()) {
            return NULL;
        }

        // Change directory...
        pCurr = pNextEntry;
    
        // ... move onto the next part of the path (keep the / for code above)
        fullpath = fullpath.substr(pos); 

	// this should never get set to FALSE, since we return explicitly 
	// from the loop. 
	// The variable stops warnings.
    } while(bRunning);	

return NULL;
}

sgxString
CSGXFileSystem::getNativePath(const CSGXFile &file) const
{
	sgxString str;
	if (file.m_pFileEntry) {
		file.m_pFileEntry->getFileNamePath(str);
		return getNativePath(str);
	} 
	return "";
}

sgxString
CSGXFileSystem::getNativePath(const sgxString &filepath) const
{
sgxString filehead;
sgxString filetail;
CSGXFileEntry *ptr = NULL;
sgxString result;

	sgxFilename::splitPathAndFilename(filehead, filetail, filepath);

	while(!ptr) {
		ptr = getFileEntry(filehead);
		if (ptr) {
			ptr->m_pFSDevice->getRoot(result);
			result += "/";
			result += filetail;	//ptr->m_Data.m_Name;
		} else if (filehead == "") {	// ptr is NULL, and we've run out of parents
			return filepath;
		} else {	// try the parent folder
			sgxString head;
			sgxString tail;

			sgxFilename::splitPathAndFilename(head, tail, filehead);
			filehead = head;
			filetail = tail + "/" + filetail;

		}
	}

	// Some OS's have a backslash for their native delinearator. So,
	// we use it when getting a _native_ path. It's not usually necessary
	// since Windows has finally caught up in allowing /, but it's not
	// guaranteed.
	tCHAR slash = getNativeDirectoryDelimiter();
	if (slash != '/') {
		result = sgxStrreplace(result, '/', slash);
	}

	return result;
}

tBOOL CSGXFileSystem::isFile(const sgxString &path) const
{
    CSGXFileEntry *ptr = getFileEntry(path);
    if (ptr && ptr->isFile()) {
        return TRUE;
    } else {
        return FALSE;
    }
}

tBOOL CSGXFileSystem::isFolder(const sgxString &path) const
{
    CSGXFileEntry *ptr = getFileEntry(path);
    if (ptr && ptr->isFolder()) {
        return TRUE;
    } else {
        return FALSE;
    }
}

tBOOL CSGXFileSystem::exists(const sgxString &path) const
{
    return getFileEntry(path) ? TRUE : FALSE;
}


void CSGXFileSystem::recurse(CSGXFileEntry *pCurrRoot, CSGXCallback *pCallback)
{
    CSGXFileEntry *ptr = pCurrRoot;

    // List files first
    while(ptr) {
        if (ptr->isFile()) {
			pCallback->call((tMEM_SIZE)ptr);
        }
        ptr = ptr->m_pNextSibling;
    }

    // Then directories
    ptr = pCurrRoot;
    while(ptr) {
        if (!ptr->isFile()) {
			pCallback->call((tMEM_SIZE)ptr);
        }
        if (ptr->m_pFirstChild) {
            recurse(ptr->m_pFirstChild, pCallback);
        }
        ptr = ptr->m_pNextSibling;
    }

}

void CSGXFileSystem::showHeirarchy(CSGXFileEntry *pCurr, tMEM_SIZE iDepth)
{
    sgxString str;
    sgxString file_stub="|", dir_stub="+";

    for(tMEM_SIZE i=0; i<iDepth; i++) {
        file_stub += "---";
        dir_stub  += "   ";
    }

    CSGXFileEntry *ptr = pCurr;

    // List files first
    while(ptr) {
        if (ptr->isFile()) {
            str = file_stub + "> " + ptr->m_Data.m_Name;
            sgxTrace(str.c_str());
        }
        ptr = ptr->m_pNextSibling;
    }

    // Then directories
    ptr = pCurr;
    while(ptr) {
        if (!ptr->isFile()) {
            str = dir_stub + "+ " + ptr->m_Data.m_Name;
            sgxTrace(str.c_str());
        }
        if (ptr->m_pFirstChild) {
            showHeirarchy(ptr->m_pFirstChild, iDepth+1);
        }
        ptr = ptr->m_pNextSibling;
    }
}

void CSGXFileSystem::showHeirarchy()
{
    showHeirarchy(m_pRoot, 0);
}


tBOOL CSGXFileSystem::getParent(sgxString &parent, const sgxString &location) const
{
    sgxString fullpath;
    
    getFullPath(fullpath, location);

	// Because we create a full path there must always be
    // at least one / present (even if it's the root)
    tUINT32 pos = fullpath.find_last_of('/');

    if (pos == 0) { // parent is the root
        parent = "/"; 
    } else {
        parent = fullpath.substr(0, pos);
    }

    return TRUE;
}

void CSGXFileSystem::getFullPath(sgxString &fullpath, const sgxString &path) const
{
    // Build a full path. This guarantees we always
    // start searching from the root.
    if (path[0] == '/') {
        fullpath = path;
    } else {
        fullpath = m_CurrentPath + path;
    }
    // We could also add further validation here
}

CSGXFileEntry *CSGXFileSystem::makeNewFileEntry(const sgxString &path)
{
    sgxString fullpath;

    getFullPath(fullpath, path);

    if (getFileEntry(fullpath)) {
        return NULL;
    }

    // create dir with base name
    sgxString basename;
    sgxString parent;

    sgxFilename::getBaseName(basename, path);
    getParent(parent, path);

    return createFileEntry(parent, basename);
}

CSGXFileEntry *CSGXFileSystem::createNewDirectory(const sgxString &PathName, const tBOOL bCreateParents)
{
	CSGXFileEntry *pNewDir = NULL;
	sgxString parent;
	if (getParent(parent, PathName)) {
		CSGXFileEntry *pParent = getFileEntry(parent);

		if (pParent == NULL) {
			if (bCreateParents) {
				pParent = createNewDirectory(parent, TRUE);
			} else {
				return NULL;
			}
		}
		sgxAssert(pParent->m_pFSDevice && "No device - you're probably trying to create a directory in the root. Mount here, instead");

		sgxString basename;
		sgxFilename::getBaseName(basename, PathName);
		pNewDir = pParent->m_pFSDevice->createNewDirectory(pParent, basename);
	}
	return pNewDir;
}

CSGXFileEntry *CSGXFileSystem::createNewFile(const sgxString &fileName)
{
	CSGXFileEntry *pNewFile = NULL;
	sgxString parent;
	if (getParent(parent, fileName)) {
		CSGXFileEntry *pParent = getFileEntry(parent);
		if (pParent) {
			sgxString basename;
			sgxFilename::getBaseName(basename, fileName);
			pNewFile = pParent->m_pFSDevice->createNewFile(pParent, basename);
		}
	}
	return pNewFile;
}

tBOOL
CSGXFileSystem::mountDirectory(const sgxString &location, tBOOL recursive) {
	return mountDirectory(createDevice(location), "/"+location, recursive);
}

tBOOL CSGXFileSystem::mountDirectory(CSGXDevice *pDevice, const sgxString &location, tBOOL recursive)
{
    CSGXFileEntry *pMount = makeNewFileEntry(location);

    if (!pMount) {
        sgxTrace("Couldn't create mount point at '%s'", location.c_str());
        return FALSE;
    }

    // attach to device
    sgxString fullpath;
    getFullPath(fullpath, location);

    pMount->m_pFSDevice = pDevice;

    return pDevice->mountDirectory(pMount, fullpath, recursive);
}

tBOOL CSGXFileSystem::umountDirectory(const sgxString &location) {
    CSGXFileEntry *pUnmount = getFileEntry(location);

    if (!pUnmount) {
        sgxTrace("No mount point at '%s'", location.c_str());
        return FALSE;
    }

	pUnmount->m_pParent->removeChild(pUnmount);
	pUnmount->releaseHierarchy();
	delete pUnmount;

	return TRUE;
}

tDISC_SIZE  CSGXFileSystem::getFileSize(const sgxString &path) const
{
CSGXFileEntry *ptr = getFileEntry(path);

    if (ptr) {
        return ptr->getFileSize();
    } else {
        return (tDISC_SIZE)-1;
    }
}

tBOOL CSGXFileSystem::copyFile(const sgxString &destFile, const sgxString &srcFile) {
	CSGXFile outfile(destFile, "rw");

	return copyFile(outfile, srcFile);
}

tBOOL CSGXFileSystem::copyFile(CSGXFile &outfile, const sgxString &srcFile) {
	tBOOL rt = FALSE;

	CSGXFile infile(srcFile, "r");

	if (infile.open() && outfile.open()) {
		const tUINT32 bufferSize = 1024;
		static tBYTE buffer[bufferSize];
		tUINT32 bytesRead;
		
		do {
			bytesRead = infile.read(buffer, bufferSize);
			outfile.write(buffer, bytesRead);
		} while (bytesRead == bufferSize);

		rt = TRUE;
	}

	infile.close();
	outfile.close();

	return rt;
}

//
//
//
tBOOL CSGXDeviceBlockState::readLine(sgxString &line) {
tBYTE lastChar;

	line = "";
	
	while(read(&lastChar, 1) == 1) {
		// CR found!
		if (lastChar == '\r') {
			// just check for LF
			if (read(&lastChar, 1)) {
				if (lastChar != '\n') {
					seek(-1, eSeekFromCurrent);	// if it was a legit character, we put it back
				}
			}
			break;
		}
		// normal character
		line += lastChar;
	}

	return isEOF();
}


		


CSGXFileEntry *
CSGXFileSystem::getSearchPathFileEntry(const sgxString &filename) {

	for(sgxVector<sgxString>::const_iterator cit=m_SearchPathList.begin();cit!=m_SearchPathList.end();++cit) {
		sgxString newFullPath = (*cit);
		newFullPath += filename;
		CSGXFileEntry *p = getFileEntry(newFullPath);
		if (p) {
			return p;
		}
	}

	return NULL;
}

tUINT32
CSGXFileSystem::getSearchPath(sgxVector<sgxString> &searchList) const {
tUINT32 added = 0;

	for(sgxVector<sgxString>::const_iterator cit=m_SearchPathList.begin();cit!=m_SearchPathList.end();++cit,++added) {
		searchList.push_back(*cit);
	}

	return added;
}

void
CSGXFileSystem::setSearchPath(const sgxVector<sgxString> &searchList) {
	for(sgxVector<sgxString>::const_iterator cit=searchList.begin();cit!=searchList.end();++cit) {
		addSearchPath(*cit);
	}
}

void
CSGXFileSystem::addSearchPath(const sgxString &nextPath) {
	m_SearchPathList.push_back(nextPath);
}

void
CSGXFileSystem::clearSearchPath() {
	m_SearchPathList.clear();
}


