/*
								+---------------------------------+
								|                                 |
								|   ***   Directory lookup  ***   |
								|                                 |
								|  Copyright  © -tHE SWINe- 2006  |
								|                                 |
								|              Dir.h              |
								|                                 |
								+---------------------------------+
*/

#pragma once
#ifndef __DIR_INCLUDED
#define __DIR_INCLUDED

/**
 *	@file Dir.h
 *	@author -tHE SWINe-
 *	@date 2006
 *	@brief basic directory lookup and file info functions
 *
 *	@date 2007-07-03
 *
 *	added filename allocation, deallocation and copy functions to TFileInfo
 *
 *	added constructors to TFileInfo::TTime
 *
 *	added TFileInfo::b_valid so it's clear wheter call to CDirectory::Get_FirstFile
 *	or CDirectory::Get_NextFile functions failed or wheter there are no more files in
 *	the directory (as was identified by the same return value before)
 *
 *	merged source code of linux CDirectory and win32 CDirectory together as it shares
 *	substantial sections
 *
 *	created Dir.cpp
 *
 *	added CDirTraversal class
 *
 *	@date 2007-07-09
 *
 *	added TFileInfo copy-constructor TFileInfo(const TFileInfo &r_t_other)
 *	changed CDirTraversal's stack to queue so directory traversal is not reversed
 *
 *	@date 2008-02-21
 *
 *	fixed Win32 implementation of CDir::Get_FirstFile where it didn't detect
 *	failure properly and sometimes it could return invalid file data
 *
 *	@date 2008-02-27
 *
 *	fixend inconsistency between linux and windows implementation (in linux, TFileInfo used
 *	to contain bare filenames only, in windows it contained full paths. full paths are now
 *	used in both implementations). note this error made CDirTraversal fail under linux.
 *
 *	@date 2008-03-04
 *
 *	now using Integer.h header
 *
 *	@date 2008-08-08
 *
 *	added \#ifdef for windows 64
 *
 *	@date 2008-10-25
 *
 *	added CDirectory::path_Separator
 *
 *	@date 2008-11-21
 *
 *	added CDirTraversal::Traverse2, enabling file listener to interrupt directory traversal
 *
 *	added parameter b_recurse_subdirectories to both original CDirTraversal::Traverse() and
 *	new CDirTraversal::Traverse2() functions.
 *
 *	cleared-up CDirTraversal class documentation comment, regarding recursive traversal
 *	without recursion (ie. using explicit stack, placed on heap)
 *
 *	@date 2008-12-11
 *
 *	added TFileInfo::n_Size64() for greater conveniency
 *
 *	@date 2008-12-22
 *
 *	removed some g++ warnings
 *
 *	@date 2009-05-23
 *
 *	removed all instances of std::vector::reserve and replaced them by stl_ut::Reserve_*
 *
 *	@date 2009-10-20
 *
 *	fixed some warnings when compiling under VC 2005, implemented "Security
 *	Enhancements in the CRT" for VC 2008. compare against MyProjects_2009-10-19_
 *
 *	@date 2010-01-07
 *
 *	renamed TFileInfo::p_s_Filename() to TFileInfo::p_s_Path(), added TFileInfo::p_s_FileName()
 *	and TFileInfo::p_s_Extension()
 *
 *	added TFileInfo::GetInfo() which updates info about file from the filesystem
 *
 *	added TFileInfo constructor with filename, which enables TFileInfo being used
 *	much like File in java
 *
 *	added TFileInfo::b_exists, which changes behavior of CDirectory::Get_FirstFile()
 *		and CDirectory::Get_NextFile(), which used TFileInfo::b_valid to indicate whether
 *		there are any more files in the directory. access to TFileInfo::b_valid is now limited
 *		only trough TFileInfo::b_Valid() function, so the old incompatible code wouldn't
 *		compile.
 *
 *	added TFileInfo::n_mode
 *
 *	@date 2010-08-10
 *
 *	added code, handling correct construction of TFileInfo for root directories
 *	via constructor / GetInfo() under windows.
 *
 *	@note note creation / last access / last modification times
 *		are not available for root directories under windows
 *
 *	@date 2010-10-28
 *
 *	fixed minor memory leak, caused by not closing FindFirstFile() handles on windows systems.
 *	it didn't manifest as memory leak as memory got allocated on system side, it just made
 *	pagefile grow on frequent use.
 *
 *	@date 2010-10-29
 *
 *	Unified windows detection macro to "\#if defined(_WIN32) || defined(_WIN64)".
 *
 *	@date 2012-06-19
 *
 *	Moved multiple inclusion guard before file documentation comment.
 *
 *	@date 2012-07-17
 *
 *	Modified PRIsizeBparams() and PRIsizeBparamsExt() to be able to cope with uint64_t
 *	properly even with old compilers.
 *
 *	Added CPath class for working with file paths, directories and files (untested as of yet).
 *
 *	Fixed a minor bug in linux version of TFileInfo::GetTempFilename() where the
 *	filename contained two redundant 'X' characters (but caused no problems otherwise).
 *
 *	@date 2012-11-04
 *
 *	Compiled under linux, fixed some bugs carried in over time.
 *
 *	@date 2013-11-13
 *
 *	Fixed temp directory lookup, linux temp directory is not always /tmp, e.g. on cluster computers,
 *	temp is often directory, specific to the job id, like "/tmp/pbs.132048.dm2".
 *
 *	@date 2014-03-11
 *
 *	Added std::string versions of CDirectory::CDirectory(), TFileInfo::TFileInfo()
 *	and TFileInfo::SetFilename().
 *
 *	@date 2014-10-15
 *
 *	Added explicit bad path handling in CDirTraversal::Traverse() and CDirTraversal::Traverse2().
 *
 */

#include "NewFix.h"

#if defined(_WIN32) || defined(_WIN64)
#define NOMINMAX
#include <windows.h>
#include <string.h>
#include <io.h>
#else // _WIN32 || _WIN64
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
#endif // _WIN32 || _WIN64
#include <time.h>
#include <string>
#include <algorithm>

#include "Integer.h"
#include "StlUtils.h"

/**
 *	@def PRIsizeB
 *
 *	@brief simple macro for printing file sizes in user-readable format
 *
 *	This macro is used in conjunction with PRIsizeBparams.
 */
#define PRIsizeB "%.*f %s"

#if 0

/**
 *	@def f_MkLongDbl
 *	@brief conversion to double, for PRIsizeBparams
 */
#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER <= 1200
#define f_MkLongDbl(a) double(int64_t((a))) // MSVC60 can't convert uint64_t to double (but fixed by SP5 or SP6)
#else // _MSC_VER && !__MWERKS__ && _MSC_VER <= 1200
#define f_MkLongDbl(a) double((a))
#endif // _MSC_VER && !__MWERKS__ && _MSC_VER <= 1200

#endif // 0

/**
 *	@def PRIsizeBparams
 *
 *	@brief simple macro for expanding size in bytes to number of fractional
 *		digits, value and unit
 *
 *	This is used with PRIsizeB for printing sizes. Usage is as follows:
 *
 *@code
 *	void Test()
 *	{
 *		printf("%d = " PRIsizeB "B\n", 256, PRIsizeBparams(256));
 *		printf("%d = " PRIsizeB "B\n", 1024 + 256, PRIsizeBparams(1024 + 256));
 *		printf("%d = " PRIsizeB "B\n", 1048576, PRIsizeBparams(1048576));
 *	}
 *
 *	output:
 *
 *	256 = 256 B
 *	1280 = 1.25 kB
 *	1048576 = 1.00 MB
 *@endcode
 *
 *	More extended version of this macro is PRIsizeBparamsExt.
 *
 *	@param[in] s is size in bytes
 */
#define PRIsizeBparams(s) (((s) < 1024)? 0 : 2), (((s) < 1024)? int(s) : ((s) < 1048576)? \
	double(s) / 1024.0 : ((s) < 1073741824)? double(s) / 1048576.0 : ((s) < 1099511627776)? \
	double(s) / 1073741824.0 : double(s) / 1099511627776.0), (((s) < 1024)? "" : ((s) < 1048576)? "k" : \
	((s) < 1073741824)? "M" : ((s) < 1099511627776)? "G" : "T")

/**
 *	@def PRIsizeBparamsExt
 *
 *	@brief simple macro for expanding size in bytes to number of fractional
 *		digits, value and unit
 *
 *	This is used with PRIsizeB for printing sizes. Usage is as follows:
 *
 *@code
 *	void Test()
 *	{
 *		printf("%d = " PRIsizeB "B\n", 256, PRIsizeBparamsExt(256, 1, 2, false));
 *		printf("%d = " PRIsizeB "B\n", 256, PRIsizeBparamsExt(256, 0, 2, true));
 *		printf("%d = " PRIsizeB "B\n", 1048576, PRIsizeBparamsExt(1048576, 0, 3, false));
 *	}
 *
 *	output:
 *
 *	256 = 256.0 B
 *	256 = 0.25 kB
 *	1048576 = 1.000 MB
 *@endcode
 *
 *	More extended version of this macro is PRIsizeBparamsExt.
 *
 *	@param[in] s is size in bytes
 *	@param[in] byteDecNum is number of fractional digits when printing size in bytes
 *	@param[in] decNum is number of fractional digits when printing size not in bytes
 *	@param[in] forceKB is flag, controlling wheter to allow output in bytes (false),
 *		or to force output in kilobytes (true); does not have any effect if s >= 1024
 */
#define PRIsizeBparamsExt(s,byteDecNum,decNum,forceKB) (((s) < 1024 && !forceKB)? \
	byteDecNum : decNum), (((s) < 1024 && !forceKB)? int(s) : ((s) < 1048576)? double(s) / 1024.0 : \
	((s) < 1073741824)? double(s) / 1048576.0 : ((s) < 1099511627776)? double(s) / 1073741824.0 : \
	double(s) / 1099511627776.0), (((s) < 1024 && !forceKB)? "" : ((s) < 1048576)? "k" : \
	((s) < 1073741824)? "M" : ((s) < 1099511627776)? "G" : "T")

/**
 *	@brief simple file-info structure
 *
 *	Keeps memory for it's filename
 *
 *	@todo Rewrite this so filename is stored in std::string, get rid of unnecessary
 *		memory management code.
 */
struct TFileInfo {
protected:
	bool b_valid; /**< are data inside this structure valid? */

public:
	/**
	 *	@brief determines wheter contents of this structure are valid
	 *
	 *	@return Returns true in case this structure contains valid
	 *		information about a file, otherwise returns false.
	 *
	 *	@note All file info's should be valid, even if the file doesn't exist
	 *		(b_exists is not set then). In case this function returns false,
	 *		it's most likely program error (not enough memory, ...).
	 */
	inline bool b_Valid() const
	{
		return b_valid;
	}

	bool b_exists; /**< does specified file / directory exist? @note in case file doesn't exist, value of the following fields are undefined */
	bool b_directory; /**< file / directory flag */

