//	FilePath.cpp - A class representing a file path on the OS.
//	----------------------------------------------------------------------------
//	This file is part of 'NiallsAVLib', base code for any kind of audiovisual
//	apps.
//	Copyright (C) 2012  Niall Moody
//
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program.  If not, see <http://www.gnu.org/licenses/>.
//	----------------------------------------------------------------------------

#include "FilePath.h"
#include "DebugHeaders.h"

#ifdef WIN32

#include <windows.h>
#include <shlobj.h>

#else

#include "UTF8File.h"

#include <errno.h>
#include <list>
#include <cstdlib>
#include <dirent.h>
#include <sys/stat.h>

#endif

#ifdef OSX

#include "SDL.h"
#include <CoreServices/CoreServices.h>
#include <CoreFoundation/CoreFoundation.h>

#endif

#include <sstream>
#include <iostream>

using namespace std;

//------------------------------------------------------------------------------
FilePath::FilePath()
{
	
}

//------------------------------------------------------------------------------
FilePath::FilePath(const wstring& p):
path(p)
{
#ifndef WIN32
	UTF8File::wstringToChar(path, narrowPath);
#endif
}

//------------------------------------------------------------------------------
FilePath::FilePath(const FilePath& other):
path(other.path)
{
#ifndef WIN32
	narrowPath = other.narrowPath;
#endif
}

//------------------------------------------------------------------------------
FilePath::~FilePath()
{
	
}

//------------------------------------------------------------------------------
void FilePath::setPath(const std::wstring& p)
{
	path = p;
#ifndef WIN32
	UTF8File::wstringToChar(path, narrowPath);
#endif
}

//------------------------------------------------------------------------------
wstring FilePath::getExtension() const
{
	wstring retval;

	if(path.empty())
		return retval;
	else if(isDirectory())
		return retval;

	retval = path.substr(path.find_last_of(L'.')+1);

	return retval;
}

//------------------------------------------------------------------------------
wstring FilePath::getName() const
{
	wstring retval;

#ifdef WIN32
	retval = path.substr(path.find_last_of(L'\\')+1);
#else
	retval = path.substr(path.find_last_of(L'/')+1);
#endif
	retval = retval.substr(0, retval.find_last_of(L'.'));

	return retval;
}

//------------------------------------------------------------------------------
wstring FilePath::getNameWithExtension() const
{
	wstring retval;

#ifdef WIN32
	retval = path.substr(path.find_last_of(L'\\')+1);
#else
	retval = path.substr(path.find_last_of(L'/')+1);
#endif

	return retval;
}

//------------------------------------------------------------------------------
bool FilePath::exists() const
{
	bool retval = false;

	if(path.empty())
		return retval;

#ifdef WIN32
	DWORD result;

	result = GetFileAttributes(path.c_str());
	retval = (result != 0xFFFFFFFF);
#else
	struct stat fileInfo;

	if(stat(narrowPath.c_str(), &fileInfo) == 0)
		retval = true;
#endif

	return retval;
}

//------------------------------------------------------------------------------
bool FilePath::isFile() const
{
	bool retval = false;

	if(path.empty())
		return retval;

#ifdef WIN32
	DWORD result;

	result = GetFileAttributes(path.c_str());
	retval = (result != 0xFFFFFFFF) && ((result&FILE_ATTRIBUTE_DIRECTORY) == 0);
#else
	struct stat fileInfo;

	//Check it exists first.
	if(stat(narrowPath.c_str(), &fileInfo) == 0)
	{
		DIR *d;

		//Now check whether it's a directory or a file.
		d = opendir(narrowPath.c_str());
		if(d)
			closedir(d);
		else
			retval = true;
	}
#endif

	return retval;
}

