//-----------------------------------------------------------------------------
// AScript tar module
// http://www.gnu.org/software/tar/manual/html_node/Standard.html
//-----------------------------------------------------------------------------
#include "Module_tar.h"

AScript_BeginModule(tar)

AScript_DeclarePrivSymbol(name);
AScript_DeclarePrivSymbol(linkname);
AScript_DeclarePrivSymbol(uname);
AScript_DeclarePrivSymbol(gname);
AScript_DeclarePrivSymbol(mode);
AScript_DeclarePrivSymbol(uid);
AScript_DeclarePrivSymbol(gid);
AScript_DeclarePrivSymbol(size);
AScript_DeclarePrivSymbol(mtime);
AScript_DeclarePrivSymbol(atime);
AScript_DeclarePrivSymbol(ctime);
AScript_DeclarePrivSymbol(chksum);
AScript_DeclarePrivSymbol(typeflag);
AScript_DeclarePrivSymbol(devmajor);
AScript_DeclarePrivSymbol(devminor);

//-----------------------------------------------------------------------------
// Header implementation
//-----------------------------------------------------------------------------
Header::Header()
{
}

Header::Header(const Header &hdr) :
	_offset(hdr._offset), _mode(hdr._mode), _uid(hdr._uid), _gid(hdr._gid),
	_size(hdr._size), _mtime(hdr._mtime), _atime(hdr._atime), _ctime(hdr._ctime),
	_chksum(hdr._chksum), _typeflag(hdr._typeflag),
	_devmajor(hdr._devmajor), _devminor(hdr._devminor)
{
	::memcpy(_name, hdr._name, sizeof(_name));
	::memcpy(_linkname, hdr._linkname, sizeof(_linkname));
	::memcpy(_uname, hdr._uname, sizeof(_uname));
	::memcpy(_gname, hdr._gname, sizeof(_gname));
}

void Header::Initialize()
{
	SetName("");
	SetLinkName("");
	SetUName("");
	SetGName("");
	SetMode(0);
	SetUid(0);
	SetGid(0);
	SetSize(0);
	//SetMTime();
	//SetATime();
	//SetCTime();
	SetChksum(0);
	SetTypeFlag('0');
	SetDevMajor(0);
	SetDevMinor(0);
}

bool Header::SetRawHeader(Signal sig, const star_header &hdr)
{
	_name[sizeof(_name) - 1] = '\0';
	_linkname[sizeof(_linkname) - 1] = '\0';
	_uname[sizeof(_uname) - 1] = '\0';
	_gname[sizeof(_gname) - 1] = '\0';
	::memcpy(_name, hdr.name, sizeof(hdr.name));
	::memcpy(_linkname, hdr.linkname, sizeof(hdr.linkname));
	::memcpy(_uname, hdr.uname, sizeof(hdr.uname));
	::memcpy(_gname, hdr.gname, sizeof(hdr.gname));
	_mode = OctetToULong(sig, hdr.mode, sizeof(hdr.mode));
	if (sig.IsSignalled()) return false;
	_uid = OctetToULong(sig, hdr.uid, sizeof(hdr.uid));
	if (sig.IsSignalled()) return false;
	_gid = OctetToULong(sig, hdr.gid, sizeof(hdr.gid));
	if (sig.IsSignalled()) return false;
	_size = OctetToULong(sig, hdr.size, sizeof(hdr.size));
	if (sig.IsSignalled()) return false;
	do {
		unsigned long num = OctetToULong(sig, hdr.mtime, sizeof(hdr.mtime));
		if (sig.IsSignalled()) return false;
		_mtime.SetUnixTime(num);
	} while (0);
	do {
		unsigned long num = OctetToULong(sig, hdr.atime, sizeof(hdr.atime));
		if (sig.IsSignalled()) return false;
		_atime.SetUnixTime(num);
	} while (0);
	do {
		unsigned long num = OctetToULong(sig, hdr.ctime, sizeof(hdr.ctime));
		if (sig.IsSignalled()) return false;
		_ctime.SetUnixTime(num);
	} while (0);
	_chksum = OctetToULong(sig, hdr.chksum, sizeof(hdr.chksum));
	if (sig.IsSignalled()) return false;
	_typeflag = hdr.typeflag;
	_devmajor = OctetToULong(sig, hdr.devmajor, sizeof(hdr.devmajor));
	if (sig.IsSignalled()) return false;
	_devminor = OctetToULong(sig, hdr.devminor, sizeof(hdr.devminor));
	if (sig.IsSignalled()) return false;
	return true;
}