	std::string s_filename; /**< file name */

	/**
	 *	@brief access flag names
	 */
	enum {
		flag_Read = 1,							/**< general reading privilege */
		flag_Write = 2,							/**< general writing privilege */
		flag_Execute = 4,						/**< general execute privilege */

		flag_owner_Read = flag_Read,			/**< owner reading privilege */
		flag_owner_Write = flag_Write,			/**< owner writing privilege */
		flag_owner_Execute = flag_Execute,		/**< owner execute privilege */

		flag_group_Read = flag_Read << 3,		/**< group reading privilege */
		flag_group_Write = flag_Write << 3,		/**< group writing privilege */
		flag_group_Execute = flag_Execute << 3,	/**< group execute privilege */

		flag_other_Read = flag_Read << 6,		/**< other reading privilege */
		flag_other_Write = flag_Write << 6,		/**< other writing privilege */
		flag_other_Execute = flag_Execute << 6	/**< other execute privilege */
	};

	int n_mode; /**< combination of flag_[Read|Write] @note this field is only for files, it is undefined for directories */
	int n_flags; /**< access flags (combination of flag_[owner|group|other]_[Read|Write|Execute]) */
	uint32_t n_size_lo; /**< filesize - 32 low bits */
	uint32_t n_size_hi; /**< filesize - 32 high bits */

	/**
	 *	@brief gets file size
	 *
	 *	@return Returns 64-bit file size.
	 */
	inline uint64_t n_Size64() const
	{
		return (uint64_t(n_size_hi) << 32) | n_size_lo;
	}

	/**
	 *	@brief very simple time structure (unification for linux / windows)
	 *
	 *	@note This was derived from windows' SYSTEMTIME structure, see it's
	 *		documentation on MSDN for more details.
	 *
	 *	@todo Supply conversions back to struct FILETIME / struct tm.
	 */
	struct TTime {
		short n_month;	/**< month index (1 - 12) */
		short n_day;	/**< month day index (1 - 31) */
		short n_year;	/**< year (1601 - 30827) */
		short n_hour;	/**< hour (0 - 23) */
		short n_minute;	/**< minute (0 - 59) */
		short n_second;	/**< second (0 - 59) */

		/**
		 *	@brief default constructor
		 *
		 *	Sets all members to -1.
		 */
		inline TTime()
			:n_month(-1), n_day(-1), n_year(-1), n_hour(-1), n_minute(-1), n_second(-1)
		{}

#if defined(_WIN32) || defined(_WIN64)
		/**
		 *	@brief (win32) conversion constructor
		 *
		 *	Takes <tt>struct SYSTEMTIME</tt> as input, always succeeds.
		 *
		 *	@param[in] r_t_time is input time
		 */
		TTime(const SYSTEMTIME &r_t_time);

		/**
		 *	@brief (win32) conversion constructor
		 *
		 *	Takes <tt>struct FILETIME</tt> as input.
		 *
		 *	@param[in] r_t_time is input time
		 *
		 *	@note FILETIME needs to be converted to SYSTEMTIME first, in case conversion
		 *		fails, all members are set to -1.
		 */
		TTime(const FILETIME &r_t_time);
#else // _WIN32 || _WIN64
		/**
		 *	@brief (unix) conversion constructor
		 *
		 *	Takes <tt>struct tm</tt> as input, always succeeds
		 *
		 *	@param[in] p_time is input time
		 */
		TTime(const tm *p_time);
#endif // _WIN32 || _WIN64

		/**
		 *	@brief equality operator
		 *
		 *	@param[in] r_t_time is time to be compared to
		 *
		 *	@return Returns true in case this is equal to r_t_time, otherwise false.
		 */
		bool operator ==(const TTime &r_t_time) const;

		/**
		 *	@brief less-than operator
		 *
		 *	@param[in] r_t_time is time to be compared to
		 *
		 *	@return Returns true in case this is less than r_t_time, otherwise false.
		 */
		bool operator <(const TTime &r_t_time) const;
	};

	/**
	 *	@brief file time type names
	 */
	enum {
		time_Creation = 0,	/**< time of file creation */
		time_LastAccess,	/**< time of last access to a file */
		time_LastWrite		/**< time of last modification */
	};

	TTime p_time[3]; /**< file time (use time_[Creation|LastAccess|LastWrite] as index) */

	/**
	 *	@brief default constructor
	 *
	 *	Doesn't initialize anything but file name, creates invalid file info.
	 *
	 *	@todo Write constructor with file name specification (get file info for specific file,
	 *		eg. for "c:\blah.txt", so TFileInfo could be used without CDirectory)
	 */
	inline TFileInfo()
		:b_valid(false)
	{}

	/**
	 *	@brief default constructor
	 *
	 *	Copies filename and fills the structure with information about the file. In case the file
	 *	doesn't exist, b_valid is set, but b_exists isn't.
	 *
	 *	@param[in] p_s_filename is null-terminated string, containing the file name
	 */
	inline TFileInfo(const char *p_s_filename)
	{
		b_valid = SetFilename(p_s_filename) && GetInfo();
		// set filename and get info about the file
	}

	/**
	 *	@brief default constructor
	 *
	 *	Copies filename and fills the structure with information about the file. In case the file
	 *	doesn't exist, b_valid is set, but b_exists isn't.
	 *
	 *	@param[in] r_s_filename is string, containing the file name
	 */
	inline TFileInfo(const std::string &r_s_filename)
	{
		b_valid = SetFilename(r_s_filename) && GetInfo();
		// set filename and get info about the file
	}

	/**
	 *	@brief copy constructor
	 *
	 *	Copies information about file r_other to this. Note there may not be enough
	 *		memory for file name, check if b_valid is set.
	 *
	 *	@param r_other is file info to be copied
	 */
	inline TFileInfo(const TFileInfo &r_other)
	{
		*this = r_other; // copy values
	}

	/**
	 *	@brief gets file name
	 *	@return Returns filename as null-terminated string
	 *		  or an empty string in case no filename was specified yet.
	 */
	const char *p_s_FileName() const;

	/**
	 *	@brief gets file name
	 *	@param[in] p_s_path is null-trminated string, containing path to the file
	 *	@return Returns filename as null-terminated string.
	 */
	static const char *p_s_FileName(const char *p_s_path);

	/**
	 *	@brief gets file extension
	 *	@return Returns file extension as null-terminated string,
	 *		or an empty string in case there's no extension.
	 */
	const char *p_s_Extension() const;

	/**
	 *	@brief gets file extension
	 *	@param[in] p_s_path is null-trminated string, containing path to the file
	 *	@return Returns file extension as null-terminated string,
	 *		or an empty string in case there's no extension.
	 */
	static const char *p_s_Extension(const char *p_s_path);

	/**
	 *	@brief gets file path
	 *	@return Returns file path (including filename) as null-terminated string,
	 *		  or 0 in case no filename was specified yet.
	 */
	inline const char *p_s_Path() const
	{
		return s_filename.c_str();
	}

	/**
	 *	@brief marks the file as non-existent (but valid)
	 */
	inline void SetNoFile()
	{
		b_valid = true;
		b_exists = false;
	}

	/**
	 *	@brief marks the file information as invalid (to mark error)
	 */
	inline void SetInvalid()
	{
		b_valid = false;
	}

	/**
	 *	@brief sets new filename
	 *
	 *	Sets filename to p_s_new_filename.
	 *
	 *	@param[in] p_s_new_filename is null-terminated string, containing new file name
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note Note p_s_new_filename can be 0 (sets this filename to 0 as well).
	 */
	bool SetFilename(const char *p_s_new_filename);

	/**
	 *	@brief sets new filename
	 *
	 *	Sets filename to r_s_new_filename.
	 *
	 *	@param[in] r_s_new_filename is string, containing new file name
	 *
	 *	@return Returns true on success, false on failure.
	 */
	inline bool SetFilename(const std::string &r_s_new_filename)
	{
		return SetFilename(r_s_new_filename.c_str());
	}

	/**
	 *	@brief copy-operator
	 *
	 *	Copies contents of r_t_file_info to this.
	 *
	 *	@param[in] r_t_file_info is file info to be copied
	 *
	 *	@return Returns true on success, false in case there
	 *		was not enough memory to hold filename string.
	 */
	bool operator =(const TFileInfo &r_t_file_info);

	/**
	 *	@brief generates name for temporary file
	 *
	 *	Creates name for temporary file in default system temp path.
	 *		If the function succeeds, it creates an empty file, so it
	 *		may be considered thread-safe (no two threads can create the same file).
	 *
	 *	@param[out] r_s_temp_file_name will contain unique temporary file name
	 *	@param[in] p_s_app_id is short application identifier,
	 *		which will be part of temp filename (may not be empty)
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note p_s_app_id might get truncated if it's too long (3 characters is usual length)
	 */
	static bool Get_TempFileName(std::string &r_s_temp_file_name, const char *p_s_app_id = "tmp");

#if defined(_WIN32) || defined(_WIN64)
	/**
	 *	@brief updates information about the file
	 *
	 *	Calls OS api to get information about the file (specified by p_s_Path()),
	 *	and fills member variables of the structure.
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note Success is even if the file doesn't exist.
	 */
	bool GetInfo();

	/**
	 *	@brief updates information about the file
	 *
	 *	Calls OS api to get information about the file (specified by p_s_Path()),
	 *	and fills member variables of the structure. This overload is optimized for
	 *	getting file information from existing WIN32_FIND_DATA structure (when iterating
	 *	trough files in a directory).
	 *
	 *	@param[in] r_t_find_data contains information about the file (not filename)
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note Success is even if the file doesn't exist.
	 */
	bool GetInfo(const WIN32_FIND_DATAA &r_t_find_data);
#else // _WIN32 || _WIN64
	/**
	 *	@brief updates information about the file
	 *
	 *	Calls OS api to get information about the file (specified by p_s_Path()),
	 *	and fills member variables of the structure.
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note Success is even if the file doesn't exist.
	 */
	bool GetInfo();
#endif // _WIN32 || _WIN64
};

/**
 *	@brief simple directory class
 *
 *	This class allows for very basic access to directory's files (sub-directories).
 */
class CDirectory {
public:
	/**
	 *	@brief path separator character
	 */
	enum {
#if defined(_WIN32) || defined(_WIN64)
		path_Separator = '\\'	/**< windows path separator character */
#else // _WIN32 || _WIN64
		path_Separator = '/'	/**< unix path separator character */
#endif // _WIN32 || _WIN64
	};

private:
	char *m_p_s_dir_name;

#if defined(_WIN32) || defined(_WIN64)
	HANDLE m_h_prev_file;
#else // _WIN32 || _WIN64
    DIR *m_p_dir;
#endif // _WIN32 || _WIN64

public:
	/**
	 *	@brief default constructor
	 *
	 *	Creates handle to directory with address p_s_dir_name. Call p_s_Name() to find
	 *		out wheter there was enough memory for string copy, otherwise this object
	 *		can't be used to find files (sub-directories).
	 *
	 *	@param[in] p_s_dir_name is directory name (such as "c:\blah" or
	 *		"c:\blah\" or ".\blah" or ".\blah\" or "blah" or "blah\")
	 *
	 *	@note p_s_dir_name is copied and can be freed right after the constructor returns.
	 *
	 *	@todo Rewrite this so name is stored in std::string, get rid of unnecessary
	 *		memory management code.
	 */
	inline CDirectory(const char *p_s_dir_name)
	{
		Initialize(p_s_dir_name);
	}