//------------------------------------------------------------------------------
bool FilePath::isDirectory() const
{
	bool retval = false;

	if(path.empty())
		return retval;

#ifdef WIN32
	DWORD result;

	result = GetFileAttributes(path.c_str());
	retval = (result != 0xFFFFFFFF) && ((result&FILE_ATTRIBUTE_DIRECTORY) != 0);

	if(result == 0xFFFFFFFF)
	{
		wchar_t tempstr[MAX_PATH];

		FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | 
					  FORMAT_MESSAGE_IGNORE_INSERTS,
					  NULL,
					  GetLastError(),
					  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
					  tempstr,
					  MAX_PATH,
					  NULL);
		DebugStatement(L"FilePath::isDirectory() error: " << tempstr);
	}
#else
	struct stat fileInfo;

	//Check it exists first.
	if(stat(narrowPath.c_str(), &fileInfo) == 0)
	{
		DIR *d;

		//Now check whether it's a directory or a file.
		d = opendir(narrowPath.c_str());
		if(d)
		{
			closedir(d);
			retval = true;
		}
	}
#endif

	return retval;
}

//------------------------------------------------------------------------------
bool FilePath::createAsDirectory() const
{
	bool retval = false;

#ifdef WIN32
	retval = CreateDirectory(path.c_str(), 0) != 0;
#else
	retval = (mkdir(narrowPath.c_str(), S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH) == 0);
#endif

	return retval;
}

//------------------------------------------------------------------------------
int FilePath::getNumChildren() const
{
	int retval = 0;

	if(path.empty())
		return retval;
	else if(!isDirectory())
		return retval;

#ifdef WIN32
	HANDLE handle;
	wstringstream tempPath;
	WIN32_FIND_DATA findData;

	//We do a search for all the files in this directory.
	tempPath << path << L"\\*";

	handle = FindFirstFile(tempPath.str().c_str(), &findData);
	if(handle != INVALID_HANDLE_VALUE)
	{
		if((wstring(findData.cFileName) != L".") &&
		   (wstring(findData.cFileName) != L".."))
		{
			++retval;
		}

		while(FindNextFile(handle, &findData))
		{
			if((wstring(findData.cFileName) != L".") &&
			   (wstring(findData.cFileName) != L".."))
			{
				++retval;
			}
		}

		FindClose(handle);
	}
#else
	DIR *d;
	struct dirent *entry;

	d = opendir(narrowPath.c_str());
	if(d)
	{
		entry = readdir(d);
		while(entry)
		{
			if((string(entry->d_name) != ".") &&
			   (string(entry->d_name) != ".."))
			{
				++retval;
			}
			entry = readdir(d);
		}

		closedir(d);
	}
#endif

	return retval;
}

//------------------------------------------------------------------------------
FilePath FilePath::getChild(int index) const
{
	int count = -1;
	FilePath retval;

#ifdef WIN32
	HANDLE handle;
	wstringstream tempPath;
	WIN32_FIND_DATA findData;

	//We do a search for all the files in this directory.
	tempPath << path << L"\\*";

	handle = FindFirstFile(tempPath.str().c_str(), &findData);
	if(handle != INVALID_HANDLE_VALUE)
	{
		if((wstring(findData.cFileName) != L".") &&
		   (wstring(findData.cFileName) != L".."))
		{
			++count;
		}

		if(count == index)
		{
			tempPath.str(L"");
			tempPath << path << L"\\" << findData.cFileName;
			retval.setPath(tempPath.str());
		}

		while(FindNextFile(handle, &findData))
		{
			if((wstring(findData.cFileName) != L".") &&
			   (wstring(findData.cFileName) != L".."))
			{
				++count;
			}

			if(count == index)
			{
				tempPath.str(L"");
				tempPath << path << L"\\" << findData.cFileName;
				retval.setPath(tempPath.str());
				break;
			}
		}

		FindClose(handle);
	}
#else
	DIR *d;
	wstring widestr;
	struct dirent *entry;
	stringstream tempPath;
	list<string> orderedChildren;
	list<string>::iterator it;

	d = opendir(narrowPath.c_str());
	if(d)
	{
		entry = readdir(d);
		while(entry)
		{
			//readdir() does not return its entries in the correct order, so...
			if((string(entry->d_name) != ".") &&
			   (string(entry->d_name) != ".."))
			{
				orderedChildren.push_back(entry->d_name);
			}
			entry = readdir(d);
		}
		orderedChildren.sort();

		for(it=orderedChildren.begin();it!=orderedChildren.end();++it)
		{
			++count;
			if(count == index)
			{
				tempPath.str("");
				tempPath << narrowPath << "/" << *it;

				UTF8File::charToWstring(tempPath.str(), widestr);
				retval.setPath(widestr);
				break;
			}
		}

		closedir(d);
	}
#endif

	return retval;
}

