#ifndef __FILE_H__
#define __FILE_H__
#include <sys/types.h>
#include <sys/stat.h>
#include <direct.h>		// getcwd()
#include "Common.h"
#include "String.h"
#include "Formatter.h"

namespace AScript {

//-----------------------------------------------------------------------------
// DateTime
//-----------------------------------------------------------------------------
class DateTime {
private:
	short _year;
	char _month, _day;
	char _hour, _minute, _second;
	long _microsec;
public:
	inline DateTime() : _year(0), _month(0),
					_day(0), _hour(0), _minute(0), _second(0), _microsec(0) {}
	inline DateTime(short year, char month, char day,
					char hour, char minute, char second, long microsec) :
		_year(year), _month(month), _day(day),
		_hour(hour), _minute(minute), _second(second), _microsec(microsec) {}
	inline DateTime(const DateTime &dt) :
		_year(dt._year), _month(dt._month), _day(dt._day),
		_hour(dt._hour), _minute(dt._minute), _second(dt._second), _microsec(dt._microsec) {}
#if defined(WINVER)
	inline DateTime(const SYSTEMTIME &st) :
		_year(static_cast<short>(st.wYear)),
		_month(static_cast<char>(st.wMonth)),
		_day(static_cast<char>(st.wDay)),
		_hour(static_cast<char>(st.wHour)),
		_minute(static_cast<char>(st.wMinute)),
		_second(static_cast<char>(st.wSecond)),
		_microsec(static_cast<long>(st.wMilliseconds) * 1000) {}
#endif
	String ToString(const TCHAR *format) const;
	short GetYear() const { return _year; }
	char GetMonth() const { return _month; }
	char GetDay() const { return _day; }
	char GetHour() const { return _hour; }
	char GetMinute() const { return _minute; }
	char GetSecond() const { return _second; }
	char GetMicroSec() const { return _microsec; }
	static int Compare(const DateTime &dt1, const DateTime &dt2);
};

//-----------------------------------------------------------------------------
// FileStat
//-----------------------------------------------------------------------------
class FileStat {
public:
	enum {
		ATTR_Dir		= (1 << 22),
		ATTR_Chr		= (1 << 21),
		ATTR_Blk		= (1 << 20),
		ATTR_Reg		= (1 << 19),
		ATTR_Fifo		= (1 << 18),
		ATTR_Lnk		= (1 << 17),
		ATTR_Sock		= (1 << 16),
	};
private:
	String _pathName;
	unsigned long _attr;
	unsigned long _bytes;
	DateTime _atime;
	DateTime _mtime;
	DateTime _ctime;
	long _uid;
	long _gid;
public:
	inline FileStat() : _attr(0), _bytes(0), _uid(0), _gid(0) {}
	inline FileStat(const TCHAR *pathName, unsigned long attr, unsigned long bytes,
				const DateTime &atime, const DateTime &mtime, const DateTime &ctime,
				long uid, long gid) :
		_pathName(pathName), _attr(attr), _bytes(bytes),
		_atime(atime), _mtime(mtime), _ctime(ctime),
		_uid(uid), _gid(gid) {}
	inline FileStat(const FileStat &fileStat) :
		_pathName(fileStat._pathName), _attr(fileStat._attr), _bytes(fileStat._bytes),
		_atime(fileStat._atime), _mtime(fileStat._mtime), _ctime(fileStat._ctime),
		_uid(fileStat._uid), _gid(fileStat._gid) {}
	inline const TCHAR *GetPathName() const { return _pathName.c_str(); }
	inline unsigned long GetSize() const { return _bytes; }
	inline const DateTime &GetATime() const { return _atime; }
	inline const DateTime &GetMTime() const { return _mtime; }
	inline const DateTime &GetCTime() const { return _ctime; }
	inline long GetUid() const { return _uid; }
	inline long GetGid() const { return _gid; }
	inline long GetMode() const { return _attr & 0777; }
	inline bool IsDir() const { return _attr & ATTR_Dir; }
	inline bool IsChr() const { return _attr & ATTR_Chr; }
	inline bool IsBlk() const { return _attr & ATTR_Blk; }
	inline bool IsReg() const { return _attr & ATTR_Reg; }
	inline bool IsFifo() const { return _attr & ATTR_Fifo; }
	inline bool IsLnk() const { return _attr & ATTR_Lnk; }
	inline bool IsSock() const { return _attr & ATTR_Sock; }
	//inline bool IsReadOnly() const { return _attr & ATTR_ReadOnly; }
};

//-----------------------------------------------------------------------------
// File
//-----------------------------------------------------------------------------
class File {
public:
	class Formatter : public FormatterBase {
	private:
		File &_file;
	public:
		inline Formatter(File &file) : _file(file) {}
		inline void Format(Signal sig, const TCHAR *format, const ValueList &valList) {
			DoFormat(sig, format, valList);
		}
		virtual void PutChar(TCHAR ch);
	};
public:
	static const TCHAR SeparatorWin;
	static const TCHAR SeparatorUnix;
	static const TCHAR Separator;
public:
	enum AccessMode {
		ACCESS_Read, ACCESS_Write, ACCESS_ReadWrite,
	};
	//enum EntryType {
	//	ENTTYPE_File, ENTTYPE_Directory,
	//};
	class Entry {
	private:
		String _fileName;
		bool _dirFlag;
	public:
		inline Entry(const String &fileName, bool dirFlag) :
								_fileName(fileName), _dirFlag(dirFlag) {}
		inline const TCHAR *GetFileName() const { return _fileName.c_str(); }
		inline bool IsDirectory() const { return _dirFlag; }
	};
	typedef std::vector<Entry> EntryList;
private:
#if defined(WINVER)
	HANDLE _hFile;
#else
	// FILE instance cannot be passed to DLL's context (see KB190799 in MSDN)
	FILE *_fp;
#endif
	bool _needCloseFlag;
	String _fileName;
	AccessMode _accessMode;
	bool _textFlag;
	static String *_pWorkingDir;
	static String *_pBaseDir;
public:
	File();
	File(const File &file);
	inline ~File() { Close(); }
	File &Open(Signal sig, const TCHAR *fileName, const TCHAR *mode);
	File &OpenStdin();
	File &OpenStdout();
	File &OpenStderr();
	void Close();
	inline const TCHAR *GetFileName() const { return _fileName.c_str(); }
	inline bool IsReadable() const {
		return _accessMode == ACCESS_Read || _accessMode == ACCESS_ReadWrite;
	}
	inline bool IsWritable() const {
		return _accessMode == ACCESS_Write || _accessMode == ACCESS_ReadWrite;
	}
	inline bool GetTextFlag() const { return _textFlag; }
	int Seek(long offset, int origin);
	int GetChar();
	void PutChar(int ch);
	FileStat GetFileStatByHandle(Signal sig) const;
	static FileStat GetFileStat(Signal sig, const TCHAR *fileName);
	static bool IsExist(const TCHAR *pathName);
	void PutString(const TCHAR *str);
	size_t Read(void *buff, size_t bytes);
	inline static bool IsSeparator(TCHAR ch) {
		return ch == SeparatorWin || ch == SeparatorUnix;
	}
	static String ExtractDirName(const TCHAR *pathName);
	static String ExtractBaseName(const TCHAR *pathName);
	static void Split(const TCHAR *pathName, StringList &pathNameSepList);
	static bool HasWildCard(const TCHAR *pathName);
	static String MakeAbsPath(const TCHAR *fileName, const TCHAR *dirName = NULL);
	static const String &GetWorkingDir();
	static const String &GetBaseDir();
	static String &RegulateName(String &result, const TCHAR *name);
	static String &RegulateName(String &result, const TCHAR *name, size_t cnt);
	static bool ListDir(Signal sig, const TCHAR *dirName, const TCHAR *pattern,
							bool ignoreCaseFlag, EntryList &entries);
	static bool Glob(Signal sig, const TCHAR *pattern,
							bool ignoreCaseFlag, EntryList &entries);
	static void GlobSub(Signal sig, const String &dirName, bool ignoreCaseFlag,
			StringList::iterator pPathNameSep, StringList::iterator pPathNameSepEnd,
			EntryList &entries);
	static bool IsMatchName(const TCHAR *pattern,
							const TCHAR *fileName, bool ignoreCaseFlag);
};

}

#endif