	/**
	 *	@brief default constructor
	 *
	 *	Creates handle to directory with address p_s_dir_name. Call p_s_Name() to find
	 *		out wheter there was enough memory for string copy, otherwise this object
	 *		can't be used to find files (sub-directories).
	 *
	 *	@param[in] r_s_dir_name is directory name (such as "c:\blah" or
	 *		"c:\blah\" or ".\blah" or ".\blah\" or "blah" or "blah\")
	 *
	 *	@note The contents of r_s_dir_name are copied and
	 *		can be freed right after the constructor returns.
	 *
	 *	@todo Rewrite this so name is stored in std::string, get rid of unnecessary
	 *		memory management code.
	 */
	inline CDirectory(const std::string &r_s_dir_name)
	{
		Initialize(r_s_dir_name.c_str());
	}

	/**
	 *	@brief destructor
	 */
	~CDirectory();

	/**
	 *	@brief gets directory name
	 *
	 *	@return Returns directory name as passed to constructor.
	 *	@return Returns 0 in case there was not enough memory for string copy.
	 */
	inline const char *p_s_Name() const
	{
		return m_p_s_dir_name;
	}

	/**
	 *	@brief finds a file
	 *
	 *	Gets first file in the directory, copies it's data to r_t_file. In case the
	 *		directory doesn't have any files, r_t_file.b_exists is set to false.
	 *
	 *	@param[out] r_t_file on return contains information about found file
	 *
	 *	@return Returns true on success (even if directory is empty - that is marked by
	 *		r_t_file.b_valid), false on failure (possibly inaccesible / non-existant
	 *		directory).
	 *
	 *	@note r_t_file.b_valid is always true, even if the file doesn't exist (aparts
	 *		from when the function fails).
	 */
	bool Get_FirstFile(TFileInfo &r_t_file);

	/**
	 *	@brief finds a file
	 *
	 *	Gets next file in the directory, copies it's data to r_t_file. In case the
	 *		directory doesn't have any more files, r_t_file.b_exists is set to false.
	 *	In case Get_FirstFile() was not called prior calling Get_NextFile, it's called
	 *		automatically.
	 *
	 *	@param[out] r_t_file on return contains information about found file 
	 *
	 *	@return Returns true on success (even if directory does not contain (more)
	 *		files - that is marked by r_t_file.b_valid), false on failure (possibly
	 *		inaccesible / non-existant directory).
	 *
	 *	@note In order to get file list for this folder again, Get_FirstFile()
	 *		must be called (r_t_file contents are only written to, not read from).
	 *	@note r_t_file.b_valid is always true, even if the file doesn't exist (aparts
	 *		from when the function fails).
	 */
	bool Get_NextFile(TFileInfo &r_t_file);

protected:
#if defined(_WIN32) || defined(_WIN64)
	bool GetFileInfo(const WIN32_FIND_DATAA &r_t_find_data, TFileInfo &r_t_file);
#endif // _WIN32 || _WIN64
	void Initialize(const char *p_s_dir_name);

private:
	CDirectory(const CDirectory &r_other); // no-copy, use pointers
	CDirectory &operator =(const CDirectory &r_other); // no-copy, use pointers
};

/**
 *	@brief simple recursive directory traversal
 *
 *	Implemented in non-recursive manner (explicit stack), so it wouldn't cause
 *		stack overflow when traversing complex directory trees. To show directory
 *		listing, use:
 *@code
 *	void OnFile(const TFileInfo &r_t_file)
 *	{
 *		printf((r_t_file.b_directory)? "[%s]\n" : "%s\n", r_t_file.p_s_Path());
 *		// directories printed in brackets
 *	}
 *
 *	int main(int n_arg_num, const char **p_arg_list)
 *	{
 *		_ASSERTE(n_arg_num == 2);
 *		return !CDirTraversal::Traverse(p_arg_list[1], OnFile);
 *		// traverse directory from commandline
 *	}
 *@endcode
 */
class CDirTraversal {
public:
	/**
	 *	@brief traverses directory
	 *
	 *	Traverses directory p_s_dir_name, all files found are passed to r_file_listener.
	 *		If b_recurse_subdirectories is set, subdirectories are traversed as well.
	 *		All files and subdirectories in a directory are passed to r_file_listener before
	 *		traversing into any of subdirectories (if b_recurse_subdirectories is set,
	 *		that is).
	 *
	 *	@param[in] p_s_dir_name is name of directory to be traversed
	 *	@param[in] r_file_listener is CFileListener object; CFileListener is file listener
	 *		functor, must implement void operator () with a single <tt>const TFileInfo &</tt>
	 *		parameter.
	 *	@param[in] b_recurse_subdirectories is directory recursion flag
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note This doesn't give file listener option to early terminate the traversal.
	 *		That is implemented in Traverse2().
	 *	@note Files, passed to file listener are always valid (TFileInfo::b_valid is true).
	 */
	template <class CFileListener>
	static bool Traverse(const char *p_s_dir_name,
		CFileListener &r_file_listener, bool b_recurse_subdirectories = true);

	/**
	 *	@brief traverses directory
	 *
	 *	Traverses directory p_s_dir_name, all files found are passed to r_file_listener.
	 *		If b_recurse_subdirectories is set, subdirectories are traversed as well.
	 *		All files and subdirectories in a directory are passed to r_file_listener before
	 *		traversing into any of subdirectories (if b_recurse_subdirectories is set,
	 *		that is).
	 *
	 *	@param[in] p_s_dir_name is name of directory to be traversed
	 *	@param[in] r_file_listener is CFileListener object; CFileListener is file listener
	 *		functor, must implement bool operator () with a single <tt>const TFileInfo &</tt>
	 *		parameter. In case it returns true, traversal continues, in case it returns false,
	 *		Traverse2() returns false immediately.
	 *	@param[in] b_recurse_subdirectories is directory recursion flag
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note Files, passed to file listener are always valid (TFileInfo::b_valid is true).
	 */
	template <class CFileListener>
	static bool Traverse2(const char *p_s_dir_name,
		CFileListener &r_file_listener, bool b_recurse_subdirectories = true);

protected:
	static inline void DeleteDir(CDirectory *p_dir);
};

//#define __CPATH_GET_USER_HOME_DIRECTORY // not in MSVC60!
#define __CPATH_NORMALIZE_NETWORK_DRIVE_SUPPORT

#if !defined(_WIN32) && !defined(_WIN64)
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h> // get user home directory
#else // !_WIN32 && !_WIN64
#ifdef __CPATH_GET_USER_HOME_DIRECTORY
#include <userenv.h>
#pragma comment(lib, "userenv.lib")
#endif // __CPATH_GET_USER_HOME_DIRECTORY
#ifdef __CPATH_NORMALIZE_NETWORK_DRIVE_SUPPORT
#include <winnetwk.h>
#pragma comment(lib, "mpr.lib")
#endif // __CPATH_NORMALIZE_NETWORK_DRIVE_SUPPORT
#include <direct.h> // _getcwd
#endif // !_WIN32 && !_WIN64

#if 1

#include "StlUtils.h"
#include <stdio.h>
#if defined(_MSC_VER) && !defined(__MWERKS__) && !defined(for) && _MSC_VER <= 1200
#define for if(0) {} else for
#endif // _MSC_VER && !__MWERKS__ && !for && _MSC_VER <= 1200
// msvc 'for' scoping hack

/**
 *	@brief path utilities
 *	@note This is largerly untested. Not to be used until this notice is removed.
 */