//------------------------------------------------------------------------------
FilePath FilePath::getChild(const wstring& child) const
{
	FilePath retval;
	wstringstream tempPath;

#ifdef WIN32
	tempPath << path << L"\\" << child;
#else
	tempPath << path << L"/" << child;
#endif
	retval.setPath(tempPath.str());

	return retval;
}

//------------------------------------------------------------------------------
FilePath FilePath::getParent() const
{
	FilePath retval;
	wstring tempstr;

#ifdef WIN32
	tempstr = path.substr(0, path.find_last_of(L'\\'));
#else
	tempstr = path.substr(0, path.find_last_of(L'/'));
#endif
	retval.setPath(tempstr);

	return retval;
}

//------------------------------------------------------------------------------
bool FilePath::copyTo(const wstring& dest)
{
	bool retval = false;

	if(path.empty())
		return retval;

#ifdef WIN32
	retval = (CopyFile(path.c_str(), dest.c_str(), false) != 0);
#else
	string narrowDest;
	stringstream tempstr;

	UTF8File::wstringToChar(dest, narrowDest);
	tempstr << "cp " << narrowPath << " " << narrowDest;
	retval = (system(tempstr.str().c_str()) == 0);
#endif

	return retval;
}

//------------------------------------------------------------------------------
FilePath FilePath::getAppDataPath()
{
	FilePath retval;

#ifdef WIN32
	wchar_t tempstr[MAX_PATH];

	if(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, tempstr) == S_OK)
		retval.setPath(tempstr);
#elif defined(LINUX)
	char *d = getenv("HOME");
	stringstream tempstr;
	wstring widestr;

	if(d)
		tempstr << d << "/." << APP_NAME;
	else
		tempstr << "~/." << APP_NAME; //Will this even work?

	UTF8File::charToWstring(tempstr.str(), widestr);
	retval.setPath(widestr);
#elif defined(OSX)
	char *d = getenv("HOME");
	wstringstream tempstr;

	if(d)
		tempstr << d << L"/Library/Application Support";
	else
		tempstr << L"~/Library/Application Support"; //Will this even work?
	retval.setPath(tempstr.str());
#endif

	return retval;
}

//------------------------------------------------------------------------------
FilePath FilePath::getCurrentExecutableDir()
{
	FilePath retval;

#ifdef WIN32
	wstring tempstr2;
	wchar_t tempstr[MAX_PATH];

	if(GetModuleFileName(NULL, tempstr, MAX_PATH))
	{
		tempstr2 = tempstr;

		retval.setPath(tempstr2.substr(0, tempstr2.find_last_of(L"\\")));
	}
#elif defined(LINUX)
	stringstream tempstr;
	wstring widestr;

	tempstr << INSTALL_DIR << "/bin";

	UTF8File::charToWstring(tempstr.str(), widestr);
	retval.setPath(widestr);
#elif defined(OSX)
	FSRef fsRef;
	CFURLRef bundlePath;

	bundlePath = CFBundleCopyBundleURL(CFBundleGetMainBundle());

	if(CFURLGetFSRef(bundlePath, &fsRef))
	{
		char tempstr[2048];
		FilePath tempPath;
		wstring widestr;

		FSRefMakePath(&fsRef, (Uint8 *)tempstr, 2048);

		UTF8File::charToWstring(string(tempstr), widestr);
		tempPath.setPath(widestr);
		retval = tempPath.getParent().getPath();
	}
#endif

	return retval;
}