void Header::GetRawHeader(star_header &hdr)
{
	::memcpy(hdr.name,		_name, sizeof(hdr.name));
	::sprintf(hdr.mode,		"%6o ", _mode);
	::sprintf(hdr.uid,		"%6o ", _uid);
	::sprintf(hdr.gid,		"%6o ", _gid);
	::sprintf(hdr.size,		"%11o", _size);
	hdr.size[11] = ' ';
	::sprintf(hdr.mtime,	"%11o", _mtime.GetUnixTime());
	hdr.mtime[11] = ' ';
	::sprintf(hdr.chksum,	"%6o", _chksum);
	hdr.chksum[7] = ' ';
	hdr.typeflag = _typeflag;
	::memcpy(hdr.linkname,	_linkname, sizeof(hdr.linkname));
	::memcpy(hdr.magic,		"ustar\x00", 6);
	::memcpy(hdr.version,	"00", 2);
	::memcpy(hdr.uname,		_uname, sizeof(hdr.uname));
	::memcpy(hdr.gname,		_gname, sizeof(hdr.gname));
	::sprintf(hdr.devmajor,	"%6o ", _devmajor);
	::sprintf(hdr.devminor,	"%6o ", _devminor);
	::memset(hdr.prefix,	0x00, sizeof(hdr.prefix));
	::sprintf(hdr.atime,	"%11o", _atime.GetUnixTime());
	hdr.atime[11] = ' ';
	::sprintf(hdr.ctime,	"%11o", _ctime.GetUnixTime());
	hdr.ctime[11] = ' ';
}

//-----------------------------------------------------------------------------
// Object_Tar implementation
//-----------------------------------------------------------------------------
Object_Tar::Object_Tar(Signal sig) : Object(AScript_PrivClass(Tar)),
		_sig(sig), _pStreamSrc(NULL), _pStreamDst(NULL), _memBlock(BLOCKSIZE)
{
}

Object_Tar::~Object_Tar()
{
	Close();
}

Object *Object_Tar::Clone() const
{
	return NULL;
}

String Object_Tar::ToString(Signal sig, bool exprFlag)
{
	String str;
	str = "<tar:";
	str += _pathName;
	str += ">";
	return str;
}

bool Object_Tar::Open(Environment &env, Signal sig, const char *pathName)
{
	_pathName = pathName;
	Directory *pDirectory =
			Directory::OpenDirectory(env, sig, pathName, Directory::NF_Signal);
	if (sig.IsSignalled()) return false;
	Stream *pStream = pDirectory->OpenStream(env, sig, Stream::ATTR_Readable, NULL);
	Directory::Delete(pDirectory);
	if (sig.IsSignalled()) return false;
	_pStreamSrc = DecorateStream(sig, pStream, pathName);
	if (sig.IsSignalled()) return false;
	return true;
}

bool Object_Tar::Create(Environment &env, Signal sig, const char *pathName)
{
	return false;
}

bool Object_Tar::Add(Signal sig, Stream &streamSrc, const char *fileName)
{
	return false;
}

bool Object_Tar::Close()
{
	Stream::Delete(_pStreamSrc);
	Stream::Delete(_pStreamDst);
	_pStreamSrc = NULL;
	_pStreamDst = NULL;
	return !_sig.IsSignalled();
}