class CPath { // todo - add const char * versions, add versions with src = dest
public:
	enum {
		path_Separator = CDirectory::path_Separator /**< @brief path separator character (a slash) */
	};

#if 0
	static bool UnitTests()
	{
		{
			std::string s_temp;
			if(!Get_TempDirectory(s_temp))
				return false;
			std::string s_temp_file;
			if(!TFileInfo::Get_TempFileName(s_temp_file))
				return false;
			DeleteFile(s_temp_file.c_str()); // don't leave rubbish
			s_temp_file.erase(s_temp_file.rfind('\\'));
			_ASSERTE(!s_temp_file.compare(s_temp));
			printf("Get_TempDirectory() returns \'%s\'\n", s_temp.c_str());
		}
		// test Get_TempDirectory()

		{
			std::string s_cur;
			if(!Get_CurrentDirectory(s_cur))
				return false;
			printf("Get_CurrentDirectory() returns \'%s\'\n", s_cur.c_str());
			system("mkdir _test_gcwd");
			//system("cd _test_gcwd"); // has no effect
			_chdir((s_cur + "\\_test_gcwd").c_str());
			printf("mkdir _test_gcwd\ncd _test_gcwd\n");
			std::string s_cur2;
			if(!Get_CurrentDirectory(s_cur2))
				return false;
			_ASSERTE(s_cur2 == (s_cur + "\\_test_gcwd"));
			printf("Get_CurrentDirectory() returns \'%s\'\n", s_cur2.c_str());
			//system("cd .."); // has no effect
			_chdir((s_cur + "\\_test_gcwd\\..").c_str());
			printf("cd ..\n");
			std::string s_cur3;
			if(!Get_CurrentDirectory(s_cur3))
				return false;
			_ASSERTE(s_cur3 == s_cur);
			system("rmdir _test_gcwd");
			printf("Get_CurrentDirectory() returns \'%s\'\n", s_cur3.c_str());
		}
		// test Get_CurrentDirectory

#ifdef __CPATH_GET_USER_HOME_DIRECTORY
		{
			std::string s_home;
			if(!Get_UserHomeDirectory(s_home))
				return false;
			printf("Get_UserHomeDirectory() returns \'%s\'\n", s_home.c_str());
		}
#else // __CPATH_GET_USER_HOME_DIRECTORY
		printf("Get_UserHomeDirectory() not compiled\n");
#endif // __CPATH_GET_USER_HOME_DIRECTORY
		// test Get_UserHomeDirectory

		{
			std::string s_norm;
			if(!WeakNormalize(s_norm, "c:\\foo\\bar"))
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!WeakNormalize(s_norm, "c:/foo/bar")) // bad slashes
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!WeakNormalize(s_norm, "c:/foo/./bar")) // dot
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!WeakNormalize(s_norm, "c:/foo//bar")) // double slashes
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!WeakNormalize(s_norm, "c://foo//bar")) // more double slashes
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!WeakNormalize(s_norm, "c://foo//bar//")) // even more double slashes
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!WeakNormalize(s_norm, "c:/foo/barbar/../bar")) // double dot
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!WeakNormalize(s_norm, "c:/foo/barbar/../bar/."))
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!WeakNormalize(s_norm, "c://foo//barbar//..//bar//.///")) // more slashes along with "complicated" path
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!WeakNormalize(s_norm, "c:/foo/./barbar/../bar/."))
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!WeakNormalize(s_norm, "./foo/./barbar/../bar/."))
				return false;
			_ASSERTE(!s_norm.compare("foo\\bar"));
			if(!WeakNormalize(s_norm, "../foo/./barbar/../bar/."))
				return false;
			_ASSERTE(!s_norm.compare("..\\foo\\bar"));
			if(!WeakNormalize(s_norm, "../foo/./barbar/../../../bar/."))
				return false;
			_ASSERTE(!s_norm.compare("..\\..\\bar"));
			if(!WeakNormalize(s_norm, "../foo/./barbar/../../../bar/.."))
				return false;
			_ASSERTE(!s_norm.compare("..\\.."));
			if(!WeakNormalize(s_norm, "../../../../.."))
				return false;
			_ASSERTE(!s_norm.compare("..\\..\\..\\..\\.."));
			if(!WeakNormalize(s_norm, ""))
				return false;
			_ASSERTE(!s_norm.compare(""));
			if(!WeakNormalize(s_norm, "."))
				return false;
			_ASSERTE(!s_norm.compare(""));
			if(!WeakNormalize(s_norm, ".."))
				return false;
			_ASSERTE(!s_norm.compare(".."));
			if(!WeakNormalize(s_norm, "/"))
				return false;
			_ASSERTE(!s_norm.compare(""));
			if(!WeakNormalize(s_norm, "c:/.."))
				return false;
			_ASSERTE(!s_norm.compare("c:\\..")); // can't remove .. due to absolute path
			if(!WeakNormalize(s_norm, "//192.168.0.2/foo/bar"))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\foo\\bar"));
			if(!WeakNormalize(s_norm, "//192.168.0.2/.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\.."));
			if(!WeakNormalize(s_norm, "//192.168.0.2\\foo\\bar"))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\foo\\bar"));
			if(!WeakNormalize(s_norm, "//192.168.0.2/foo/bar")) // bad slashes
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\foo\\bar"));
			if(!WeakNormalize(s_norm, "//192.168.0.2/foo/./bar")) // dot
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\foo\\bar"));
			if(!WeakNormalize(s_norm, "//192.168.0.2/foo/barbar/../bar")) // double dot
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\foo\\bar"));
			if(!WeakNormalize(s_norm, "//192.168.0.2/foo/barbar/../bar/."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\foo\\bar"));
			if(!WeakNormalize(s_norm, "//192.168.0.2/foo/./barbar/../bar/."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\foo\\bar"));
			if(!WeakNormalize(s_norm, "//192.168.0.2/./foo/./barbar/../bar/."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\foo\\bar"));
			if(!WeakNormalize(s_norm, "//192.168.0.2/../foo/./barbar/../bar/."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\..\\foo\\bar"));
			if(!WeakNormalize(s_norm, "//192.168.0.2/../foo/./barbar/../../../bar/."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\..\\..\\bar"));
			if(!WeakNormalize(s_norm, "//192.168.0.2/../foo/./barbar/../../../bar/.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\..\\.."));
			if(!WeakNormalize(s_norm, "//192.168.0.2/../../../../.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\..\\..\\..\\..\\.."));
			if(!WeakNormalize(s_norm, "//192.168.0.2"))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2"));
			if(!WeakNormalize(s_norm, "//192.168.0.2/."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2"));
			if(!WeakNormalize(s_norm, "//192.168.0.2/.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\.."));
			if(!WeakNormalize(s_norm, "//192.168.0.2/"))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2"));
			if(!WeakNormalize(s_norm, "//./COM1"))
				return false;
			_ASSERTE(!s_norm.compare("\\\\.\\COM1"));
			if(!WeakNormalize(s_norm, "//./COM1/.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\.\\"));
			if(!WeakNormalize(s_norm, "//./COM1/../.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\.\\.."));
			if(!WeakNormalize(s_norm, "//./../COM1/.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\.\\.."));
			if(!WeakNormalize(s_norm, "//./../COM1"))
				return false;
			_ASSERTE(!s_norm.compare("\\\\.\\..\\COM1"));
			if(!WeakNormalize(s_norm, "//?/GLOBALROOT"))
				return false;
			_ASSERTE(!s_norm.compare("\\\\?\\GLOBALROOT"));
			if(!WeakNormalize(s_norm, "//?/GLOBALROOT/.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\?\\"));
			if(!WeakNormalize(s_norm, "//?/GLOBALROOT/../.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\?\\.."));
			if(!WeakNormalize(s_norm, "//?//../GLOBALROOT"))
				return false;
			_ASSERTE(!s_norm.compare("\\\\?\\..\\GLOBALROOT"));
			if(!WeakNormalize(s_norm, "//?//../GLOBALROOT/.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\?\\.."));
			if(!WeakNormalize(s_norm, "//./c:/foo/bar"))
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!WeakNormalize(s_norm, "//?/c:/foo/bar"))
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!WeakNormalize(s_norm, "//?/UNC//192.168.0.2/foo/bar"))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\foo\\bar"));
			if(!WeakNormalize(s_norm, "//?/UNC/c:/foo/bar"))
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
		}
		// test WeakNormalize

		// todo - debug the following section and write the rest of the unit tests
		// todo - use comparison, not assertion (or use runtime assert)
		{
			std::string s_norm;
			if(!Normalize(s_norm, "c:\\foo\\bar"))
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!Normalize(s_norm, "c:/foo/bar")) // bad slashes
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!Normalize(s_norm, "c:/foo/./bar")) // dot
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!Normalize(s_norm, "c:/foo//bar")) // double slashes
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!Normalize(s_norm, "c://foo//bar")) // more double slashes
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!Normalize(s_norm, "c://foo//bar//")) // even more double slashes
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar\\"));
			if(!Normalize(s_norm, "c:/foo/barbar/../bar")) // double dot
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!Normalize(s_norm, "c:/foo/barbar/../bar/."))
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!Normalize(s_norm, "c://foo//barbar//..//bar//.///")) // more slashes along with "complicated" path
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar\\"));
			if(!Normalize(s_norm, "c:/foo/./barbar/../bar/."))
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!Normalize(s_norm, "./foo/./barbar/../bar/."))
				return false;
			std::string s_cwd;
			if(!Get_CurrentDirectory(s_cwd))
				return false;
			_ASSERTE(!s_norm.compare(s_cwd + "\\foo\\bar"));
			if(!Normalize(s_norm, "c:/../foo/./barbar/../bar/."))
				return false;
			_ASSERTE(!s_norm.compare(s_cwd + "c:\\foo\\bar")); // WeakNormalize doesn't erase double dots where not applicable (such as at drive root), but Normalize does
			if(!Normalize(s_norm, "c:/../foo/./barbar/../../../bar/."))
				return false;
			_ASSERTE(!s_norm.compare("c:\\bar"));
			if(!Normalize(s_norm, "c:/../foo/./barbar/../../../bar/.."))
				return false;
			_ASSERTE(!s_norm.compare("c:\\"));
			if(!Normalize(s_norm, "c:/../../../../.."))
				return false;
			_ASSERTE(!s_norm.compare("c:\\"));
			if(!Normalize(s_norm, ""))
				return false;
			_ASSERTE(!s_norm.compare(""));
			if(!Normalize(s_norm, "."))
				return false;
			_ASSERTE(!s_norm.compare(s_cwd));
			if(!Normalize(s_norm, "c:/.."))
				return false;
			_ASSERTE(!s_norm.compare("c:\\"));
			if(!Normalize(s_norm, "c:/"))
				return false;
			_ASSERTE(!s_norm.compare("c:\\"));
			if(!Normalize(s_norm, "c:/.."))
				return false;
			_ASSERTE(!s_norm.compare("c:\\"));
			if(!Normalize(s_norm, "//192.168.0.2/foo/bar"))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\foo\\bar"));
			if(!Normalize(s_norm, "//192.168.0.2/.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\.."));
			if(!Normalize(s_norm, "//192.168.0.2\\foo\\bar"))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\foo\\bar"));
			if(!Normalize(s_norm, "//192.168.0.2/foo/bar")) // bad slashes
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\foo\\bar"));
			if(!Normalize(s_norm, "//192.168.0.2/foo/./bar")) // dot
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\foo\\bar"));
			if(!Normalize(s_norm, "//192.168.0.2/foo/barbar/../bar")) // double dot
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\foo\\bar"));
			if(!Normalize(s_norm, "//192.168.0.2/foo/barbar/../bar/."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\foo\\bar"));
			if(!Normalize(s_norm, "//192.168.0.2/foo/./barbar/../bar/."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\foo\\bar"));
			if(!Normalize(s_norm, "//192.168.0.2/./foo/./barbar/../bar/."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\foo\\bar"));
			if(!Normalize(s_norm, "//192.168.0.2/../foo/./barbar/../bar/."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\..\\foo\\bar"));
			if(!Normalize(s_norm, "//192.168.0.2/../foo/./barbar/../../../bar/."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\..\\..\\bar"));
			if(!Normalize(s_norm, "//192.168.0.2/../foo/./barbar/../../../bar/.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\..\\.."));
			if(!Normalize(s_norm, "//192.168.0.2/../../../../.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\..\\..\\..\\..\\.."));
			if(!Normalize(s_norm, "//192.168.0.2"))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2"));
			if(!Normalize(s_norm, "//192.168.0.2/."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2"));
			if(!Normalize(s_norm, "//192.168.0.2/.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\.."));
			if(!Normalize(s_norm, "//192.168.0.2/"))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\"));
			if(!Normalize(s_norm, "//./COM1"))
				return false;
			_ASSERTE(!s_norm.compare("\\\\.\\COM1"));
			if(!Normalize(s_norm, "//./COM1/.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\.\\"));
			if(!Normalize(s_norm, "//./COM1/../.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\.\\.."));
			if(!Normalize(s_norm, "//./../COM1/.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\.\\.."));
			if(!Normalize(s_norm, "//./../COM1"))
				return false;
			_ASSERTE(!s_norm.compare("\\\\.\\..\\COM1"));
			if(!Normalize(s_norm, "//?/GLOBALROOT"))
				return false;
			_ASSERTE(!s_norm.compare("\\\\?\\GLOBALROOT"));
			if(!Normalize(s_norm, "//?/GLOBALROOT/.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\?\\"));
			if(!Normalize(s_norm, "//?/GLOBALROOT/../.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\?\\.."));
			if(!Normalize(s_norm, "//?//../GLOBALROOT"))
				return false;
			_ASSERTE(!s_norm.compare("\\\\?\\..\\GLOBALROOT"));
			if(!Normalize(s_norm, "//?//../GLOBALROOT/.."))
				return false;
			_ASSERTE(!s_norm.compare("\\\\?\\.."));
			if(!Normalize(s_norm, "//./c:/foo/bar"))
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!Normalize(s_norm, "//?/c:/foo/bar"))
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
			if(!Normalize(s_norm, "//?/UNC//192.168.0.2/foo/bar"))
				return false;
			_ASSERTE(!s_norm.compare("\\\\192.168.0.2\\foo\\bar"));
			if(!Normalize(s_norm, "//?/UNC/c:/foo/bar"))
				return false;
			_ASSERTE(!s_norm.compare("c:\\foo\\bar"));
		}

		return true;
	}
#endif // 0

	/**
	 *	@brief gets temporary directory the user can write to
	 *	@param[out] r_s_path is the path to the temporary directory, never ends with a slash
	 *	@return Returns true on success, false on failure.
	 */
	static bool Get_TempDirectory(std::string &r_s_path)
	{
#if defined(_WIN32) || defined(_WIN64)
		if(!stl_ut::Resize_To_N(r_s_path, GetTempPath(0, NULL) + 1) ||
		   !GetTempPathA((DWORD)r_s_path.size(), &r_s_path[0])) // size won't exceed DWORD_MAX, since it's read from DWORD
			return false; // something went wrong
		_ASSERTE(strlen(r_s_path.c_str()) > 0 && r_s_path[strlen(r_s_path.c_str()) - 1] == '\\'); // the returned string ends with a backslash
		r_s_path.resize(strlen(r_s_path.c_str()) - 1); // cut the backslash here
		// get temp path (eg. "c:\\windows\\temp")
#else // _WIN32 || _WIN64
#if 0 // g++ linker warns about using mktemp(), don't want to use that anymore
		char p_s_temp[256] = "/tmp/fileXXXXXX";
		if(!mktemp(p_s_temp)) // do *not* use mkstemp(), do not want the file to be lying around
			return false;
		_ASSERTE(strrchr(p_s_temp + 1, '/')); // should contain slash
		*(char*)strrchr(p_s_temp + 1, '/') = 0; // erase the last slash (hence the string does not contain it)
		if(!stl_ut::AssignCStr(r_s_path, p_s_temp))
			return false;
		// get temp file name and erase the file name to get the path
#else // 0
		const char *p_s_temp = getenv("TMPDIR"); // environment variable
		// The caller must take care not to modify this string, since that would change the
		// environment of the process. Do not free it either.
		// (e.g. on cluster computers, temp is often directory specific to the job id, like "/tmp/pbs.132048.dm2")

		if(!p_s_temp) {
			if(P_tmpdir)
				p_s_temp = P_tmpdir; // in stdio.h
			else {
				p_s_temp = "/tmp"; // just hope it is there

				/*TFileInfo t_temp_info(p_s_temp);
				if(!t_temp_info.b_exists || !t_temp_info.b_directory)
					return false;*/
				// don't want to depend on Dir.cpp, some apps already use only the header
			}
		}
		// fallbacks if the environment variable is not set

		if(!stl_ut::AssignCStr(r_s_path, p_s_temp))
			return false;
		if(!r_s_path.empty() && r_s_path[r_s_path.length() - 1] == '/')
			r_s_path.erase(r_s_path.end() - 1);
		// get rid of the trailing slash
#endif // 0
#endif // _WIN32 || _WIN64

		_ASSERTE(!b_EndsWithSlash(r_s_path));
		_ASSERTE(b_Is_Normalized(r_s_path));
		_ASSERTE(b_Is_Absolute(r_s_path));
		// make sure there is no slash at the end, and that the path is normalized

		return true;
	}

	/**
	 *	@brief gets the current working directory
	 *	@param[out] r_s_path is the path to the current working directory, never ends with a slash
	 *	@return Returns true on success, false on failure.
	 */
	static bool Get_CurrentDirectory(std::string &r_s_path)
	{
		if(!stl_ut::Resize_To_N(r_s_path, 8))
			return false;
		for(;;) {
#if defined(_WIN32) || defined(_WIN64)
			_ASSERTE(r_s_path.size() - 1 <= INT_MAX); 
			if(_getcwd(&r_s_path[0], int(r_s_path.size() - 1))) {
#else // _WIN32 || _WIN64
			if(getcwd(&r_s_path[0], r_s_path.size() - 1)) { // different name, same function
#endif // _WIN32 || _WIN64
				r_s_path.resize(strlen(r_s_path.c_str()));
				break;
			}
			// in case the buffer is large enough, trim it

			if(!stl_ut::Resize_To_N(r_s_path, r_s_path.size() * 2))
				return false;
			// grow the buffer
		}

		Drop_TrailingSlash(r_s_path);
		_ASSERTE(b_Is_Normalized(r_s_path));
		_ASSERTE(b_Is_Absolute(r_s_path));
		// make sure there is no slash at the end, and that the path is normalized

		return true;
	}

#ifdef __CPATH_GET_USER_HOME_DIRECTORY

	/**
	 *	@brief gets the current logged-on user home directory
	 *	@param[out] r_s_path is the path to the user home directory, never ends with a slash
	 *	@return Returns true on success, false on failure.
	 */
	static bool Get_UserHomeDirectory(std::string &r_s_path)
	{
#if defined(_WIN32) || defined(_WIN64)
		HANDLE h_process = GetCurrentProcess(); // = -1
		HANDLE h_process_real = h_process;
		/*if(!DuplicateHandle(h_process, h_process, h_process,
		   &h_process_real, PROCESS_QUERY_INFORMATION, TRUE, 0))
			return false;*/
		HANDLE h_proc_token;
		if(!OpenProcessToken(h_process, TOKEN_QUERY, &h_proc_token)) {
			//CloseHandle(h_process_real);
			return false;
		}
		// note the handle returned by GetCurrentProcess() doesn't need to be closed

		DWORD n_size = MAX_PATH;
		GetUserProfileDirectoryA(h_proc_token, NULL, &n_size); // fails, but that's ok
		// get size

		if(!stl_ut::Resize_To_N(r_s_path, n_size + 1) ||
		   !GetUserProfileDirectoryA(h_proc_token, &r_s_path[0], &n_size)) {
			CloseHandle(h_proc_token);
			//CloseHandle(h_process_real);
			return false;
		}
		//CloseHandle(h_process_real);
		CloseHandle(h_proc_token);
		// get the directory

		r_s_path.resize(strlen(r_s_path.c_str()));
		// trim it

		_ASSERTE(!b_EndsWithSlash(r_s_path)); // shouldn't have on windows
#else // _WIN32 || _WIN64
		struct passwd *p_pw = getpwuid(getuid());
		if(!p_pw || !stl_ut::Assign_CStr(r_s_path, p_pw->pw_dir))
			return false;
#endif // _WIN32 || _WIN64

		Drop_TrailingSlash(r_s_path);
		_ASSERTE(b_Is_Normalized(r_s_path));
		_ASSERTE(b_Is_Absolute(r_s_path));
		// make sure there is no slash at the end, and that the path is normalized

		return true;
	}

#endif // __CPATH_GET_USER_HOME_DIRECTORY

	/**
	 *	@brief normalizes a path (removes unnecessary dots and slashes, changes slashes
	 *		to the OS convention, removes symlinks and drive links and similar)
	 *
	 *	@param[in,out] r_s_path is the normalized path to a directory, or a file
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note This should only be relied on in simple cases, not involving symlinks and similar.
	 *	@note This has a lot of problems. On windows, this function is not a standard API,
	 *		and needs to be implemented by hand, with some quirks involved. On linux, this uses
	 *		the realpath() function, which may have some memory allocation issues (will
	 *		either always work, or always crash).
	 *
	 *	@todo Try to find perl path source code and see what they use.
	 */
	static bool Normalize(std::string &r_s_path)
	{
#if defined(_WIN32) || defined(_WIN64)
		return Canonicalize(r_s_path);
#else // _WIN32 || _WIN64
		std::replace(r_s_path.begin(), r_s_path.end(), '\\', '/');
		// replace back slashes by forward slashes

		char *p_s_path;
		if(!(p_s_path = realpath(r_s_path.c_str(), 0)))
			return false;
		// cannonicalize using realpath. problems follow:

		/*
			Never use this function. It is broken by design since it
			is impossible to determine a suitable size for the output
			buffer. According to POSIX a buffer of size PATH_MAX suf-
			fices, but PATH_MAX need not be a defined constant, and
			may have to be obtained using pathconf(). And asking
			pathconf() does not really help, since on the one hand
			POSIX warns that the result of pathconf() may be huge and
			unsuitable for mallocing memory. And on the other hand
			pathconf() may return -1 to signify that PATH_MAX is not
			bounded.

			The libc4 and libc5 implementation contains a buffer over-
			flow (fixed in libc-5.4.13). Thus, suid programs like
			mount need a private version.
		 */

		// note this could be solved using get / set cwd, but that
		// would require the path to exist and to be accessible

		bool b_result = stl_ut::AssignCStr(r_s_path, p_s_path);
		delete[] p_s_path;
		return b_result;
		// return result
#endif // _WIN32 || _WIN64
	}

	static inline bool Normalize(std::string &r_s_dest, const std::string &r_s_path)
	{
		return stl_ut::Assign(r_s_dest, r_s_path) && Normalize(r_s_dest);
	}

	static inline bool Normalize(std::string &r_s_dest, const char *p_s_path)
	{
		return stl_ut::AssignCStr(r_s_dest, p_s_path) && Normalize(r_s_dest);
	}

	/**
	 *	@brief collapses redundant separators and up-level references
	 *
	 *	A simpler version of Normalize(), intended for easy cases.
	 *	Does not resolve drive letter substitutions, ignores symlinks.
	 *
	 *	@param[in,out] r_s_path is the normalized path to a directory, or a file
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note This should *not* be called from inside any ÜberLame general
	 *		purpose function, the caller needs to know that the paths
	 *		will not contain any entities this function might hamper.
	 */
	static bool WeakNormalize(std::string &r_s_path)
	{
#if defined(_WIN32) || defined(_WIN64)
		std::replace(r_s_path.begin(), r_s_path.end(), '/', '\\');
#else // _WIN32 || _WIN64
		std::replace(r_s_path.begin(), r_s_path.end(), '\\', '/');
#endif // _WIN32 || _WIN64
		// replace slashes by the correct ones

#if defined(_WIN32) || defined(_WIN64)
		if(r_s_path.length() >= 6 && isalpha(uint8_t(r_s_path[4]))) {
			if(!strncmp(r_s_path.c_str(), "\\\\?\\", 4) && r_s_path[5] == ':')
				r_s_path.erase(0, 4);
			else if(!strncmp(r_s_path.c_str(), "\\\\.\\", 4) && r_s_path[5] == ':')
				r_s_path.erase(0, 4);
		}
		// get rid of \\?\ and \\.\ prefixes on drive-letter paths

		if(r_s_path.length() >= 11) {
			if(!strncmp(r_s_path.c_str(), "\\\\?\\UNC\\", 8)) {
				if(isalpha(uint8_t(r_s_path[8])) && r_s_path[9] == ':' && r_s_path[10] == '\\')
					r_s_path.erase(0, 8);
				else if(r_s_path[8] == '\\')
					r_s_path.erase(0, 7);
			}
		}
		// get rid of \\?\UNC on drive-letter and UNC paths
#endif // _WIN32 || _WIN64
		// some path simplifications for windows

#if defined(_WIN32) || defined(_WIN64)
		bool b_uses_namespace = r_s_path.length() >= 4 && r_s_path[0] == '\\' && r_s_path[1] == '\\' &&
			(r_s_path[2] == '.' || r_s_path[2] == '?') && r_s_path[3] == '\\';
		char c_namespace_char = (b_uses_namespace)? r_s_path[2] : 0;
		if(b_uses_namespace)
			r_s_path.erase(0, 4);
#endif // _WIN32 || _WIN64
		// windows use "\\?\" and "\\.\" to denote namespaces, get rid of that

		std::vector<std::string> segment_list;
		const char p_s_separator[] = {path_Separator, 0};
		if(!stl_ut::Split(segment_list, r_s_path, p_s_separator))
			return false;
		// split the path to segments (a pussy way to do it, but less messy)

		try {
#if defined(_WIN32) || defined(_WIN64)
			std::string s_head;
			if(segment_list.size() >= 3 && segment_list[0].empty() && segment_list[1].empty() &&
			   !segment_list[2].empty()) {
				s_head = "\\\\" + segment_list[2];
				segment_list.erase(segment_list.begin(), segment_list.begin() + 3);
				// in case of "\\server\" paths, erase the server
			} else if(!segment_list.empty() && segment_list[0].length() == 2 &&
			   segment_list[0][1] == ':' && isalpha(uint8_t(segment_list[0][0]))) {
				s_head.swap(segment_list[0]);
				segment_list.erase(segment_list.begin());
				// in case of "c:\" paths, erase the drive specifier
			}
#endif // _WIN32 || _WIN64

#if defined(_WIN32) || defined(_WIN64)
			for(size_t i = segment_list.size(); i > 0; -- i) {
#else // _WIN32 || _WIN64
			for(size_t i = segment_list.size(); i > 1; -- i) {
				// linux absolute addresses start with "/", hence first empty segment should be ignored
#endif // _WIN32 || _WIN64
				if(segment_list[i - 1].empty())
					segment_list.erase(segment_list.begin() + (i - 1));
			}
			// in case there is a double slash, replace by a single one

			for(size_t i = segment_list.size(); i > 0; -- i) {
				if(!segment_list[i - 1].compare("."))
					segment_list.erase(segment_list.begin() + (i - 1));
			}
			// drop dots

			for(size_t i = 0, n = segment_list.size(); i < n; ++ i) {
				if(!segment_list[i].compare("..")) {
					if(i > 0 && segment_list[i - 1].compare("..")) { // is there somewhere to go up to?
						segment_list.erase(segment_list.begin() + (i - 1), segment_list.begin() + (i + 1));
						i -= 2; // repeat ...
						n -= 2; // !!
					}
				}
			}
			// drop double dots

#if defined(_WIN32) || defined(_WIN64)
			if(b_uses_namespace) {
				r_s_path = "\\\\";
				r_s_path += c_namespace_char;
				r_s_path += '\\';
				// begin with namespace declaration ("\\?\" or "\\.\")
			} else
				r_s_path.erase();
			if(!s_head.empty()) {
				r_s_path += s_head; // prepend share or drive specification
				if(!segment_list.empty())
					r_s_path += path_Separator;
			}
#else // _WIN32 || _WIN64
			r_s_path.erase();
#endif // _WIN32 || _WIN64
			// clear output string

			for(size_t i = 0, n = segment_list.size(); i < n; ++ i) {
				if(i)
					r_s_path += path_Separator;
				r_s_path += segment_list[i];
			}
		} catch(std::bad_alloc&) {
			return false;
		}

		return true;
	}

	static inline bool WeakNormalize(std::string &r_s_dest, const std::string &r_s_path)
	{
		return stl_ut::Assign(r_s_dest, r_s_path) && WeakNormalize(r_s_dest);
	}

	static inline bool WeakNormalize(std::string &r_s_dest, const char *p_s_path)
	{
		return stl_ut::AssignCStr(r_s_dest, p_s_path) && WeakNormalize(r_s_dest);
	}

	static bool Join(std::string &r_s_dest, const std::string &r_s_head, const std::string &r_s_tail)
	{
		if(b_Is_Absolute(r_s_tail))
			return &r_s_dest == &r_s_tail || stl_ut::Assign(r_s_dest, r_s_tail); // tail is dest, or overwrite dest with tail
		// in case tail is an absolute path, discard head

		if(&r_s_dest == &r_s_tail) {
			std::string s_temp_tail;
			return stl_ut::Assign(s_temp_tail, r_s_tail) && Join(r_s_dest, r_s_head, s_temp_tail);
		}
		_ASSERTE(&r_s_dest != &r_s_tail);
		// in case destination string is tail, need to copy it to temp storage first

		const char p_s_slash[] = {path_Separator, 0};
		return (&r_s_head == &r_s_dest || stl_ut::Assign(r_s_dest, r_s_head)) && // head is dest or copy head to dest
			(b_EndsWithSlash(r_s_head) || stl_ut::AppendCStr(r_s_dest, p_s_slash)) && // head contains slash or append slash
			stl_ut::Append(r_s_dest, r_s_tail); // copy tail
		// note this path may be not normalized (contain ".." or "." or double slashes)
	}

	static bool Join(std::string &r_s_dest, const char *p_s_head, const char *p_s_tail)
	{
		if(b_Is_Absolute(p_s_tail))
			return r_s_dest.c_str() == p_s_tail || stl_ut::AssignCStr(r_s_dest, p_s_tail); // tail is dest, or overwrite dest with tail
		// in case tail is an absolute path, discard head

		if(r_s_dest.c_str() == p_s_tail) {
			std::string s_temp_tail;
			return stl_ut::AssignCStr(s_temp_tail, p_s_tail) &&
				Join(r_s_dest, p_s_head, s_temp_tail.c_str());
		}
		_ASSERTE(r_s_dest.c_str() != p_s_tail);
		// in case destination string is tail, need to copy it to temp storage first

		const char p_s_slash[] = {path_Separator, 0};
		return (p_s_head == r_s_dest.c_str() || stl_ut::AssignCStr(r_s_dest, p_s_head)) && // head is dest or copy head to dest
			(b_EndsWithSlash(p_s_head) || stl_ut::AppendCStr(r_s_dest, p_s_slash)) && // head contains slash or append slash
			stl_ut::AppendCStr(r_s_dest, p_s_tail); // copy tail
		// note this path may be not normalized (contain ".." or "." or double slashes)
	}

	static inline bool Join(std::string &r_s_head_dest, const std::string &r_s_tail)
	{
		return Join(r_s_head_dest, r_s_head_dest, r_s_tail);
	}

	static inline bool Join(std::string &r_s_head_dest, const char *p_s_tail)
	{
		return Join(r_s_head_dest, r_s_head_dest.c_str(), p_s_tail);
	}

	static inline bool To_Relative(std::string &r_s_path)
	{
		std::string s_cwd;
		return Get_CurrentDirectory(s_cwd) && To_Relative(r_s_path, s_cwd);
	}

	static bool To_Relative(std::string &r_s_path, const std::string &r_s_current_dir)
	{
		if(!b_Is_Absolute(r_s_current_dir)) {
			std::string s_abs_current;
			return To_Absolute(s_abs_current, r_s_current_dir) && To_Relative(r_s_path, s_abs_current);
		}
		_ASSERTE(b_Is_Absolute(r_s_current_dir));
		// make sure the current directory is absolute

		if(!b_Is_Absolute(r_s_path) && !To_Absolute(r_s_path))
			return false;
		// make sure the path to convert to relative is also absolute

		_ASSERTE(0); // not implemented
		// todo - match the head of the absolute path with current directory,
		// skip matched parts, for every unmatched of r_s_current_dir, there's one double dot,
		// copy the rest of the path after that
		// note r_s_current_dir or r_s_path may contain '.' or '..' or '//' themselves

		return true;
	}

	static bool To_Absolute(std::string &r_s_path)
	{
		if(b_Is_Absolute(r_s_path))
			return true;
		// in case it's absolute, do nothing

		std::string s_cwd;
		return Get_CurrentDirectory(s_cwd) && Join(r_s_path, s_cwd, r_s_path);
		// in case it's relative, join with current directory
		// note this path may be not normalized (contain ".." or "." or double slashes)
	}

	static bool To_Absolute(std::string &r_s_dest, const std::string &r_s_path)
	{
		return (&r_s_dest == &r_s_path || stl_ut::Assign(r_s_dest, r_s_path)) && To_Absolute(r_s_dest);
	}

	static bool Get_Path(std::string &r_s_path, const std::string &r_s_filename)
	{
		size_t n_pos = r_s_filename.find_last_of("\\/");

		if(n_pos == std::string::npos) {
			r_s_path.erase();
			return true;
		}
		// no slash, the whole string is filename (no path)

		++ n_pos;
		// keep slash

		if(&r_s_path == &r_s_filename)
			r_s_path.erase(n_pos);
		else {
			try {
				r_s_path.erase();
				r_s_path.insert(r_s_path.begin(), r_s_filename.begin(), r_s_filename.begin() + n_pos);
			} catch(std::bad_alloc&) {
				return false;
			}
		}
		return true;
	}

	static inline bool Get_Path(std::string &r_s_path)
	{
		return Get_Path(r_s_path, r_s_path);
	}

	static bool Get_Filename(std::string &r_s_name, const std::string &r_s_filename)
	{
		size_t n_pos = r_s_filename.find_last_of("\\/");

		if(n_pos == std::string::npos || n_pos == r_s_filename.length() - 1) { // fixme
			r_s_name.erase();
			return true;
		}
		// no slash, the whole string is filename

		++ n_pos;
		// delete the slash

		if(&r_s_name == &r_s_filename)
			r_s_name.erase(0, n_pos);
		else {
			try {
				r_s_name.erase();
				r_s_name.insert(r_s_name.begin(), r_s_filename.begin() + n_pos, r_s_filename.end());
			} catch(std::bad_alloc&) {
				return false;
			}
		}

		return true;
	}

	static inline bool Get_Filename(std::string &r_s_path)
	{
		return Get_Filename(r_s_path, r_s_path);
	}

	/**
	 *	@brief gets extension from a file, pointed to in a filename
	 *
	 *	@param[out] r_s_extension is filled with the extension (including the dot),
	 *		or left empty in case there is no extension
	 *	@param[in] r_s_filename is path to a file, or just a path
	 *
	 *	@return Returns true on success, false on failure.
	 */
	static bool Get_Extension(std::string &r_s_extension, const std::string &r_s_filename)
	{
		size_t n_pos = r_s_filename.find_last_of("\\/");
		if(n_pos == std::string::npos)
			n_pos = 0; // no slash, the whole string is a filename
		else if(n_pos == r_s_filename.length() - 1) { // fixme
			r_s_extension.erase(); // slash at the end, the whole string is path (no filename, no extension)
			return true;
		}
		size_t n_pos2 = r_s_filename.rfind('.');
		if(n_pos2 == std::string::npos || n_pos2 < n_pos) {
			r_s_extension.erase(); // dot not found, or found before slash
			return true;
		}
		// find the position of the dot

		if(&r_s_extension == &r_s_filename)
			r_s_extension.erase(0, n_pos2);
		else {
			try {
				r_s_extension.erase();
				r_s_extension.insert(r_s_extension.begin(), r_s_filename.begin() + n_pos2, r_s_filename.end());
			} catch(std::bad_alloc&) {
				return false;
			}
		}

		return true;
	}

	static inline bool Get_Extension(std::string &r_s_path)
	{
		return Get_Extension(r_s_path, r_s_path);
	}

	static bool Split(std::string &r_s_path, std::string &r_s_name, const std::string &r_s_filename)
	{
		_ASSERTE(&r_s_path != &r_s_name); // make sure those two do not point to the same string

		size_t n_pos = r_s_filename.find_last_of("\\/");

		if(n_pos == std::string::npos) {
			if(&r_s_filename != &r_s_name &&
			   !stl_ut::Assign(r_s_name, r_s_filename))
				return false;
			r_s_path.erase();
			// no separator, the whole path is a filename then

			return true;
		}

		if(&r_s_path != &r_s_filename) {
			try {
				r_s_path.erase();
				r_s_path.insert(r_s_path.begin(), r_s_filename.begin(),
					r_s_filename.begin() + (n_pos + 1));
			} catch(std::bad_alloc&) {
				return false;
			}
		}
		if(&r_s_name != &r_s_filename) {
			try {
				r_s_name.erase();
				r_s_name.insert(r_s_name.begin(), r_s_filename.begin() +
					(n_pos + 1), r_s_filename.end());
			} catch(std::bad_alloc&) {
				return false;
			}
		}
		if(&r_s_path == &r_s_filename)
			r_s_path.erase(n_pos + 1);
		if(&r_s_name == &r_s_filename)
			r_s_name.erase(0, n_pos + 1);
		// split the path, mind that some strings may share storage

		return true;
	}

	static inline bool Split(std::string &r_s_path_name, std::string &r_s_name)
	{
		return Split(r_s_path_name, r_s_name, r_s_path_name);
	}

	static bool Expand_SystemVariables(std::string &r_s_dest, const std::string &r_s_path)
	{
		_ASSERTE(0); // todo
#if defined(_WIN32) || defined(_WIN64)
#else // _WIN32 || _WIN64
#endif // _WIN32 || _WIN64

		return true;
	}

	static inline bool b_Exists(const std::string &r_s_path)
	{
		TFileInfo t(r_s_path.c_str());
		_ASSERTE(t.b_Valid()); // might not be
		return t.b_exists;
	}

	static inline TFileInfo t_GetInfo(const std::string &r_s_path)
	{
		return TFileInfo(r_s_path.c_str());
	}

	static bool b_Is_Absolute(const std::string &r_s_path)
	{
#if defined(_WIN32) || defined(_WIN64)
		return (r_s_path.length() >= 2 && (r_s_path[0] == '/' || r_s_path[0] == '\\') &&
			(r_s_path[1] == '/' || r_s_path[1] == '\\')) ||
			// double backslash marks UNC address

			(r_s_path.length() >= 3 && isalpha(uint8_t(r_s_path[0])) && r_s_path[1] == ':' &&
			(r_s_path[2] == '/' || r_s_path[2] == '\\')) ||
			// disk designator

			false;// (r_s_path.length() >= 1 && (r_s_path[0] == '/' || r_s_path[0] == '\\'));
			// single backslash (wtf?) // todo - check this
#else // _WIN32 || _WIN64
		return r_s_path.length() >= 1 && r_s_path[0] == '/'; // and that should be it
#endif // _WIN32 || _WIN64
	}

	/**
	 *	@brief normalize the case of a pathname
	 *
	 *	On Unix-based platforms, this leaves the path unchanged.
	 *	On Windows filesystems, it converts the path to lowercase,
	 *	it also converts forward slashes to backward slashes.
	 *
	 *
	 *	@return Returns true on success, false on failure.
	 */
	static bool Normalize_Case(std::string &r_s_dest, const std::string &r_s_path)
	{
		if(&r_s_dest != &r_s_path && !stl_ut::Assign(r_s_dest, r_s_path))
			return false;
		// copy the path, if needed

#if defined(_WIN32) || defined(_WIN64)
		std::replace(r_s_dest.begin(), r_s_dest.end(), '/', '\\');
		// replace slashes for backslashes

		size_t n_begin = 0;
		if(r_s_dest.length() >= 2 && isupper(uint8_t(r_s_dest[0])) && r_s_dest[1] == ':') {
			r_s_dest[0] = tolower(uint8_t(r_s_dest[0]));
			n_begin = 2;
		}
		// in case there is drive letter, make it lowercase

		if(r_s_dest.length() >= 6 && isupper(uint8_t(r_s_dest[4]))) {
			if((!strncmp(r_s_dest.c_str(), "\\\\?\\", 4) && r_s_dest[5] == ':') ||
			   (!strncmp(r_s_dest.c_str(), "\\\\.\\", 4) && r_s_dest[5] == ':')) {
				r_s_dest[4] = tolower(uint8_t(r_s_dest[4]));
				n_begin = 6;
			}
		}
		// in case there is drive letter, make it lowercase (drive letter with "\\?\" or "\\.\" prefix)

		if(r_s_dest.length() >= 11) {
			if(!strncmp(r_s_dest.c_str(), "\\\\?\\", 4) &&
			   toupper(uint8_t(r_s_dest[4])) == 'U' &&
			   toupper(uint8_t(r_s_dest[5])) == 'N' &&
			   toupper(uint8_t(r_s_dest[6])) == 'C' && r_s_dest[7] == '\\') {
			    r_s_dest[4] = 'U';
			    r_s_dest[5] = 'N';
			    r_s_dest[6] = 'C';
				// makes sure 'UNC' is uppercase

				if(isalpha(uint8_t(r_s_dest[8])) && r_s_dest[9] == ':' && r_s_dest[10] == '\\') {
					if(isupper(uint8_t(r_s_dest[8])))
						r_s_dest[8] = tolower(uint8_t(r_s_dest[8]));
					n_begin = 11;
				} else
					n_begin = 7;
				// in case there is drive letter, make it lowercase
			}
		}
		// handle \\?\UNC on drive-letter

		for(size_t i = n_begin, n = r_s_dest.length(); i < n; ++ i)
			r_s_dest[i] = tolower(uint8_t(r_s_dest[i]));
		// transform the rest of the string to lowercase
#else // _WIN32 || _WIN64
		// nothing to be done here ...
#endif // _WIN32 || _WIN64

		return true;
	}

	/**
	 *	@brief makes a short filename for verbose, by skipping a prefix
	 *
	 *	@param[in] p_s_filename is file name
	 *	@param[in] n_max_length is maximum output length
	 *
	 *	@return Returns a pair of prefix and filename suffix.
	 */
	static std::pair<const char*, const char*> t_ShortFileName(const char *p_s_filename, size_t n_max_length = 80)
	{
		_ASSERTE(p_s_filename);
		_ASSERTE(n_max_length > 7);
		size_t n_len;
		if((n_len = strlen(p_s_filename)) > n_max_length) {
			const char *p_s_end = p_s_filename + n_len;
			const char *p_s_shorter = p_s_filename;
			do {
#if defined(_WIN32) || defined(_WIN64)
				const char *p_s_pattern = "/\\";
				const char *p_s_slash = std::find_first_of(p_s_shorter,
					p_s_end, p_s_pattern, p_s_pattern + 2);
				// windows accept both kinds of slashes, making it messy
#else // _WIN32 || _WIN64
				const char *p_s_slash = std::find(p_s_shorter, p_s_end, (char)CPath::path_Separator);
#endif // _WIN32 || _WIN64
				if(p_s_slash != p_s_end)
					p_s_shorter = p_s_slash + 1;
				else
					break;
				_ASSERTE(p_s_end > p_s_shorter); // will never produce an empty string
			} while(size_t(p_s_end - p_s_shorter) > n_max_length - 4);
			_ASSERTE(p_s_end > p_s_shorter); // will never produce an empty string
			if(size_t(p_s_end - p_s_shorter) <= n_max_length - 4)
				return std::make_pair((const char*)(CPath::path_Separator == '\\')? "...\\" : ".../", p_s_shorter);
			else
				return std::make_pair((const char*)"...", p_s_shorter + (p_s_end - p_s_shorter - n_max_length + 7));
		} else
			return std::make_pair((const char*)"", p_s_filename);
		// MSVC 60 needs (const char*) before the constant string, otherwise it instantiates std::pair<char[4], char*> 
	}

	static bool Make_Directory(const std::string &r_s_path);
	static bool Make_Directories(const std::string &r_s_path);
	static bool Remove_Directory(const std::string &r_s_path);
	static bool Remove_DirTree(const std::string &r_s_path);
	static bool Remove_File(const std::string &r_s_filename);
	static bool Move_File(const std::string &r_s_filename);
	static bool Copy_File(const std::string &r_s_filename); // todo - implement those

	static FILE *p_OpenFile(const char *p_s_filename, const char *p_s_mode)
	{
#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER >= 1400
		FILE *p_file;
		if(!fopen_s(&p_file, p_s_filename, p_s_mode))
			return p_file;
		return 0; // fail
#else //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
		return fopen(p_s_filename, p_s_mode);
#endif //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
	}

	static inline FILE *p_OpenFile(const std::string &r_s_filename, const char *p_s_mode)
	{
		return p_OpenFile(r_s_filename.c_str(), p_s_mode);
	}

	static inline FILE *p_OpenFile(const std::string &r_s_filename, std::string &r_s_mode)
	{
		return p_OpenFile(r_s_filename.c_str(), r_s_mode.c_str());
	}

	static bool b_EndsWithSlash(const std::string &r_s_path)
	{
		if(r_s_path.empty())
			return false;
		char n_slash = r_s_path[r_s_path.length() - 1];
		return n_slash == '/' || n_slash == '\\';
	}

	static bool b_EndsWithSlash(const char *p_s_path)
	{
		if(!*p_s_path)
			return false;
		char n_slash = p_s_path[strlen(p_s_path) - 1];
		return n_slash == '/' || n_slash == '\\'; // todo - modify all the functions - in linux, a backslash shouldn't slip by
	}

	//static bool b_Is_Normalized(const std::string &r_s_path); // hard to determine; rename to b_is_sane or something like that
	//static bool b_Is_Normalized(const char *p_s_path); // todo - decide what that should do (find bad characters? find the opposite slashes? find dots and double dots?)
	static bool b_Is_Normalized(const std::string &UNUSED(r_s_path))
	{	return true;	}

	static void Drop_TrailingSlash(std::string &r_s_path)
	{
		if(r_s_path.empty())
			return;
		char &r_n_back = r_s_path[r_s_path.length() - 1];
		while(r_n_back == '/' || r_n_back == '\\') // there might be more of them
			r_s_path.erase(r_s_path.end() - 1);
	}

protected:
#if defined(_WIN32) || defined(_WIN64)

	static bool Canonicalize(std::string &r_s_path)
	{
		// this function is a copy of the one, described at:
		// http://pdh11.blogspot.cz/2009/05/pathcanonicalize-versus-what-it-says-on.html

		char p_s_canonical[MAX_PATH];
		if(!::GetFullPathNameA(r_s_path.c_str(), MAX_PATH, p_s_canonical, NULL))
			return false;
		// note that PathCanonicalize does NOT do what we want here, it's a
		// purely textual operation that eliminates /./ and /../ only

		std::string s_new_path = p_s_canonical;
		if(s_new_path.length() >= 6 && isalpha(uint8_t(s_new_path[4]))) {
			if(!strncmp(s_new_path.c_str(), "\\\\?\\", 4) && s_new_path[5] == ':')
				s_new_path.erase(0, 4);
			else if(!strncmp(s_new_path.c_str(), "\\\\.\\", 4) && s_new_path[5] == ':')
				s_new_path.erase(0, 4);
		}
		// get rid of \\?\ and \\.\ prefixes on drive-letter paths

		if(s_new_path.length() >= 11) {
			if(!strncmp(s_new_path.c_str(), "\\\\?\\UNC\\", 8)) {
				if(isalpha(uint8_t(s_new_path[8])) && s_new_path[9] == ':' && s_new_path[10] == '\\')
					s_new_path.erase(0, 8);
				else if(s_new_path[8] == '\\')
					s_new_path.erase(0, 7);
			}
		}
		// get rid of \\?\UNC on drive-letter and UNC paths

		// anything other than UNC and drive-letter is something we don't understand
		
		if(s_new_path.length() >= 2) {
			if(s_new_path[0] == '\\' && s_new_path[1] == '\\') {
				if(s_new_path[2] == '?' || s_new_path[2] == '.')
					return true; // not understood
				// it's UNC
			} else if(isalpha(uint8_t(s_new_path[0])) && s_new_path[1] == ':') {
				// it's a drive letter, need to potentially unwind substituted letters
				for (;;) {
					char p_s_drive[3];
					p_s_drive[0] = (char)toupper(uint8_t(s_new_path[0]));
					p_s_drive[1] = ':';
					p_s_drive[2] = 0;
					p_s_canonical[0] = 0;
					if(!::QueryDosDeviceA(p_s_drive, p_s_canonical, MAX_PATH))
						break;
					if(!strncmp(p_s_canonical, "\\??\\", 4)) {
						s_new_path = std::string(p_s_canonical + 4) +
							std::string(s_new_path.begin(), s_new_path.begin() + 2);
					} else {
						s_new_path[0] = (char)tolower(uint8_t(s_new_path[0])); // make sure a lowercase letter is used
						break; // not substituted, done
					}
				}

				char p_s_drive[4];
				p_s_drive[0] = (char)toupper(uint8_t(s_new_path[0]));
				p_s_drive[1] = ':';
				p_s_drive[2] = '\\';
				p_s_drive[3] = 0;

				if(::GetDriveTypeA(p_s_drive) == DRIVE_REMOTE) {
					p_s_drive[2] = '\0';
					// QueryDosDevice() and WNetGetConnection() forbid the
					// trailing slash; GetDriveType() requires it

#ifdef __CPATH_NORMALIZE_NETWORK_DRIVE_SUPPORT
					DWORD bufsize = MAX_PATH;
					if(::WNetGetConnectionA(p_s_drive, p_s_canonical, &bufsize) == NO_ERROR)
						s_new_path = std::string(p_s_canonical) + std::string(s_new_path.begin(), s_new_path.begin() + 2);
#else // __CPATH_NORMALIZE_NETWORK_DRIVE_SUPPORT
					break; // can't substitute
#endif // __CPATH_NORMALIZE_NETWORK_DRIVE_SUPPORT
				}
			} else {
				return true;
				// not understood
			}
		} else {
			return true;
			// not understood
		}

		if(::GetLongPathNameA(s_new_path.c_str(), p_s_canonical, MAX_PATH)) // will fail on nonexistent paths
			r_s_path = p_s_canonical; // copy back to dest
		else
			r_s_path.swap(s_new_path); // otherwise use the path as is (possibly with 8.3 names)
		// remove 8.3 names

		std::replace(r_s_path.begin(), r_s_path.end(), '/', '\\');
		// replace forward slashes by backslashes

		return true;
	}

#endif // _WIN32 || _WIN64
};

#endif // 1

template <class CFileListener>
bool CDirTraversal::Traverse(const char *p_s_dir_name,
	CFileListener &r_file_listener, bool b_recurse_subdirectories /*= true*/)
{
	if(CPath::b_EndsWithSlash(p_s_dir_name)) {
		std::string s_slashless;
		if(!stl_ut::AssignCStr(s_slashless, p_s_dir_name))
			return false;
		CPath::Drop_TrailingSlash(s_slashless);
		TFileInfo info(s_slashless);
		if(!info.b_Valid() || !info.b_exists || !info.b_directory)
			return false;
	} else {
		TFileInfo info(p_s_dir_name);
		if(!info.b_Valid() || !info.b_exists || !info.b_directory)
			return false;
	}
	// explicitly handle bad paths

	CDirectory *p_dir_info;
	if(!(p_dir_info = new(std::nothrow) CDirectory(p_s_dir_name)))
		return false;
	// prepare the first directory handle

	std::vector<CDirectory*> m_dir_queue;
	stl_ut::Reserve_N(m_dir_queue, 16);
	if(!m_dir_queue.capacity())
		return false;
	m_dir_queue.push_back(p_dir_info);
	// put it into the queue

	while(!m_dir_queue.empty()) {
		CDirectory *p_dir = m_dir_queue.back();
		m_dir_queue.erase(m_dir_queue.end() - 1);
		// fetch a single record from top of the queue

		size_t n_pos = m_dir_queue.size();

		TFileInfo t_file_info;
		if(!p_dir->Get_FirstFile(t_file_info)) {
			std::for_each(m_dir_queue.begin(), m_dir_queue.end(), DeleteDir);
			delete p_dir;
			return false; // t_odo - free the pair data
		}
		while(/*t_file_info.b_valid &&*/ t_file_info.b_exists) {
			_ASSERTE(t_file_info.b_Valid());
			r_file_listener((const TFileInfo&)t_file_info);
			// pass file data to listener
		
			if(b_recurse_subdirectories && t_file_info.b_directory) {
				CDirectory *p_subdir_info;
				if(!stl_ut::Reserve_1More(m_dir_queue) ||
				   !(p_subdir_info = new(std::nothrow) CDirectory(t_file_info.p_s_Path()))) {
					std::for_each(m_dir_queue.begin(), m_dir_queue.end(), DeleteDir);
					delete p_dir;
					return false;
				}
				// make sure there's enough space in the queue,
				// prepare the subdirectory handle

				m_dir_queue.insert(m_dir_queue.begin() + n_pos, p_subdir_info);
			}
			// add the directory to the list to recurse

			if(!p_dir->Get_NextFile(t_file_info)) {
				std::for_each(m_dir_queue.begin(), m_dir_queue.end(), DeleteDir);
				delete p_dir;
				return false;
			}
			// get next file info
		}
		delete p_dir;
	}

	return true;
}

template <class CFileListener>
bool CDirTraversal::Traverse2(const char *p_s_dir_name,
	CFileListener &r_file_listener, bool b_recurse_subdirectories /*= true*/)
{
	if(CPath::b_EndsWithSlash(p_s_dir_name)) {
		std::string s_slashless;
		if(!stl_ut::AssignCStr(s_slashless, p_s_dir_name))
			return false;
		CPath::Drop_TrailingSlash(s_slashless);
		TFileInfo info(s_slashless);
		if(!info.b_Valid() || !info.b_exists || !info.b_directory)
			return false;
	} else {
		TFileInfo info(p_s_dir_name);
		if(!info.b_Valid() || !info.b_exists || !info.b_directory)
			return false;
	}
	// explicitly handle bad paths

	CDirectory *p_dir_info;
	if(!(p_dir_info = new(std::nothrow) CDirectory(p_s_dir_name)))
		return false;
	// prepare the first directory handle

	std::vector<CDirectory*> m_dir_queue;
	stl_ut::Reserve_N(m_dir_queue, 16);
	if(!m_dir_queue.capacity())
		return false;
	m_dir_queue.push_back(p_dir_info);
	// put it into the queue

	while(!m_dir_queue.empty()) {
		CDirectory *p_dir = m_dir_queue.back();
		m_dir_queue.erase(m_dir_queue.end() - 1);
		// fetch a single record from top of the queue

		size_t n_pos = m_dir_queue.size();

		TFileInfo t_file_info;
		if(!p_dir->Get_FirstFile(t_file_info)) {
			std::for_each(m_dir_queue.begin(), m_dir_queue.end(), DeleteDir);
			delete p_dir;
			return false; // t_odo - free the pair data
		}
		while(/*t_file_info.b_valid &&*/ t_file_info.b_exists) {
			_ASSERTE(t_file_info.b_Valid());
			if(!r_file_listener((const TFileInfo&)t_file_info)) {
				std::for_each(m_dir_queue.begin(), m_dir_queue.end(), DeleteDir);
				delete p_dir;
				return false;
			}
			// pass file data to listener
		
			if(b_recurse_subdirectories && t_file_info.b_directory) {
				CDirectory *p_subdir_info;
				if(!stl_ut::Reserve_1More(m_dir_queue) ||
				   !(p_subdir_info = new(std::nothrow) CDirectory(t_file_info.p_s_Path()))) {
					std::for_each(m_dir_queue.begin(), m_dir_queue.end(), DeleteDir);
					delete p_dir;
					return false;
				}
				// make sure there's enough space in the queue,
				// prepare the subdirectory handle

				m_dir_queue.insert(m_dir_queue.begin() + n_pos, p_subdir_info);
			}
			// add the directory to the list to recurse

			if(!p_dir->Get_NextFile(t_file_info)) {
				std::for_each(m_dir_queue.begin(), m_dir_queue.end(), DeleteDir);
				delete p_dir;
				return false;
			}
			// get next file info
		}
		delete p_dir;
	}

	return true;
}

inline void CDirTraversal::DeleteDir(CDirectory *p_dir)
{
	delete p_dir;
}

#endif // !__DIR_INCLUDED
