#include <efsw/FileInfo.hpp>
#include <efsw/FileSystem.hpp>
#include <efsw/String.hpp>

#ifndef _DARWIN_FEATURE_64_BIT_INODE
#define _DARWIN_FEATURE_64_BIT_INODE
#endif

#ifndef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 64
#endif

#include <sys/stat.h>

#include <limits.h>
#include <stdlib.h>

#ifdef EFSW_COMPILER_MSVC
	#ifndef S_ISDIR
	#define S_ISDIR(f) ((f)&_S_IFDIR)
	#endif

	#ifndef S_ISREG
	#define S_ISREG(f) ((f)&_S_IFREG)
	#endif

	#ifndef S_ISRDBL
	#define S_ISRDBL(f) ((f)&_S_IREAD)
	#endif
#else
	#include <unistd.h>

	#ifndef S_ISRDBL
	#define S_ISRDBL(f) ((f)&S_IRUSR)
	#endif
#endif

namespace efsw {

bool FileInfo::exists( const std::string& filePath )
{
	FileInfo fi( filePath );
	return fi.exists();
}

bool FileInfo::isLink( const std::string& filePath )
{
	FileInfo fi( filePath, true );
	return fi.isLink();
}

bool FileInfo::inodeSupported()
{
	#if EFSW_PLATFORM != EFSW_PLATFORM_WIN32
	return true;
	#else
	return false;
	#endif
}

FileInfo::FileInfo() :
	ModificationTime(0),
	OwnerId(0),
	GroupId(0),
	Permissions(0),
	Inode(0)
{}

FileInfo::FileInfo( const std::string& filepath ) :
	Filepath( filepath ),
	ModificationTime(0),
	OwnerId(0),
	GroupId(0),
	Permissions(0),
	Inode(0)
{
	getInfo();
}

FileInfo::FileInfo( const std::string& filepath, bool linkInfo ) :
	Filepath( filepath ),
	ModificationTime(0),
	OwnerId(0),
	GroupId(0),
	Permissions(0),
	Inode(0)
{
	if ( linkInfo )
	{
		getRealInfo();
	}
	else
	{
		getInfo();
	}
}

void FileInfo::getInfo()
{
	#if EFSW_PLATFORM == EFSW_PLATFORM_WIN32
	if ( Filepath.size() == 3 && Filepath[1] == ':' && Filepath[2] == FileSystem::getOSSlash() )
	{
		Filepath += FileSystem::getOSSlash();
	}
	#endif

	/// Why i'm doing this? stat in mingw32 doesn't work for directories if the dir path ends with a path slash
	bool slashAtEnd = FileSystem::slashAtEnd( Filepath );

	if ( slashAtEnd )
	{
		FileSystem::dirRemoveSlashAtEnd( Filepath );
	}

	#if EFSW_PLATFORM != EFSW_PLATFORM_WIN32
	struct stat st;
	int res = stat( Filepath.c_str(), &st );
	#else
	struct _stat st;
	int res = _wstat( String::fromUtf8( Filepath ).toWideString().c_str(), &st );
	#endif

	if ( 0 == res )
	{
		ModificationTime	= st.st_mtime;
		Size				= st.st_size;
		OwnerId				= st.st_uid;
		GroupId				= st.st_gid;
		Permissions			= st.st_mode;
		Inode				= st.st_ino;
	}

	if ( slashAtEnd )
	{
		FileSystem::dirAddSlashAtEnd( Filepath );
	}
}

void FileInfo::getRealInfo()
{
	bool slashAtEnd = FileSystem::slashAtEnd( Filepath );

	if ( slashAtEnd )
	{
		FileSystem::dirRemoveSlashAtEnd( Filepath );
	}

	#if EFSW_PLATFORM != EFSW_PLATFORM_WIN32
	struct stat st;
	int res = lstat( Filepath.c_str(), &st );
	#else
	struct _stat st;
	int res = _wstat( String::fromUtf8( Filepath ).toWideString().c_str(), &st );
	#endif

	if ( 0 == res )
	{
		ModificationTime	= st.st_mtime;
		Size				= st.st_size;
		OwnerId				= st.st_uid;
		GroupId				= st.st_gid;
		Permissions			= st.st_mode;
		Inode				= st.st_ino;
	}

	if ( slashAtEnd )
	{
		FileSystem::dirAddSlashAtEnd( Filepath );
	}
}

bool FileInfo::operator==( const FileInfo& Other ) const
{
	return (	ModificationTime	== Other.ModificationTime &&
				Size				== Other.Size &&
				OwnerId				== Other.OwnerId &&
				GroupId				== Other.GroupId &&
				Permissions			== Other.Permissions &&
				Inode				== Other.Inode
	);
}

bool FileInfo::isDirectory() const
{
	return 0 != S_ISDIR(Permissions);
}

bool FileInfo::isRegularFile() const
{
	return 0 != S_ISREG(Permissions);
}

bool FileInfo::isReadable() const
{
#if EFSW_PLATFORM != EFSW_PLATFORM_WIN32
	static bool isRoot = getuid() == 0;
	return isRoot || 0 != S_ISRDBL(Permissions);
#else
	return 0 != S_ISRDBL(Permissions);
#endif
}

bool FileInfo::isLink() const
{
#if EFSW_PLATFORM != EFSW_PLATFORM_WIN32
	return S_ISLNK(Permissions);
#else
	return false;
#endif
}

std::string FileInfo::linksTo()
{
#if EFSW_PLATFORM != EFSW_PLATFORM_WIN32
	if ( isLink() )
	{
		char * ch = realpath( Filepath.c_str(), NULL);

		if ( NULL != ch )
		{
			std::string tstr( ch );

			free( ch );

			return tstr;
		}
	}
#endif
	return std::string("");
}

bool FileInfo::exists()
{
	bool slashAtEnd = FileSystem::slashAtEnd( Filepath );

	if ( slashAtEnd )
	{
		FileSystem::dirRemoveSlashAtEnd(Filepath);
	}

#if EFSW_PLATFORM != EFSW_PLATFORM_WIN32
	struct stat st;
	int res = stat( Filepath.c_str(), &st );
#else
	struct _stat st;
	int res = _wstat( String::fromUtf8( Filepath ).toWideString().c_str(), &st );
#endif

	if (slashAtEnd)
	{
		FileSystem::dirAddSlashAtEnd(Filepath);
	}

	return 0 == res;
}

FileInfo& FileInfo::operator=( const FileInfo& Other )
{
	this->Filepath			= Other.Filepath;
	this->Size				= Other.Size;
	this->ModificationTime	= Other.ModificationTime;
	this->GroupId			= Other.GroupId;
	this->OwnerId			= Other.OwnerId;
	this->Permissions		= Other.Permissions;
	this->Inode				= Other.Inode;
	return *this;
}

bool FileInfo::sameInode( const FileInfo& Other ) const
{
	return inodeSupported() && Inode == Other.Inode;
}

bool FileInfo::operator!=( const FileInfo& Other ) const
{
	return !(*this == Other);
}

} 