Header *Object_Tar::NextHeader(Signal sig, bool skipBodyFlag)
{
	return ReadHeader(sig, _pStreamSrc, _memBlock.GetPointer(), skipBodyFlag);
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Tar
//-----------------------------------------------------------------------------
// tar.tar#add(stream:stream, filename?:string):map:reduce
AScript_DeclareMethod(Tar, add)
{
	SetMode(RSLTMODE_Reduce, FLAG_Map);
	DeclareArg(env, "stream", VTYPE_Stream);
	DeclareArg(env, "filename", VTYPE_String, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Tar, add)
{
	Object_Tar *pSelf = Object_Tar::GetSelfObj(args);
	String fileName;
	if (args.IsString(1)) {
		fileName = args.GetString(1);
	} else {
		fileName = Directory::ExtractBaseName(args.GetStream(0).GetName());
	}
	if (!pSelf->Add(sig, args.GetStream(0), fileName.c_str())) return Value::Null;
	return args.GetSelf();
}

// tar.tar#each() {block?}
AScript_DeclareMethod(Tar, each)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Tar, each)
{
	Object_Tar *pSelf = Object_Tar::GetSelfObj(args);
	Iterator *pIterator = new Iterator_Each(Object_Tar::Reference(pSelf));
	return ReturnIterator(env, sig, args, pIterator);
}

// tar.tar#close():reduce
AScript_DeclareMethod(Tar, close)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
}

AScript_ImplementMethod(Tar, close)
{
	Object_Tar *pSelf = Object_Tar::GetSelfObj(args);
	if (!pSelf->Close()) return Value::Null;
	return args.GetSelf();
}

// implementation of class Tar
AScript_ImplementPrivClass(Tar)
{
	AScript_AssignMethod(Tar, add);
	AScript_AssignMethod(Tar, each);
	AScript_AssignMethod(Tar, close);
}

//-----------------------------------------------------------------------------
// Iterator_Each implementation
//-----------------------------------------------------------------------------
Iterator_Each::Iterator_Each(Object_Tar *pObjTar) : Iterator(false), _pObjTar(pObjTar)
{
}

Iterator_Each::~Iterator_Each()
{
	Object::Delete(_pObjTar);
}

bool Iterator_Each::DoNext(Environment &env, Signal sig, Value &value)
{
	Header *pHdr = _pObjTar->NextHeader(sig, false);
	if (pHdr == NULL) return false;
	Stream *pStreamSrc = Stream::Reference(_pObjTar->GetStreamSrc());
	Stream_TAR *pStream = new Stream_TAR(sig, pStreamSrc, *pHdr);
	value = Value(env, pStream);
	//value = Value(env, pHdr->GetName());
	delete pHdr;
	return true;
}

String Iterator_Each::ToString(Signal sig) const
{
	String rtn;
	rtn += "<iterator:tar.each>";
	return rtn;
}

//-----------------------------------------------------------------------------
// TAR format data types
//-----------------------------------------------------------------------------
const int BLOCKSIZE			= 512;

const char *TMAGIC			= "ustar";	// ustar and a null
const int TMAGLEN			= 6;
const char *TVERSION		= "00";		// 00 and no null
const int TVERSLEN			= 2;

// Values used in typeflag field.
const char REGTYPE			= '0';		// regular file
const char AREGTYPE			= '\0';		// regular file
const char LNKTYPE			= '1';		// link
const char SYMTYPE			= '2';		// reserved
const char CHRTYPE			= '3';		// character special
const char BLKTYPE			= '4';		// block special
const char DIRTYPE			= '5';		// directory
const char FIFOTYPE			= '6';		// FIFO special
const char CONTTYPE			= '7';		// reserved
const char XHDTYPE			= 'x';		// Extended header referring to the next file in the archive
const char XGLTYPE			= 'g';		// Global extended header

// Bits used in the mode field, values in octal. 
const unsigned long TSUID	= 04000;	// set UID on execution
const unsigned long TSGID	= 02000;	// set GID on execution
const unsigned long TSVTX	= 01000;	// reserved
										// file permissions
