//-----------------------------------------------------------------------------
// AScript fs module
//-----------------------------------------------------------------------------
#include "Module_fs.h"

AScript_BeginModule(fs)

AScript_DeclarePrivSymbol(pathname);
AScript_DeclarePrivSymbol(basename);
AScript_DeclarePrivSymbol(dirname);
AScript_DeclarePrivSymbol(size);
AScript_DeclarePrivSymbol(uid);
AScript_DeclarePrivSymbol(gid);
AScript_DeclarePrivSymbol(atime);
AScript_DeclarePrivSymbol(mtime);
AScript_DeclarePrivSymbol(ctime);
AScript_DeclarePrivSymbol(isdir);
AScript_DeclarePrivSymbol(ischr);
AScript_DeclarePrivSymbol(isblk);
AScript_DeclarePrivSymbol(isreg);
AScript_DeclarePrivSymbol(isfifo);
AScript_DeclarePrivSymbol(islnk);
AScript_DeclarePrivSymbol(issock);

//-----------------------------------------------------------------------------
// CodecFactory
//-----------------------------------------------------------------------------
CodecFactory *GetCodecFactory()
{
	static CodecFactory *pCodecFactory = NULL;
	if (pCodecFactory == NULL) {
		pCodecFactory = CodecFactory::Lookup("cp932");
	}
	if (pCodecFactory == NULL) {
		pCodecFactory = CodecFactory::Lookup("us-ascii");
	}
	return pCodecFactory;
}

//-----------------------------------------------------------------------------
// FileStat implementation
//-----------------------------------------------------------------------------
#if defined(HAVE_WINDOWS_H)
FileStat::FileStat(const char *pathName, const BY_HANDLE_FILE_INFORMATION &attrData) :
	_pathName(pathName), _attr(0), _bytes(attrData.nFileSizeLow),
	_uid(0), _gid(0)
{
	_atime = OAL::ToDateTime(attrData.ftLastAccessTime);
	_mtime = OAL::ToDateTime(attrData.ftLastWriteTime);
	_ctime = OAL::ToDateTime(attrData.ftCreationTime);
	if (attrData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
		_attr |= ATTR_Dir;
	} else {
		_attr |= ATTR_Reg;
	}
	if (attrData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
		_attr |= 0666;
	} else {
		_attr |= 0777;
	}
}

FileStat::FileStat(const char *pathName, const WIN32_FILE_ATTRIBUTE_DATA &attrData) :
	_pathName(pathName), _attr(0), _bytes(attrData.nFileSizeLow), _uid(0), _gid(0)
{
	_atime = OAL::ToDateTime(attrData.ftLastAccessTime);
	_mtime = OAL::ToDateTime(attrData.ftLastWriteTime);
	_ctime = OAL::ToDateTime(attrData.ftCreationTime);
	if (attrData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
		_attr |= ATTR_Dir;
	} else {
		_attr |= ATTR_Reg;
	}
	if (attrData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
		_attr |= 0666;
	} else {
		_attr |= 0777;
	}
}

FileStat::FileStat(const char *pathName, const WIN32_FIND_DATA &findData) :
	_pathName(pathName), _attr(0), _bytes(findData.nFileSizeLow), _uid(0), _gid(0)
{
	_atime = OAL::ToDateTime(findData.ftLastAccessTime);
	_mtime = OAL::ToDateTime(findData.ftLastWriteTime);
	_ctime = OAL::ToDateTime(findData.ftCreationTime);
	if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
		_attr |= ATTR_Dir;
	} else {
		_attr |= ATTR_Reg;
	}
	if (findData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
		_attr |= 0666;
	} else {
		_attr |= 0777;
	}
}

FileStat *FileStat::Generate(Signal sig, const char *fileName)
{
	unsigned long attr = 0;
	WIN32_FILE_ATTRIBUTE_DATA attrData;
	String pathName = OAL::MakeAbsPath(OAL::FileSeparator, fileName, NULL);
	if (::GetFileAttributesEx(pathName.c_str(), GetFileExInfoStandard, &attrData) == 0) {
		sig.SetError(ERR_IOError, "failed to get file status of %s", pathName.c_str());
		return NULL;
	}
	return new FileStat(pathName.c_str(), attrData);
}