//------------------------------------------------------------------------------
FilePath FilePath::getDataFilesDir()
{
	FilePath retval;

#ifdef WIN32
	wstring tempstr2;
	wchar_t tempstr[MAX_PATH];

	if(GetModuleFileName(NULL, tempstr, MAX_PATH))
	{
		tempstr2 = tempstr;

		retval.setPath(tempstr2.substr(0, tempstr2.find_last_of(L"\\")));
	}
#elif defined(LINUX)
	stringstream tempstr;
	wstring widestr;

	tempstr << INSTALL_DIR << "/share/" << APP_NAME;

	UTF8File::charToWstring(tempstr.str(), widestr);
	retval.setPath(widestr);
#elif defined(OSX)
	FSRef fsRef;
	CFURLRef bundlePath;
	wstringstream tempStream;

	bundlePath = CFBundleCopyBundleURL(CFBundleGetMainBundle());

	if(CFURLGetFSRef(bundlePath, &fsRef))
	{
		char tempstr[2048];

		FSRefMakePath(&fsRef, (Uint8 *)tempstr, 2048);

		tempStream << tempstr << L"/Contents/Resources";
		retval = tempStream.str();
	}
#endif

	return retval;
}

//------------------------------------------------------------------------------
FilePath FilePath::getHomeDir()
{
	FilePath retval;

#ifdef WIN32
	wchar_t tempstr[MAX_PATH];

	if(SHGetFolderPath(NULL, CSIDL_PROFILE, NULL, 0, tempstr) == S_OK)
		retval.setPath(tempstr);
#elif defined(LINUX)
	string tempstr = getenv("HOME");
	wstring widestr;

	UTF8File::charToWstring(tempstr, widestr);
	retval.setPath(widestr);
#elif defined(OSX)
	string tempstr = getenv("HOME");
	wstring widestr;
	
	UTF8File::charToWstring(tempstr, widestr);
	retval.setPath(widestr);
#endif

	return retval;
}

//------------------------------------------------------------------------------
vector<FilePath> FilePath::getFontsDirs()
{
	vector<FilePath> retval;

#ifdef WIN32
	wstring tempstr;
	size_t requiredSize;

	_wgetenv_s(&requiredSize, NULL, 0, L"SystemDrive");
	if(requiredSize)
	{
		tempstr.resize(requiredSize);

		_wgetenv_s(&requiredSize,
				   const_cast<wchar_t *>(tempstr.c_str()),
				   requiredSize,
				   L"SystemDrive");
		//Kill the trailing 0, because that screw things up for us.
		tempstr = tempstr.substr(0, tempstr.size()-1);
	}
	else
	{
		DebugStatement(L"FilePath: Could not find SystemDrive environment variable.");
	}

	//retval.push_back(FilePath(_wgetenv(L"SystemDrive")).getChild(L"Windows").getChild(L"Fonts"));
	retval.push_back(FilePath(tempstr).getChild(L"Windows").getChild(L"Fonts"));
#elif defined(LINUX)
	retval.push_back(FilePath(L"/usr/share/fonts/truetype"));
	retval.push_back(FilePath::getHomeDir().getChild(L".fonts"));
#elif defined(OSX)
	retval.push_back(FilePath::getHomeDir().getChild(L"Library").getChild(L"Fonts"));
	retval.push_back(FilePath(L"/Library/Fonts"));
	retval.push_back(FilePath(L"/System/Library/Fonts"));
	retval.push_back(FilePath(L"/Network/Library/Fonts"));
#endif

	return retval;
}