const unsigned long TUREAD	= 00400;	// read by owner
const unsigned long TUWRITE	= 00200;	// write by owner
const unsigned long TUEXEC	= 00100;	// execute/search by owner
const unsigned long TGREAD	= 00040;	// read by group
const unsigned long TGWRITE	= 00020;	// write by group
const unsigned long TGEXEC	= 00010;	// execute/search by group
const unsigned long TOREAD	= 00004;	// read by other
const unsigned long TOWRITE	= 00002;	// write by other
const unsigned long TOEXEC	= 00001;	// execute/search by other

//-----------------------------------------------------------------------------
// Object_Stat declaration
//-----------------------------------------------------------------------------
AScript_DeclarePrivClass(Stat);

class Object_Stat : public Object {
private:
	Header _hdr;
public:
	AScript_DeclareObjectAccessor(Stat)
public:
	inline Object_Stat(const Header &hdr) : Object(AScript_PrivClass(Stat)), _hdr(hdr) {}
	inline Object_Stat(const Object_Stat &obj) : Object(obj), _hdr(obj._hdr) {}
	virtual ~Object_Stat();
	virtual Object *Clone() const;
	virtual Value DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag);
	virtual String ToString(Signal sig, bool exprFlag);
};

//-----------------------------------------------------------------------------
// 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(name))) {
		return Value(env, _hdr.GetName());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(linkname))) {
		return Value(env, _hdr.GetLinkName());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(uname))) {
		return Value(env, _hdr.GetUName());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(gname))) {
		return Value(env, _hdr.GetGName());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(mode))) {
		return Value(_hdr.GetMode());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(uid))) {
		return Value(_hdr.GetUid());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(gid))) {
		return Value(_hdr.GetGid());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(size))) {
		return Value(_hdr.GetSize());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(mtime))) {
		return Value(env, _hdr.GetMTime());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(atime))) {
		return Value(env, _hdr.GetATime());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(ctime))) {
		return Value(env, _hdr.GetCTime());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(chksum))) {
		return Value(_hdr.GetChksum());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(typeflag))) {
		return Value(_hdr.GetTypeFlag());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(devmajor))) {
		return Value(_hdr.GetDevMajor());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(devminor))) {
		return Value(_hdr.GetDevMinor());
	}
	evaluatedFlag = false;
	return Value::Null;
}

String Object_Stat::ToString(Signal sig, bool exprFlag)
{
	String str;
	str = "<tar.stat:";
	str += _hdr.GetName();
	str += ">";
	return str;
}

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

//-----------------------------------------------------------------------------
// Stream_TAR implementation
//-----------------------------------------------------------------------------
Stream_TAR::Stream_TAR(Signal sig, Stream *pStreamSrc, const Header &hdr) :
	Stream(sig, ATTR_Readable), _pStreamSrc(pStreamSrc),
	_hdr(hdr), _name(hdr.GetName()), _bytesSrc(hdr.GetSize()),
	_offsetTop(pStreamSrc->Tell())
{
}

Stream_TAR::~Stream_TAR()
{
	//Stream::Delete(_pStreamSrc);
	Close();
}

const char *Stream_TAR::GetName() const
{
	return _name.c_str();
}

size_t Stream_TAR::DoRead(Signal sig, void *buff, size_t bytes)
{
	Stream &streamSrc = GetSource();
	size_t bytesRest = _bytesSrc - (streamSrc.Tell() - _offsetTop);
	if (bytes > bytesRest) bytes = bytesRest;
	return streamSrc.Read(sig, buff, bytes);
}

bool Stream_TAR::DoSeek(Signal sig, long offset, size_t offsetPrev, SeekMode seekMode)
{
	Stream &streamSrc = GetSource();
	if (seekMode == SeekSet) {
		return streamSrc.Seek(sig, static_cast<long>(_offsetTop + offset), seekMode);
	} else if (seekMode == SeekCur) {
		return streamSrc.Seek(sig, offset, seekMode);
	}
	return false;
}