#else
FileStat::FileStat(const char *pathName, const struct stat &stat) :
	_pathName(pathName), _attr(0), _bytes(stat.st_size),
	_uid(stat.st_uid), _gid(stat.st_gid)
{
	_atime = OAL::ToDateTime(stat.st_atime);
	_mtime = OAL::ToDateTime(stat.st_mtime);
	_ctime = OAL::ToDateTime(stat.st_ctime);
	if (S_ISDIR(stat.st_mode)) _attr |= ATTR_Dir;
	if (S_ISCHR(stat.st_mode)) _attr |= ATTR_Chr;
	if (S_ISBLK(stat.st_mode)) _attr |= ATTR_Blk;
	if (S_ISREG(stat.st_mode)) _attr |= ATTR_Reg;
	if (S_ISFIFO(stat.st_mode)) _attr |= ATTR_Fifo;
	if (S_ISLNK(stat.st_mode)) _attr |= ATTR_Lnk;
	if (S_ISSOCK(stat.st_mode)) _attr |= ATTR_Sock;
	_attr |= (stat.st_mode & 0777);
}

FileStat *FileStat::Generate(Signal sig, const char *fileName)
{
	unsigned long attr = 0;
	struct stat stat;
	String pathName = OAL::MakeAbsPath(OAL::FileSeparator, fileName, NULL);
	if (::stat(pathName.c_str(), &stat) != 0) {
		sig.SetError(ERR_IOError, "failed to get file status of %s", pathName.c_str());
		return NULL;
	}
	return new FileStat(pathName.c_str(), stat);
}

#endif

//-----------------------------------------------------------------------------
// Object_Stat implementation
//-----------------------------------------------------------------------------
Object_Stat::~Object_Stat()
{
}

Object *Object_Stat::Clone() const
{
	return new Object_Stat(*this);
}

Value Object_Stat::DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag)
{
	Environment &env = *this;
	evaluatedFlag = true;
	if (pSymbol->IsIdentical(AScript_PrivSymbol(pathname))) {
		return Value(env, _fileStat.GetPathName());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(basename))) {
		return Value(env, Directory::ExtractBaseName(_fileStat.GetPathName()).c_str());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(dirname))) {
		return Value(env, Directory::ExtractDirName(_fileStat.GetPathName()).c_str());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(size))) {
		return Value(static_cast<Number>(_fileStat.GetSize()));
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(uid))) {
		return Value(static_cast<Number>(_fileStat.GetUid()));
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(gid))) {
		return Value(static_cast<Number>(_fileStat.GetGid()));
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(atime))) {
		return Value(new Object_DateTime(env, _fileStat.GetATime()));
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(mtime))) {
		return Value(new Object_DateTime(env, _fileStat.GetMTime()));
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(ctime))) {
		return Value(new Object_DateTime(env, _fileStat.GetCTime()));
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(isdir))) {
		return Value(_fileStat.IsDir());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(ischr))) {
		return Value(_fileStat.IsChr());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(isblk))) {
		return Value(_fileStat.IsBlk());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(isreg))) {
		return Value(_fileStat.IsReg());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(isfifo))) {
		return Value(_fileStat.IsFifo());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(islnk))) {
		return Value(_fileStat.IsLnk());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(issock))) {
		return Value(_fileStat.IsSock());
	}
	evaluatedFlag = false;
	return Value::Null;
}

