/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include "filesystem/filesystem.h"
		#include "filesystem/io_handle.h"
		#include "log/log.h"


#if	__PLATFORM_POSIX__
#include <unistd.h>
#include <sys/stat.h>
#elif __PLATFORM_WINDOWS__
#include <direct.h>
#endif

#include "filesystem/io_cfile.h"
#include "memory/nauto_ptr.h"


		using GS::String;
		using namespace GS::IO;

//------------------------------------------------------------------------------
bool			Filesystem::Exists(const char *uri) const
{
	AutoPtr <Handle> h(Open(uri));
	return h.IsValid();
}
size_t			Filesystem::FileSize(const char *uri) const
{
	AutoPtr <Handle> h(Open(uri));
	return h.IsNull() ? 0 : h->GetSize();
}
bool			Filesystem::FileLoad(const char *uri, GS::Array <char> &buffer, bool verbose) const
{
	AutoPtr <Handle> h(Open(uri));
	if	(h.IsNull())
	{
		if	(verbose)
			__LOG_W__ << "Failed to open '" << uri << "'.\n";
		return false;
	}

	// Warning: Do not load through IO::Base::FileLoad. That would create another handle!
	size_t size = h->GetSize();
	if	(!buffer.Allocate(size))
		__ERR__(__LOG_W__ << "Failed to allocate memory to load '" << uri << "'.\n", false)

	return asbool(h->Read(buffer.c_ptr(), size) == size);
}
bool			Filesystem::FileSave(const char *uri, const GS::Array <char> &buffer) const
{
	AutoPtr <Handle> h(Open(uri, ModeWrite));
	if	(h.IsNull())
		__ERR__(__LOG_W__ << "Failed to open '" << uri << "'.\n", false)
	return asbool(h->Write(buffer.c_ptr(), buffer.GetSize()) == buffer.GetSize());
}
bool			Filesystem::FileCopy(const char *src, const char *dst) const
{
	Array <char> buffer;
	return FileLoad(src, buffer) && FileSave(dst, buffer);
}
bool			Filesystem::FileMove(const char *src, const char *dst) const
{
	Array <char> buffer;
	return FileLoad(src, buffer) && FileSave(dst, buffer) && Delete(src);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
String			Filesystem::MapToAbsolute(const char *uri) const
{
	String _uri(uri);
	ListForeachPtr(MountPoint *, m, mount_list)
		if	(_uri.StartsWith(m->mount_point))
			return m->io_sys->MapToAbsolute(_uri.Mid(m->mount_point.Len())).CleanFilePath();

	ListForeachPtr(Base *, i, root_mount)
	{
		AutoPtr <Handle> h(i->Open(_uri));
		if	(h.IsValid())
			return i->MapToAbsolute(_uri).CleanFilePath();
	}
	return _uri;
}
String			Filesystem::StripRootPath(const char *path) const
{
	String _path(path);
	_path.FileCleanName();
	ListForeachPtr(Base *, i, root_mount)
	{
		String rpath = i->MapToRelative(_path);
		if	(!rpath.IsEmpty())
			if	(rpath != _path)
				return rpath[0] == '/' ? rpath.Mid(1) : rpath;		// Ensure no leading '/' remains.
	}
	return _path;
}
bool			Filesystem::Mount(Base *io_sys, const char *mount_point)
{
	if	(mount_point)
	{
		ListForeachPtr(MountPoint *, m, mount_list)
			if (m->mount_point == mount_point)
			{
				delete io_sys;
				return false;
			}


		return asbool(mount_list.Prepend(new MountPoint(mount_point, io_sys)));
	}
	else
	{
		ListForeachPtr(Base *, i, root_mount)
			if	(i == io_sys)
				__ERR__(__LOG_E__ << "Cannot mount IO system twice as root.\n", false)

		return asbool(root_mount.Prepend(io_sys));
	}
}
void			Filesystem::Unmount(const char *mount_point)
{	Unmount(GetIOSystem(mount_point));	}
void			Filesystem::Unmount(Base *io_sys)
{
	ListForeachPtr(MountPoint *, m, mount_list)
		if	(m->io_sys.c_ptr() == io_sys)
			mount_list.Remove(m);
	ListForeachPtr(Base *, i, root_mount)
		if	(i == io_sys)
			root_mount.Remove(i);
}
void			Filesystem::UnmountAll()
{
	ListForeachPtr(MountPoint *, m, mount_list)
		mount_list.Remove(m);
	while (List <Base *> ::Item *m = root_mount.GetRoot())
		root_mount.Remove(m);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Base			*Filesystem::GetIOSystem(const char *mount_point) const
{
	ListForeachPtr(MountPoint *, m, mount_list)
		if	(m->mount_point == mount_point)
			return m->io_sys;
	return NULL;
}
const char		*Filesystem::GetMountPoint(const Base *io_sys) const
{
	ListForeachPtr(MountPoint *, m, mount_list)
		if	(m->io_sys == io_sys)
			return m->mount_point;
	return NULL;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Handle			*Filesystem::Open(const char *uri, Mode mode) const
{
	String _uri(uri);

	if	(_uri.IsEmpty())
		return NULL;

	ListForeachPtr(MountPoint *, m, mount_list)
		if	(_uri.StartsWith(m->mount_point))
			return m->io_sys->Open(_uri.Mid(m->mount_point.Len()), mode);
	ListForeachPtr(Base *, i, root_mount)
		if	(Handle *h = i->Open(uri, mode))
			return h;

	return NULL;
}
void			Filesystem::Close(Handle *h) const
{
	ListForeachPtr(MountPoint *, m, mount_list)
		if	(m->io_sys.c_ptr() == h->GetIOSystem())
			m->io_sys->Close(h);
	ListForeachPtr(Base *, i, root_mount)
		if	(i == h->GetIOSystem())
			i->Close(h);
}
bool			Filesystem::Delete(const char *uri) const
{
	AutoPtr <Handle> h(Open(uri));
	if	(h.IsValid())
	{
		Base *io_sys = h->GetIOSystem();
		h = NULL;
		return io_sys->Delete(uri);
	}
	else
		return asbool(unlink(uri) == 0);
	return false;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool			Filesystem::MkDir(const char *path) const
{
	String _path(path);

	if	(_path.IsEmpty())
		return false;

	ListForeachPtr(MountPoint *, m, mount_list)
		if	(_path.StartsWith(m->mount_point))
			return m->io_sys->MkDir(_path.Mid(m->mount_point.Len()));

	// [EJ] No creation on a root filesystem here!
	return false;
}
//------------------------------------------------------------------------------