size_t Stream_TAR::DoWrite(Signal sig, const void *buff, size_t len)
{
	return 0;
}

bool Stream_TAR::DoFlush(Signal sig)
{
	return false;
}

bool Stream_TAR::DoClose(Signal sig)
{
	Stream &streamSrc = GetSource();
	size_t bytesRest = (_bytesSrc + BLOCKSIZE - 1) / BLOCKSIZE * BLOCKSIZE -
												(streamSrc.Tell() - _offsetTop);
	
	::printf("bytesRest = %d\n", bytesRest);
	
	Stream::Delete(_pStreamSrc);
	_pStreamSrc = NULL;
	return true;
}

size_t Stream_TAR::DoGetSize()
{
	return _bytesSrc;
}

Object *Stream_TAR::DoGetStatObj(Signal sig)
{
	return new Object_Stat(_hdr);
}

//-----------------------------------------------------------------------------
// Directory_TAR implementation
//-----------------------------------------------------------------------------
Directory_TAR::Directory_TAR(Directory *pParent, const char *name, Type type,
		DirBuilder::Structure *pStructure, Record_TAR *pRecord) :
	Directory(pParent, name, type, OAL::FileSeparator),
	_pStructure(pStructure), _pRecord(pRecord)
{
}

Directory_TAR::~Directory_TAR()
{
	DirBuilder::Structure::Delete(_pStructure);
}

Directory *Directory_TAR::DoNext(Environment &env, Signal sig)
{
	return _pRecord->Next(this);
}

Stream *Directory_TAR::DoOpenStream(Environment &env, Signal sig,
								unsigned long attr, const char *encoding)
{
	Directory *pDirectory = this;
	for ( ; pDirectory != NULL && !pDirectory->IsBoundaryContainer();
										pDirectory = pDirectory->GetParent()) ;
	if (pDirectory != NULL) pDirectory = pDirectory->GetParent();
	if (pDirectory == NULL) {
		sig.SetError(ERR_IOError, "failed to open a stream");
		return NULL;
	}
	Stream *pStream = pDirectory->OpenStream(env, sig, attr, encoding);
	if (IsGZippedTar(pDirectory->GetName())) {
		ZLib::GZHeader hdr;
		if (!hdr.Read(sig, *pStream)) return NULL;
		ZLib::Stream_Inflater *pStreamInflater =
						new ZLib::Stream_Inflater(sig, pStream, InvalidSize);
		if (!pStreamInflater->Initialize(sig, -MAX_WBITS)) {
			Stream::Delete(pStream);
			return NULL;
		}
		pStream = pStreamInflater;
	} else if (IsBZippedTar(pDirectory->GetName())) {
		BZLib::Stream_Decompressor *pStreamDecompressor =
						new BZLib::Stream_Decompressor(sig, pStream, InvalidSize);
		if (!pStreamDecompressor->Initialize(sig, 0, 0)) {
			Stream::Delete(pStream);
			return NULL;
		}
		pStream = pStreamDecompressor;
	}
	//::printf("%s: %08x\n", pDirectory->GetName(), _pRecord->GetOffset());
	//::printf("offset: %d\n", _pRecord->GetOffset());
	const Header *pHdr = _pRecord->GetHeader();
	if (!pStream->Seek(sig,
			static_cast<long>(pHdr->GetOffset()), Stream::SeekSet)) {
		Stream::Delete(pStream);
		return NULL;
	}
	return new Stream_TAR(sig, pStream, *pHdr);
}

Object *Directory_TAR::DoGetStatObj(Signal sig)
{
	return new Object_Stat(*_pRecord->GetHeader());
}

//-----------------------------------------------------------------------------
// Record_TAR implementation
//-----------------------------------------------------------------------------
DirBuilder::Record *Record_TAR::DoGenerateChild(const char *name, bool containerFlag)
{
	Record_TAR *pRecord = new Record_TAR(_pStructure, this, name, containerFlag);
	AddChild(pRecord);
	return pRecord;
}