String Object_Stat::ToString(Signal sig, bool exprFlag)
{
	return String("<fs.stat>");
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Stat
//-----------------------------------------------------------------------------
// implementation of class Stat
AScript_ImplementPrivClass(Stat)
{
}

//-----------------------------------------------------------------------------
// Stream_File implementation
//-----------------------------------------------------------------------------
Stream_File::~Stream_File()
{
	Close();
}

const char *Stream_File::GetName() const
{
	return _fileName.c_str();
}

#if defined(HAVE_WINDOWS_H)
Stream_File::Stream_File(Signal sig) : Stream(sig, ATTR_BwdSeekable), _hFile(INVALID_HANDLE_VALUE), _needCloseFlag(false)
{
}

bool Stream_File::Open(Signal sig, const char *fileName,
								unsigned long attr, const char *encoding)
{
	Close();
	_attr |= attr;
	if (!InstallCodec(encoding, true)) {
		sig.SetError(ERR_CodecError, "unsupported encoding '%s'", encoding);
		return false;
	}
	_fileName = OAL::MakeAbsPath(OAL::FileSeparator, fileName);
	DWORD dwDesiredAccess =
			IsAppend()? GENERIC_WRITE :
			IsReadable()? GENERIC_READ :
			IsWritable()? GENERIC_WRITE :
			GENERIC_READ;
	DWORD dwShareMode = FILE_SHARE_READ;
	DWORD dwCreationDisposition =
			IsAppend()? OPEN_ALWAYS :
			IsReadable()? OPEN_EXISTING :
			IsWritable()? CREATE_ALWAYS :
			OPEN_EXISTING;
	Binary fileNameEnc;
	std::auto_ptr<Codec_Encoder> pEncoder(GetCodecFactory()->CreateEncoder(false));
	if (!pEncoder->Encode(sig, fileNameEnc, _fileName)) return false;
	_hFile = ::CreateFile(fileNameEnc.c_str(), dwDesiredAccess, dwShareMode,
					NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
	if (_hFile == INVALID_HANDLE_VALUE) {
		sig.SetError(ERR_IOError, "can't open file '%s'",
								Directory::ExtractBaseName(fileName).c_str());
		return false;
	}
	if (IsAppend()) {
		::SetFilePointer(_hFile, 0, NULL, FILE_END);
	}
	_map.hFileMappingObject = NULL;
	_map.buff = NULL;
	_map.bytes = 0;
	_map.offset = 0;
	_needCloseFlag = true;
	if (dwDesiredAccess == GENERIC_READ) {
		_map.hFileMappingObject =
				::CreateFileMapping(_hFile, NULL, PAGE_READONLY, 0, 0, NULL);
		if (_map.hFileMappingObject == NULL) {
			// it seems to open an empty file
			return true;
		}
		_map.buff = reinterpret_cast<unsigned char *>(
				::MapViewOfFile(_map.hFileMappingObject, FILE_MAP_READ, 0, 0, 0));
		_map.bytes = ::GetFileSize(_hFile, NULL);
		_map.offset = 0;
	}
	return true;
}

bool Stream_File::OpenStdin()
{
	Close();
	_hFile = ::GetStdHandle(STD_INPUT_HANDLE);
	if (_hFile != INVALID_HANDLE_VALUE) {
		_fileName = "stdin", SetReadable(true), SetWritable(false);
		InstallCodec("cp932", true);
	}
	return true;
}

bool Stream_File::OpenStdout()
{
	Close();
	_hFile = ::GetStdHandle(STD_OUTPUT_HANDLE);
	if (_hFile != INVALID_HANDLE_VALUE) {
		_fileName = "stdout", SetReadable(false), SetWritable(true);
		InstallCodec("cp932", true);
	}
	return true;
}

bool Stream_File::OpenStderr()
{
	Close();
	_hFile = ::GetStdHandle(STD_ERROR_HANDLE);
	if (_hFile != INVALID_HANDLE_VALUE) {
		_fileName = "stderr", SetReadable(false), SetWritable(true);
		InstallCodec("cp932", true);
	}
	return true;
}

size_t Stream_File::DoRead(Signal sig, void *buff, size_t bytes)
{
	if (_map.hFileMappingObject == NULL) {
		DWORD dwBytesRead;
		::ReadFile(_hFile, buff, static_cast<DWORD>(bytes), &dwBytesRead, NULL);
		return static_cast<size_t>(dwBytesRead);
	} else {
		size_t bytesRead = bytes;
		if (_map.offset >= _map.bytes) {
			return 0;
		} else if (_map.offset + bytesRead > _map.bytes) {
			bytesRead = _map.bytes - _map.offset;
		}
		::memcpy(buff, _map.buff + _map.offset, bytesRead);
		_map.offset += bytesRead;
		return bytesRead;
	}
}

size_t Stream_File::DoWrite(Signal sig, const void *buff, size_t bytes)
{
	DWORD dwBytesWritten;
	::WriteFile(_hFile, buff, static_cast<DWORD>(bytes), &dwBytesWritten, NULL);
	return static_cast<size_t>(dwBytesWritten);
}

bool Stream_File::DoSeek(Signal sig, long offset, size_t offsetPrev, SeekMode seekMode)
{
	if (_hFile == INVALID_HANDLE_VALUE) return true;
	if (_map.hFileMappingObject == NULL) {
		DWORD dwMoveMethod =
			(seekMode == SeekSet)? FILE_BEGIN :
			(seekMode == SeekCur)? FILE_CURRENT : FILE_BEGIN;
		DWORD dwPtr = ::SetFilePointer(_hFile, offset, NULL, dwMoveMethod);
		if (dwPtr == INVALID_SET_FILE_POINTER) {
			sig.SetError(ERR_IOError, "seek error");
			return false;
		}
	} else {
		if (seekMode == SeekSet) {
			_map.offset = static_cast<size_t>(offset);
		} else if (seekMode == SeekCur) {
			if (offset < 0 && _map.offset < static_cast<size_t>(-offset)) {
				sig.SetError(ERR_IOError, "seek error");
				return false;
			}
			_map.offset = static_cast<size_t>(_map.offset + offset);
		}
		if (_map.offset > _map.bytes) {
			sig.SetError(ERR_IOError, "seek error");
			return false;
		}
	}
	return true;
}

bool Stream_File::DoFlush(Signal sig)
{
	::FlushFileBuffers(_hFile);
	return true;
}

bool Stream_File::DoClose(Signal sig)
{
	if (_needCloseFlag && _hFile != INVALID_HANDLE_VALUE) {
		if (_map.hFileMappingObject != NULL) {
			::UnmapViewOfFile(_map.buff);
			::CloseHandle(_map.hFileMappingObject);
			_map.hFileMappingObject = NULL;
			_map.buff = NULL;
		}
		::CloseHandle(_hFile);
		_hFile = INVALID_HANDLE_VALUE;
		_needCloseFlag = false;
		_fileName = "<invalid>";
		ReleaseCodec();
	}
	return true;
}

size_t Stream_File::DoGetSize()
{
	if (_hFile == INVALID_HANDLE_VALUE) return 0;
	DWORD rtn = ::GetFileSize(_hFile, NULL);
	if (rtn == 0xffffffff) return InvalidSize;
	return static_cast<size_t>(rtn);
}

Object *Stream_File::DoGetStatObj(Signal sig)
{
	BY_HANDLE_FILE_INFORMATION attrData;
	String pathName = OAL::MakeAbsPath(OAL::FileSeparator, _fileName.c_str(), NULL);
	if (::GetFileInformationByHandle(_hFile, &attrData) == 0) {
		sig.SetError(ERR_IOError, "failed to get file status of %s", pathName.c_str());
		return NULL;
	}
	return new Object_Stat(FileStat(pathName.c_str(), attrData));
}

#else // !defined(HAVE_WINDOWS_H)
Stream_File::Stream_File(Signal sig) : Stream(sig, ATTR_BwdSeekable), _fp(NULL), _needCloseFlag(false)
{
}

bool Stream_File::Open(Signal sig, const char *fileName,
								unsigned long attr, const char *encoding)
{
	Close();
	_attr |= attr;
	if (!InstallCodec(encoding, false)) {
		sig.SetError(ERR_CodecError, "unsupported encoding '%s'", encoding);
		return false;
	}
	_fileName = OAL::MakeAbsPath(OAL::FileSeparator, fileName);
	char modeMod[8];
	if (IsAppend()) {
		modeMod[0] = 'a';
	} else if (IsReadable()) {
		modeMod[0] = 'r';
	} else if (IsWritable()) {
		modeMod[0] = 'w';
	} else {
		modeMod[0] = 'r';
	}
	modeMod[1] = '\0';
	Binary fileNameEnc;
	std::auto_ptr<Codec_Encoder> pEncoder(GetCodecFactory()->CreateEncoder(false));
	if (!pEncoder->Encode(sig, fileNameEnc, _fileName)) return false;
	_fp = ::fopen(fileNameEnc.c_str(), modeMod);
	if (_fp == NULL) {
		sig.SetError(ERR_IOError, "can't open file '%s'",
						Directory::ExtractBaseName(fileName).c_str());
		return false;
	}
	_needCloseFlag = true;
	return true;
}

bool Stream_File::OpenStdin()
{
	_fp = stdin;
	_fileName = "stdin", SetReadable(true), SetWritable(false);
	InstallCodec(Codec::EncodingFromLANG(), false);
	return true;
}

bool Stream_File::OpenStdout()
{
	_fp = stdout;
	_fileName = "stdout", SetReadable(false), SetWritable(true);
	InstallCodec(Codec::EncodingFromLANG(), false);
	return true;
}

bool Stream_File::OpenStderr()
{
	_fp = stderr;
	_fileName = "stderr", SetReadable(false), SetWritable(true);
	InstallCodec(Codec::EncodingFromLANG(), false);
	return true;
}

size_t Stream_File::DoRead(Signal sig, void *buff, size_t bytes)
{
	return ::fread(buff, 1, bytes, _fp);
}

size_t Stream_File::DoWrite(Signal sig, const void *buff, size_t bytes)
{
	return ::fwrite(buff, 1, bytes, _fp);
}

bool Stream_File::DoFlush(Signal sig)
{
	::fflush(_fp);
	return true;
}

bool Stream_File::DoSeek(Signal sig, long offset, size_t offsetPrev, SeekMode seekMode)
{
	if (_fp == NULL) return true;
	int origin =
		(seekMode == SeekSet)? SEEK_SET :
		(seekMode == SeekCur)? SEEK_CUR : SEEK_SET;
	if (::fseek(_fp, offset, origin) != 0) {
		sig.SetError(ERR_IOError, "seek error");
		return false;
	}
	return true;
}

bool Stream_File::DoClose(Signal sig)
{
	if (_needCloseFlag && _fp != NULL) {
		::fclose(_fp);
		_fp = NULL;
		_needCloseFlag = false;
		_fileName = "<invalid>";
		ReleaseCodec();
	}
	return true;
}

size_t Stream_File::DoGetSize()
{
	if (_fp == NULL) return 0;
	struct stat stat;
	if (::fstat(fileno(_fp), &stat) != 0) return InvalidSize;
	return static_cast<size_t>(stat.st_size);
}

Object *Stream_File::DoGetStatObj(Signal sig)
{
	unsigned long attr = 0;
	struct stat stat;
	String pathName = OAL::MakeAbsPath(OAL::FileSeparator, _fileName.c_str(), NULL);
	if (::fstat(fileno(_fp), &stat) != 0) {
		sig.SetError(ERR_IOError, "failed to get file status of %s", pathName.c_str());
		return NULL;
	}
	return new Object_Stat(FileStat(pathName.c_str(), stat));
}

#endif

//-----------------------------------------------------------------------------
// Directory_FileSys implementation
//-----------------------------------------------------------------------------
#if defined(HAVE_WINDOWS_H)
Directory_FileSys::Directory_FileSys(Directory *pParent, const char *name,
										Type type, FileStat *pFileStat) :
	Directory(pParent, name, type, OAL::FileSeparator),
	_hFind(INVALID_HANDLE_VALUE), _pFileStat(pFileStat),
	_pDecoder(GetCodecFactory()->CreateDecoder(false)),
	_pEncoder(GetCodecFactory()->CreateEncoder(false))
{
}

Directory_FileSys::~Directory_FileSys()
{
	::FindClose(_hFind);
}

Directory *Directory_FileSys::DoNext(Environment &env, Signal sig)
{
	WIN32_FIND_DATA findData;
	if (_hFind == INVALID_HANDLE_VALUE) {
		String pathName(MakePathName(false));
		if (!pathName.empty()) pathName += GetSeparator();
		pathName += "*.*";
		Binary pathNameEnc;
		if (!_pEncoder->Encode(sig, pathNameEnc, pathName)) return NULL;
		_hFind = ::FindFirstFile(pathNameEnc.c_str(), &findData);
		if (_hFind == INVALID_HANDLE_VALUE) return NULL;
	} else if (!::FindNextFile(_hFind, &findData)) {
		::FindClose(_hFind);
		_hFind = INVALID_HANDLE_VALUE;
		return NULL;
	}
	while (::strcmp(findData.cFileName, ".") == 0 ||
			::strcmp(findData.cFileName, "..") == 0) {
		if (!::FindNextFile(_hFind, &findData)) {
			::FindClose(_hFind);
			_hFind = INVALID_HANDLE_VALUE;
			return NULL;
		}
	}
	Type type = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)?
												TYPE_Container : TYPE_Item;
	String fileName;
	if (!_pDecoder->Decode(sig, fileName,
				findData.cFileName, ::strlen(findData.cFileName))) return NULL;
	return new Directory_FileSys(Directory::Reference(this), fileName.c_str(),
							type, new FileStat(fileName.c_str(), findData));
}

bool Directory_FileSys::IsExist(Signal sig, const char *pathName)
{
	WIN32_FILE_ATTRIBUTE_DATA attrData;
	String pathNameEnc;
	std::auto_ptr<Codec_Encoder> pEncoder(GetCodecFactory()->CreateEncoder(false));
	if (!pEncoder->Encode(sig, pathNameEnc, pathName)) return false;
	return ::GetFileAttributesEx(pathNameEnc.c_str(), GetFileExInfoStandard, &attrData) != 0;
}

bool Directory_FileSys::IsDir(Signal sig, const char *pathName)
{
	WIN32_FILE_ATTRIBUTE_DATA attrData;
	String pathNameEnc;
	std::auto_ptr<Codec_Encoder> pEncoder(GetCodecFactory()->CreateEncoder(false));
	if (!pEncoder->Encode(sig, pathNameEnc, pathName)) return false;
	return ::GetFileAttributesEx(pathNameEnc.c_str(), GetFileExInfoStandard, &attrData) != 0 &&
					(attrData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
}

#else

Directory_FileSys::Directory_FileSys(Directory *pParent, const char *name,
												Type type, FileStat *pFileStat) :
	Directory(pParent, name, type, OAL::FileSeparator),
	_pDir(NULL), _pFileStat(pFileStat),
	_pDecoder(GetCodecFactory()->CreateDecoder(false)),
	_pEncoder(GetCodecFactory()->CreateEncoder(false))
{
}

Directory_FileSys::~Directory_FileSys()
{
	if (_pDir != NULL) closedir(_pDir);
}

Directory *Directory_FileSys::DoNext(Environment &env, Signal sig)
{
	if (_pDir == NULL) {
		String pathName(MakePathName(false));
		Binary pathNameEnc;
		if (!_pEncoder->Encode(sig, pathNameEnc, pathName)) return NULL;
		_pDir = opendir(pathNameEnc.empty()? "." : pathNameEnc.c_str());
		if (_pDir == NULL) return NULL;
	}
	struct dirent *pEnt = NULL;
	for (;;) {
		pEnt = readdir(_pDir);
		if (pEnt == NULL) {
			closedir(_pDir);
			_pDir = NULL;
			return NULL;
		}
		if (::strcmp(pEnt->d_name, ".") != 0 &&
			::strcmp(pEnt->d_name, "..") != 0) break;
	}
	Type type = (pEnt->d_type == DT_DIR)? TYPE_Container : TYPE_Item;
	String fileName;
	if (!_pDecoder->Decode(sig, fileName,
				pEnt->d_name, ::strlen(pEnt->d_name))) return NULL;
	return new Directory_FileSys(
				Directory::Reference(this), fileName.c_str(), type, NULL);
}

bool Directory_FileSys::IsExist(Signal sig, const char *pathName)
{
	struct stat stat;
	String pathNameEnc;
	std::auto_ptr<Codec_Encoder> pEncoder(GetCodecFactory()->CreateEncoder(false));
	if (!pEncoder->Encode(sig, pathNameEnc, pathName)) return false;
	return ::stat(pathNameEnc.c_str(), &stat) == 0;
}

bool Directory_FileSys::IsDir(Signal sig, const char *pathName)
{
	struct stat stat;
	String pathNameEnc;
	std::auto_ptr<Codec_Encoder> pEncoder(GetCodecFactory()->CreateEncoder(false));
	if (!pEncoder->Encode(sig, pathNameEnc, pathName)) return false;
	return ::stat(pathNameEnc.c_str(), &stat) == 0 && S_ISDIR(stat.st_mode);
}

#endif

Object *Directory_FileSys::DoGetStatObj(Signal sig)
{
	if (_pFileStat.get() == NULL) {
		FileStat *pFileStat =
				FileStat::Generate(sig, MakePathName(false).c_str());
		if (sig.IsSignalled()) return NULL;
		_pFileStat.reset(pFileStat);
	}
	return new Object_Stat(*_pFileStat);
}

Stream *Directory_FileSys::DoOpenStream(Environment &env, Signal sig,
										unsigned long attr, const char *encoding)
{
	Stream_File *pStream = new Stream_File(sig);
	if (!pStream->Open(sig, MakePathName(false).c_str(), attr, encoding)) {
		return NULL;
	}
	return pStream;
}

//-----------------------------------------------------------------------------
// DirectoryFactory_FileSys implementation
//-----------------------------------------------------------------------------
bool DirectoryFactory_FileSys::IsResponsible(Environment &env, Signal sig,
								const Directory *pParent, const char *pathName)
{
	if (pParent != NULL) return false;
	
	return true;
}

Directory *DirectoryFactory_FileSys::DoOpenDirectory(Environment &env, Signal sig,
	Directory *pParent, const char **pPathName, Directory::NotFoundMode notFoundMode)
{
	Directory *pDirectory = NULL;
	String field;
	String pathAccum;
	const char *p = *pPathName;
	if (IsFileSeparator(*p)) {
		pathAccum += OAL::FileSeparator;
		p++;
		Directory::Type type = Directory::TYPE_RootContainer;
		pDirectory = new Directory_FileSys(
						Directory::Reference(pParent), "", type, NULL);
		pParent = pDirectory;
	}
	for ( ; ; p++) {
		char ch = *p;
		if (IsFileSeparator(ch) || ch == '\0') {
			pathAccum += field;
			Directory::Type type = Directory::TYPE_Container;
			if (!pathAccum.empty()) {
				bool existFlag = Directory_FileSys::IsExist(sig, pathAccum.c_str());
				if (sig.IsSignalled()) return NULL;
				if (existFlag) {
					type = Directory_FileSys::IsDir(sig, pathAccum.c_str())?
								Directory::TYPE_Container : Directory::TYPE_Item;
					if (sig.IsSignalled()) return NULL;
				} else if (notFoundMode == Directory::NF_Wouldbe) {
					type = IsFileSeparator(ch)?
								Directory::TYPE_Container : Directory::TYPE_Item;
				} else if (notFoundMode == Directory::NF_Signal) {
					sig.SetError(ERR_IOError, "path not exist: %s", pathAccum.c_str());
					return NULL;
				} else {
					return NULL;
				}
			}
			pDirectory = new Directory_FileSys(
					Directory::Reference(pParent), field.c_str(), type, NULL);
			pParent = pDirectory;
			field.clear();
			if (type == Directory::TYPE_Item || ch == '\0') break;
			pathAccum += OAL::FileSeparator;
		} else {
			field += ch;
		}
	}
	*pPathName = p;
	return pDirectory;
}

//-----------------------------------------------------------------------------
// AScript module functions: fs
//-----------------------------------------------------------------------------
// fs.rename(src:string, dst:string):map:void
AScript_DeclareFunction(rename)
{
	SetMode(RSLTMODE_Void, FLAG_Map);
	DeclareArg(env, "src", VTYPE_String);
	DeclareArg(env, "dst", VTYPE_String);
	SetHelp("Renames a file or directory.");
}

AScript_ImplementFunction(rename)
{
	OAL::Rename(args.GetString(0), args.GetString(1));
	return Value::Null;
}

// fs.remove(pathname:string):map:void
AScript_DeclareFunction(remove)
{
	SetMode(RSLTMODE_Void, FLAG_Map);
	DeclareArg(env, "pathname", VTYPE_String);
	SetHelp("Removes a file from the file system.");
}

AScript_ImplementFunction(remove)
{
	OAL::Remove(args.GetString(0));
	return Value::Null;
}

// fs.mkdir(pathname:string):map:void
AScript_DeclareFunction(mkdir)
{
	SetMode(RSLTMODE_Void, FLAG_Map);
	DeclareArg(env, "pathname", VTYPE_String);
	SetHelp("Creates a directory.");
}

AScript_ImplementFunction(mkdir)
{
	OAL::MakeDir(args.GetString(0), false);
	return Value::Null;
}

// fs.chdir(pathname:string):void
AScript_DeclareFunction(chdir)
{
	SetMode(RSLTMODE_Void, FLAG_None);
	DeclareArg(env, "pathname", VTYPE_String);
	SetHelp("Changes the current working directory.");
}

AScript_ImplementFunction(chdir)
{
	if (!OAL::ChangeCurDir(args.GetString(0))) {
		sig.SetError(ERR_IOError, "failed to change current directory");
	}
	return Value::Null;
}

// fs.getcwd()
AScript_DeclareFunction(getcwd)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	SetHelp("Returns the current working directory.");
}

AScript_ImplementFunction(getcwd)
{
	return Value(env, OAL::GetCurDir().c_str());
}

// Module entry
AScript_ModuleEntry()
{
	// symbol realization
	AScript_RealizePrivSymbol(pathname);
	AScript_RealizePrivSymbol(basename);
	AScript_RealizePrivSymbol(dirname);
	AScript_RealizePrivSymbol(size);
	AScript_RealizePrivSymbol(uid);
	AScript_RealizePrivSymbol(gid);
	AScript_RealizePrivSymbol(atime);
	AScript_RealizePrivSymbol(mtime);
	AScript_RealizePrivSymbol(ctime);
	AScript_RealizePrivSymbol(isdir);
	AScript_RealizePrivSymbol(ischr);
	AScript_RealizePrivSymbol(isblk);
	AScript_RealizePrivSymbol(isreg);
	AScript_RealizePrivSymbol(isfifo);
	AScript_RealizePrivSymbol(islnk);
	AScript_RealizePrivSymbol(issock);
	// class realization
	AScript_RealizePrivClass(Stat, "stat", env.LookupClass(VTYPE_Object));
	// symbol realization
	DirectoryFactory::Register(new DirectoryFactory_FileSys());
	// assign symbols in sys module
	Module *pModuleSys = env.GetModule_sys();
	do {
		Stream_File *pStream = new Stream_File(sig);
		pStream->OpenStdin();
		pModuleSys->AssignValue(AScript_Symbol(stdin),
							Value(new Object_Stream(env, pStream)), false);
	} while (0);
	do {
		Stream_File *pStream = new Stream_File(sig);
		pStream->OpenStdout();
		pModuleSys->AssignValue(AScript_Symbol(stdout),
							Value(new Object_Stream(env, pStream)), false);
	} while (0);
	do {
		Stream_File *pStream = new Stream_File(sig);
		pStream->OpenStderr();
		pModuleSys->AssignValue(AScript_Symbol(stderr),
							Value(new Object_Stream(env, pStream)), false);
	} while (0);
	// function assignment
	AScript_AssignFunction(rename);
	AScript_AssignFunction(remove);
	AScript_AssignFunction(mkdir);
	AScript_AssignFunction(chdir);
	AScript_AssignFunction(getcwd);
}

AScript_ModuleTerminate()
{
}

AScript_EndModule(fs, fs)

AScript_RegisterModule(fs)