Directory *Record_TAR::DoGenerateDirectory(Directory *pParent, Directory::Type type)
{
	return new Directory_TAR(Directory::Reference(pParent),
		GetName(), type, DirBuilder::Structure::Reference(_pStructure), this);
}

//-----------------------------------------------------------------------------
// DirectoryFactory_TAR implementation
//-----------------------------------------------------------------------------
bool DirectoryFactory_TAR::IsResponsible(Environment &env, Signal sig,
						const Directory *pParent, const char *pathName)
{
	return pParent != NULL && !pParent->IsContainer() &&
			(EndsWith(pParent->GetName(), ".tar", true) ||
			 IsGZippedTar(pParent->GetName()));
}

Directory *DirectoryFactory_TAR::DoOpenDirectory(Environment &env, Signal sig,
	Directory *pParent, const char **pPathName, Directory::NotFoundMode notFoundMode)
{
	Stream *pStream = pParent->OpenStream(env, sig, Stream::ATTR_Readable, NULL);
	if (sig.IsSignalled()) return NULL;
	pStream = DecorateStream(sig, pStream, pParent->GetName());
	if (sig.IsSignalled()) return NULL;
	Value valStream(env, pStream);	// this works like std::auto_ptr
	OAL::Memory memory;
	void *buffBlock = memory.Allocate(BLOCKSIZE);
	DirBuilder::Structure *pStructure = new DirBuilder::Structure();
	pStructure->SetRoot(new Record_TAR(pStructure, NULL, "", true));
	for (;;) {
		Header *pHdr = ReadHeader(sig, pStream, buffBlock, true);
		if (sig.IsSignalled()) {
			DirBuilder::Structure::Delete(pStructure);
			pStream->Close();
			return NULL;
		}
		if (pHdr == NULL) break;
		Record_TAR *pRecord =
				dynamic_cast<Record_TAR *>(pStructure->AddRecord(pHdr->GetName()));
		pRecord->SetHeader(pHdr);
	}
	pStream->Close();
	Directory *pDirectory = pStructure->GenerateDirectory(sig,
											pParent, pPathName, notFoundMode);
	DirBuilder::Structure::Delete(pStructure);
	return pDirectory;
}

//-----------------------------------------------------------------------------
// AScript module functions: tar
//-----------------------------------------------------------------------------
// tar.open(pathname:string) {block?}
AScript_DeclareFunction(open)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "pathname", VTYPE_String);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(open)
{
	const char *pathName = args.GetString(0);
	Object_Tar *pObjTar = new Object_Tar(sig);
	if (!pObjTar->Open(env, sig, pathName)) {
		Object::Delete(pObjTar);
		return Value::Null;
	}
	Value result(pObjTar);
	return ReturnValue(env, sig, args, result);
}

// tar.create(pathname:string) {block?}
AScript_DeclareFunction(create)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "pathname", VTYPE_String);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(create)
{
	const char *pathName = args.GetString(0);
	Object_Tar *pObjTar = new Object_Tar(sig);
	if (!pObjTar->Create(env, sig, pathName)) {
		Object::Delete(pObjTar);
		return Value::Null;
	}
	Value result(pObjTar);
	return ReturnValue(env, sig, args, result);
}

// tar.test(stream:stream)
AScript_DeclareFunction(test)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "stream", VTYPE_Stream);
}

AScript_ImplementFunction(test)
{
	Stream &stream = args.GetStream(0);
	return Value::Null;
}

// Module entry
AScript_ModuleEntry()
{
	// symbol realization
	AScript_RealizePrivSymbol(name);
	AScript_RealizePrivSymbol(linkname);
	AScript_RealizePrivSymbol(uname);
	AScript_RealizePrivSymbol(gname);
	AScript_RealizePrivSymbol(mode);
	AScript_RealizePrivSymbol(uid);
	AScript_RealizePrivSymbol(gid);
	AScript_RealizePrivSymbol(size);
	AScript_RealizePrivSymbol(mtime);
	AScript_RealizePrivSymbol(atime);
	AScript_RealizePrivSymbol(ctime);
	AScript_RealizePrivSymbol(chksum);
	AScript_RealizePrivSymbol(typeflag);
	AScript_RealizePrivSymbol(devmajor);
	AScript_RealizePrivSymbol(devminor);
	// class realization
	AScript_RealizePrivClass(Tar, "tar", env.LookupClass(VTYPE_Object));
	AScript_RealizePrivClass(Stat, "stat", env.LookupClass(VTYPE_Object));
	// function assignment
	AScript_AssignFunction(open);
	AScript_AssignFunction(create);
	AScript_AssignFunction(test);
	// registration of directory factory
	DirectoryFactory::Register(new DirectoryFactory_TAR());
}

AScript_ModuleTerminate()
{
}

//-----------------------------------------------------------------------------
// utilities
//-----------------------------------------------------------------------------
Header *ReadHeader(Signal sig, Stream *pStream, void *buffBlock, bool skipBodyFlag)
{
	int nTerminater = 0;
	for (;;) {
		size_t bytesRead = pStream->Read(sig, buffBlock, BLOCKSIZE);
		if (sig.IsSignalled()) return NULL;
		if (bytesRead < BLOCKSIZE) {
			sig.SetError(ERR_FormatError, "failed to read a block");
			return NULL;
		}
		bool zeroBlockFlag = true;
		unsigned long *p = reinterpret_cast<unsigned long *>(buffBlock);
		for (int i = 0; i < BLOCKSIZE / 4; i++, p++) {
			if (*p != 0x00000000) {
				zeroBlockFlag = false;
				break;
			}
		}
		if (!zeroBlockFlag) break;
		nTerminater++;
		if (nTerminater == 2) return NULL;
	}
	star_header &hdr = *reinterpret_cast<star_header *>(buffBlock);
	Header *pHdr = new Header();
	pHdr->SetOffset(pStream->Tell());
	if (!pHdr->SetRawHeader(sig, hdr)) {
		delete pHdr;
		return NULL;
	}
	if (skipBodyFlag && !pStream->Seek(sig,
						pHdr->CalcBlocks() * BLOCKSIZE, Stream::SeekCur)) {
		delete pHdr;
		return NULL;
	}
	return pHdr;
}

Stream *DecorateStream(Signal sig, Stream *pStream, const char *name)
{
	if (IsGZippedTar(name)) {
		ZLib::GZHeader hdr;
		if (!hdr.Read(sig, *pStream)) return NULL;
		ZLib::Stream_Inflater *pStreamInflater =
					new ZLib::Stream_Inflater(sig, pStream, InvalidSize);
		if (!pStreamInflater->Initialize(sig, -MAX_WBITS)) {
			Stream::Delete(pStream);
			return NULL;
		}
		pStream = pStreamInflater;
	} else if (IsBZippedTar(name)) {
		BZLib::Stream_Decompressor *pStreamDecompressor =
						new BZLib::Stream_Decompressor(sig, pStream, InvalidSize);
		if (!pStreamDecompressor->Initialize(sig, 0, 0)) {
			Stream::Delete(pStream);
			return NULL;
		}
		pStream = pStreamDecompressor;
	}
	return pStream;
}

unsigned long OctetToULong(Signal sig, const char *octet, size_t len)
{
	unsigned long num = 0;
	for (const char *p = octet; len > 0; len--, p++) {
		char ch = *p;
		if (ch == '\0') break;
		if (ch == ' ') {
			continue;
		} else if (!IsOctDigit(ch)) {
			sig.SetError(ERR_FormatError, "invalid octet format");
			return 0;
		}
		num = (num << 3) | (ch - '0');
	}
	return num;
}

AScript_EndModule(tar, tar)

AScript_RegisterModule(tar)
