//-----------------------------------------------------------------------------
// AScript zipfile module
// specification: http://www.pkware.com/documents/casestudies/APPNOTE.TXT
//-----------------------------------------------------------------------------
#include <ascript.h>
#include "ascript/ZLibHelper.h"
#include "Module_zipfile.h"

AScript_BeginModule(zipfile)

AScript_DeclarePrivSymbol(filename);
AScript_DeclarePrivSymbol(comment);
AScript_DeclarePrivSymbol(compression_method);
AScript_DeclarePrivSymbol(mtime);
AScript_DeclarePrivSymbol(crc32);
AScript_DeclarePrivSymbol(size);
AScript_DeclarePrivSymbol(compressed_size);
AScript_DeclarePrivSymbol(attributes);

//-----------------------------------------------------------------------------
// Object_ZipFile implementation
//-----------------------------------------------------------------------------
Object_ZipFile::Object_ZipFile(Signal sig) : Object(AScript_PrivClass(ZipFile)),
							_sig(sig), _pStreamSrc(NULL), _pStreamDst(NULL)
{
}

Object_ZipFile::~Object_ZipFile()
{
	Close();
	foreach (CentralFileHeaderList, ppHdr, _hdrList) {
		delete *ppHdr;
	}
}

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

String Object_ZipFile::ToString(Signal sig, bool exprFlag)
{
	String str;
	str = "<zipfile.zipfile:";
	str += _pathName;
	do {
		str += ":";
		str += NumberToString(_hdrList.size());
		str += "files";
	} while (0);
	str += ">";
	return str;
}

bool Object_ZipFile::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;
	_pStreamSrc = pDirectory->OpenStream(env, sig, Stream::ATTR_Readable, NULL);
	Directory::Delete(pDirectory);
	if (sig.IsSignalled()) return false;
	if (!_pStreamSrc->IsBwdSeekable()) {
		Stream *pStreamPrefetch = Stream::Prefetch(sig, _pStreamSrc, true);
		if (sig.IsSignalled()) return NULL;
		_pStreamSrc = pStreamPrefetch;
	}
	unsigned long offsetCentralDirectory = SeekCentralDirectory(sig, _pStreamSrc);
	if (sig.IsSignalled()) return false;
	if (!_pStreamSrc->Seek(sig, offsetCentralDirectory, Stream::SeekSet)) return false;
	Object_ZipFile *pObjZipFile = new Object_ZipFile(sig);
	Value result(pObjZipFile);
	unsigned long signature;
	while (ReadStream(sig, *_pStreamSrc, &signature)) {
		//::printf("%08x\n", signature);
		if (signature == LocalFileHeader::Signature) {
			LocalFileHeader hdr;
			if (!hdr.Read(sig, *_pStreamSrc)) return false;
			if (!hdr.SkipFileData(sig, *_pStreamSrc)) return false;
		} else if (signature == ArchiveExtraDataRecord::Signature) {
			ArchiveExtraDataRecord record;
			if (!record.Read(sig, *_pStreamSrc)) return false;
		} else if (signature == CentralFileHeader::Signature) {
			CentralFileHeader *pHdr = new CentralFileHeader();
			if (!pHdr->Read(sig, *_pStreamSrc)) {
				delete pHdr;
				return false;
			}
			_hdrList.push_back(pHdr);
		} else if (signature == DigitalSignature::Signature) {
			DigitalSignature signature;
			if (!signature.Read(sig, *_pStreamSrc)) return false;
		} else if (signature == Zip64EndOfCentralDirectory::Signature) {
			Zip64EndOfCentralDirectory dir;
			if (!dir.Read(sig, *_pStreamSrc)) return false;
		} else if (signature == Zip64EndOfCentralDirectoryLocator::Signature) {
			Zip64EndOfCentralDirectoryLocator loc;
			if (!loc.Read(sig, *_pStreamSrc)) return false;
		} else if (signature == EndOfCentralDirectoryRecord::Signature) {
			EndOfCentralDirectoryRecord record;
			if (!record.Read(sig, *_pStreamSrc)) return false;
			break;
		} else {
			sig.SetError(ERR_FormatError, "unknown signature %08x", signature);
			return false;
		}
	}
	return true;
}

bool Object_ZipFile::Create(Environment &env, Signal sig, const char *pathName)
{
	_pathName = pathName;
	_pStreamDst = Directory::OpenStream(env, sig,
									pathName, Stream::ATTR_Writable, NULL);
	return !sig.IsSignalled();
}

bool Object_ZipFile::Add(Signal sig, Stream &streamSrc, const char *fileName)
{
	if (_pStreamDst == NULL) {
		sig.SetError(ERR_IOError, "invalid accesss to zipfile");
		return false;
	}
	const int memLevel = 8;
	CentralFileHeader *pHdr = new CentralFileHeader();
	_hdrList.push_back(pHdr);
	unsigned short version = (0 << 8) | (2 * 10 + 0);	// MS-DOS, 2.0
	unsigned short generalPurposeBitFlag = (1 << 3);	// ExistDataDescriptor
	unsigned short compressionMethod = METHOD_Deflate;
	DateTime dt = OAL::GetCurDateTime(false);
	unsigned short lastModFileTime = GetDosTime(dt);
	unsigned short lastModFileDate = GetDosDate(dt);
	unsigned long compressedSize = 0;
	unsigned long uncompressedSize = 0;
	unsigned long externalFileAttributes = (1 << 5);
	unsigned long relativeOffsetOfLocalHeader =
							static_cast<unsigned long>(_pStreamDst->Tell());
	do {
		CentralFileHeader::Fields &fields = pHdr->GetFields();
		XPackUShort(fields.VersionMadeBy,				version);
		XPackUShort(fields.VersionNeededToExtract,		version);
		XPackUShort(fields.GeneralPurposeBitFlag,		generalPurposeBitFlag);
		XPackUShort(fields.CompressionMethod,			compressionMethod);
		XPackUShort(fields.LastModFileTime,				lastModFileTime);
		XPackUShort(fields.LastModFileDate,				lastModFileDate);
		XPackULong(fields.Crc32,						0x00000000);
		XPackULong(fields.CompressedSize,				compressedSize);
		XPackULong(fields.UncompressedSize,				uncompressedSize);
		XPackUShort(fields.FileNameLength,				0x0000);
		XPackUShort(fields.ExtraFieldLength,			0x0000);
		XPackUShort(fields.FileCommentLength,			0x0000);
		XPackUShort(fields.DiskNumberStart,				0x0000);
		XPackUShort(fields.InternalFileAttributes,		0x0000);
		XPackULong(fields.ExternalFileAttributes,		externalFileAttributes);
		XPackULong(fields.RelativeOffsetOfLocalHeader,	relativeOffsetOfLocalHeader);
		pHdr->SetFileName(fileName);
		if (!pHdr->WriteAsLocalFileHeader(sig, *_pStreamDst)) return false;
	} while (0);
	Stream *pStreamOut = NULL;
	size_t offsetDst = _pStreamDst->Tell();
	size_t offsetSrc = streamSrc.Tell();
	if (compressionMethod == METHOD_Deflate) {
		ZLib::Stream_Deflater *pStreamDeflater =
					new ZLib::Stream_Deflater(sig, Stream::Reference(_pStreamDst));
		if (!pStreamDeflater->Initialize(sig, -MAX_WBITS, memLevel)) {
			Stream::Delete(pStreamDeflater);
			return false;
		}
		pStreamOut = pStreamDeflater;
	} else {
		pStreamOut = Stream::Reference(_pStreamDst);
	}
	CRC32 crc32;
	OAL::Memory memory;
	void *buff = memory.Allocate(32768);
	for (;;) {
		size_t bytesRead = streamSrc.Read(sig, buff, memory.GetSize());
		if (sig.IsSignalled()) break;
		if (bytesRead == 0) break;
		crc32.Update(buff, bytesRead);
		pStreamOut->Write(sig, buff, bytesRead);
		if (sig.IsSignalled()) break;
	}
	pStreamOut->Flush(sig);
	if (sig.IsSignalled()) return false;
	Stream::Delete(pStreamOut);
	unsigned long crc32num = crc32.GetResult();
	compressedSize = static_cast<unsigned long>(_pStreamDst->Tell() - offsetDst);
	uncompressedSize = static_cast<unsigned long>(streamSrc.Tell() - offsetSrc);
	do {
		DataDescriptor desc;
		DataDescriptor::Fields &fields = desc.GetFields();
		XPackULong(fields.Crc32,			crc32num);
		XPackULong(fields.CompressedSize,	compressedSize);
		XPackULong(fields.UncompressedSize,	uncompressedSize);
		if (!desc.Write(sig, *_pStreamDst)) return false;
	} while (0);
	do {
		CentralFileHeader::Fields &fields = pHdr->GetFields();
		XPackULong(fields.Crc32,			crc32num);
		XPackULong(fields.CompressedSize,	compressedSize);
		XPackULong(fields.UncompressedSize,	uncompressedSize);
	} while (0);
	return true;
}

bool Object_ZipFile::Close()
{
	if (_pStreamSrc != NULL) {
		Stream::Delete(_pStreamSrc);
		_pStreamSrc = NULL;
	}
	if (_pStreamDst != NULL) {
		size_t offset = _pStreamDst->Tell();
		foreach (CentralFileHeaderList, ppHdr, _hdrList) {
			CentralFileHeader *pHdr = *ppHdr;
			if (!pHdr->Write(_sig, *_pStreamDst)) return false;
			//pHdr->Print();
		}
		unsigned long offsetOfCentralDirectory = static_cast<unsigned long>(offset);
		unsigned long sizeOfTheCentralDirectory =
								static_cast<unsigned long>(_pStreamDst->Tell() - offset);
		unsigned short nCentralFileHeaders = static_cast<unsigned short>(_hdrList.size());
		do {
			EndOfCentralDirectoryRecord rec;
			EndOfCentralDirectoryRecord::Fields &fields = rec.GetFields();
			XPackUShort(fields.NumberOfThisDisk,									0x0000);
			XPackUShort(fields.NumberOfTheDiskWithTheStartOfTheCentralDirectory,	0x0000);
			XPackUShort(fields.TotalNumberOfEntriesInTheCentralDirectoryOnThisDisk,	nCentralFileHeaders);
			XPackUShort(fields.TotalNumberOfEntriesInTheCentralDirectory,			nCentralFileHeaders);
			XPackULong(fields.SizeOfTheCentralDirectory,							sizeOfTheCentralDirectory);
			XPackULong(fields.OffsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber,
																offsetOfCentralDirectory);
			XPackUShort(fields.ZIPFileCommentLength,								0x0000);
			if (!rec.Write(_sig, *_pStreamDst)) return false;
		} while (0);
		_pStreamDst->Close();
		Stream::Delete(_pStreamDst);
		_pStreamDst = NULL;
		return !_sig.IsSignalled();
	}
	return true;
}

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

AScript_ImplementMethod(ZipFile, add)
{
	Object_ZipFile *pSelf = Object_ZipFile::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();
}

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

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

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

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

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

//-----------------------------------------------------------------------------
// Iterator_Each implementation
//-----------------------------------------------------------------------------
Iterator_Each::Iterator_Each(Object_ZipFile *pObjZipFile) :
								Iterator(false), _pObjZipFile(pObjZipFile)
{
	_ppHdr = pObjZipFile->GetHeaderList().begin();
}

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

bool Iterator_Each::DoNext(Environment &env, Signal sig, Value &value)
{
	if (_ppHdr == _pObjZipFile->GetHeaderList().end()) return false;
	const CentralFileHeader *pHdr = *_ppHdr;
	const CentralFileHeader::Fields &fields = pHdr->GetFields();
	Stream &streamSrc = _pObjZipFile->GetStreamSrc();
	long offset = XUnpackLong(fields.RelativeOffsetOfLocalHeader);
	Stream *pStream = CreateStream(sig, &streamSrc, pHdr);
	if (sig.IsSignalled()) return false;
	Object_Stream *pObjStream = new Object_Stream(env, pStream);
	value = Value(pObjStream);
	_ppHdr++;
	return true;
}

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

//-----------------------------------------------------------------------------
// AScript module functions: zip
//-----------------------------------------------------------------------------
// zipfile.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_ZipFile *pObjZipFile = new Object_ZipFile(sig);
	if (!pObjZipFile->Open(env, sig, pathName)) {
		Object::Delete(pObjZipFile);
		return Value::Null;
	}
	Value result(pObjZipFile);
	return ReturnValue(env, sig, args, result);
}

// zipfile.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_ZipFile *pObjZipFile = new Object_ZipFile(sig);
	if (!pObjZipFile->Create(env, sig, pathName)) {
		Object::Delete(pObjZipFile);
		return Value::Null;
	}
	Value result(pObjZipFile);
	return ReturnValue(env, sig, args, result);
}

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

AScript_ImplementFunction(test)
{
	return Value::Null;
}

// Module entry
AScript_ModuleEntry()
{
	// symbol realization
	AScript_RealizePrivSymbol(filename);
	AScript_RealizePrivSymbol(comment);
	AScript_RealizePrivSymbol(compression_method);
	AScript_RealizePrivSymbol(mtime);
	AScript_RealizePrivSymbol(crc32);
	AScript_RealizePrivSymbol(size);
	AScript_RealizePrivSymbol(compressed_size);
	AScript_RealizePrivSymbol(attributes);
	// class realization
	AScript_RealizePrivClass(ZipFile, "zipfile", 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 stream/directory factory
	DirectoryFactory::Register(new DirectoryFactory_ZIP());
}

AScript_ModuleTerminate()
{
}

//-----------------------------------------------------------------------------
// 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(filename))) {
		return Value(env, _hdr.GetFileName());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(comment))) {
		return Value(env, _hdr.GetFileComment());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(mtime))) {
		return Value(env, _hdr.GetLastModDateTime());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(crc32))) {
		return Value(env, _hdr.GetCrc32());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(compression_method))) {
		return Value(static_cast<unsigned long>(_hdr.GetCompressionMethod()));
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(size))) {
		return Value(static_cast<unsigned long>(_hdr.GetUncompressedSize()));
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(compressed_size))) {
		return Value(static_cast<unsigned long>(_hdr.GetCompressedSize()));
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(attributes))) {
		return Value(static_cast<unsigned long>(_hdr.GetExternalFileAttributes()));
	}
	evaluatedFlag = false;
	return Value::Null;
}

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

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

//-----------------------------------------------------------------------------
// Stream_ZIP implementation
//-----------------------------------------------------------------------------
Stream_ZIP::Stream_ZIP(Signal sig, Stream *pStreamSrc, const CentralFileHeader &hdr) :
	Stream(sig, ATTR_Readable), _pStreamSrc(pStreamSrc), _hdr(hdr),
	_name(hdr.GetFileName()), _bytesUncompressed(hdr.GetUncompressedSize()),
	_bytesCompressed(hdr.GetCompressedSize()), _crc32Expected(hdr.GetCrc32()) {}

Stream_ZIP::~Stream_ZIP()
{
	Stream::Delete(_pStreamSrc);
}

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

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

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

bool Stream_ZIP::DoClose(Signal sig)
{
	Stream::Delete(_pStreamSrc);
	_pStreamSrc = NULL;
	return true;
}

size_t Stream_ZIP::DoGetSize()
{
	return _bytesUncompressed;
}

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

//-----------------------------------------------------------------------------
// Stream_ZIP_Store implementation
// Compression method #0: stored (no compression)
//-----------------------------------------------------------------------------
Stream_ZIP_Store::Stream_ZIP_Store(Signal sig, Stream *pStreamSrc, const CentralFileHeader &hdr) :
	Stream_ZIP(sig, pStreamSrc, hdr), _offsetTop(pStreamSrc->Tell())
{
}

Stream_ZIP_Store::~Stream_ZIP_Store()
{
}

bool Stream_ZIP_Store::Initialize(Signal sig)
{
	return true;
}

size_t Stream_ZIP_Store::DoRead(Signal sig, void *buff, size_t bytes)
{
	size_t bytesRest = _bytesUncompressed - (_pStreamSrc->Tell() - _offsetTop);
	if (bytes > bytesRest) bytes = bytesRest;
	size_t bytesRead = _pStreamSrc->Read(sig, buff, bytes);
	_crc32.Update(buff, bytesRead);
	if (bytesRead == 0 && _crc32Expected != _crc32.GetResult()) {
		sig.SetError(ERR_FormatError, "CRC error");
	}
	return bytesRead;
}

bool Stream_ZIP_Store::DoSeek(Signal sig, long offset, size_t offsetPrev, SeekMode seekMode)
{
	if (seekMode == SeekSet) {
		return _pStreamSrc->Seek(sig, static_cast<long>(_offsetTop + offset), seekMode);
	} else if (seekMode == SeekCur) {
		return _pStreamSrc->Seek(sig, offset, seekMode);
	}
	return false;
}

//-----------------------------------------------------------------------------
// Stream_ZIP_Deflate implementation
// Compression method #8: Deflated
//-----------------------------------------------------------------------------
Stream_ZIP_Deflate::Stream_ZIP_Deflate(Signal sig, Stream *pStreamSrc, const CentralFileHeader &hdr) :
	Stream_ZIP(sig, pStreamSrc, hdr), _pStreamInflater(NULL)
{
}

Stream_ZIP_Deflate::~Stream_ZIP_Deflate()
{
	Stream::Delete(_pStreamInflater);
}

bool Stream_ZIP_Deflate::Initialize(Signal sig)
{
	_pStreamInflater = new ZLib::Stream_Inflater(sig,
						Stream::Reference(_pStreamSrc), _bytesCompressed);
	return _pStreamInflater->Initialize(sig, -MAX_WBITS);
}

size_t Stream_ZIP_Deflate::DoRead(Signal sig, void *buff, size_t bytes)
{
	size_t bytesRead = _pStreamInflater->Read(sig, buff, bytes);
	_crc32.Update(buff, bytesRead);
	if (bytesRead == 0 && _crc32Expected != _crc32.GetResult()) {
		sig.SetError(ERR_FormatError, "CRC error");
	}
	return bytesRead;
}

bool Stream_ZIP_Deflate::DoSeek(Signal sig, long offset, size_t offsetPrev, SeekMode seekMode)
{
	return _pStreamInflater->Seek(sig, offset, seekMode);
}

//-----------------------------------------------------------------------------
// Stream_ZIP_Deflate64 implementation
// Compression method #9: Enhanced Deflating using Deflate64(tm)
//-----------------------------------------------------------------------------
Stream_ZIP_Deflate64::Stream_ZIP_Deflate64(Signal sig, Stream *pStreamSrc, const CentralFileHeader &hdr) :
	Stream_ZIP(sig, pStreamSrc, hdr)
{
}

Stream_ZIP_Deflate64::~Stream_ZIP_Deflate64()
{
}

bool Stream_ZIP_Deflate64::Initialize(Signal sig)
{
	sig.SetError(ERR_SystemError, "this compression method is not implemented yet");
	return false;
}

size_t Stream_ZIP_Deflate64::DoRead(Signal sig, void *buff, size_t bytes)
{
	return 0;
}

bool Stream_ZIP_Deflate64::DoSeek(Signal sig, long offset, size_t offsetPrev, SeekMode seekMode)
{
	return false;
}

//-----------------------------------------------------------------------------
// Record_ZIP implementation
//-----------------------------------------------------------------------------
Record_ZIP::Record_ZIP(DirBuilder::Structure *pStructure, Record_ZIP *pParent,
								const char *name, bool containerFlag) :
	DirBuilder::Record(pStructure, pParent, name, containerFlag), _pHdr(NULL)
{
}

Record_ZIP::~Record_ZIP()
{
}

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

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

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

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

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

Stream *Directory_ZIP::DoOpenStream(Environment &env, Signal sig,
								unsigned long attr, const char *encoding)
{
	Stream *pStreamSrc = NULL;
	for (Directory *pDirectory = this; pDirectory != NULL;
											pDirectory = pDirectory->GetParent()) {
		if (pDirectory->IsBoundaryContainer()) {
			pStreamSrc = pDirectory->GetParent()->
							OpenStream(env, sig, Stream::ATTR_Readable, NULL);
			if (sig.IsSignalled()) return NULL;
			break;
		}
	}
	const CentralFileHeader *pHdr = _pRecord->GetCentralFileHeader();
	ASSUME(env, pHdr != NULL);
	Stream *pStream = CreateStream(sig, pStreamSrc, pHdr);
	Stream::Delete(pStreamSrc);
	return pStream;
}

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

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

Directory *DirectoryFactory_ZIP::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;
	if (!pStream->IsBwdSeekable()) {
		Stream *pStreamPrefetch = Stream::Prefetch(sig, pStream, true);
		if (sig.IsSignalled()) return NULL;
		pStream = pStreamPrefetch;
	}
	Value valStream(env, pStream);	// this works like std::auto_ptr
	DirBuilder::Structure *pStructure = new DirBuilder::Structure();
	pStructure->SetRoot(new Record_ZIP(pStructure, NULL, "", true));
	unsigned long offsetCentralDirectory = SeekCentralDirectory(sig, pStream);
	if (sig.IsSignalled()) return NULL;
	if (!pStream->Seek(sig, offsetCentralDirectory, Stream::SeekSet)) return NULL;
	unsigned long signature;
	while (ReadStream(sig, *pStream, &signature)) {
		//::printf("%08x\n", signature);
		if (signature == LocalFileHeader::Signature) {
			LocalFileHeader hdr;
			if (!hdr.Read(sig, *pStream)) return NULL;
			if (!hdr.SkipFileData(sig, *pStream)) return NULL;
		} else if (signature == ArchiveExtraDataRecord::Signature) {
			ArchiveExtraDataRecord record;
			if (!record.Read(sig, *pStream)) return NULL;
		} else if (signature == CentralFileHeader::Signature) {
			CentralFileHeader *pHdr = new CentralFileHeader();
			if (!pHdr->Read(sig, *pStream)) {
				delete pHdr;
				return NULL;
			}
			Record_ZIP *pRecord = dynamic_cast<Record_ZIP *>(
									pStructure->AddRecord(pHdr->GetFileName()));
			pRecord->SetCentralFileHeader(pHdr);
		} else if (signature == DigitalSignature::Signature) {
			DigitalSignature signature;
			if (!signature.Read(sig, *pStream)) return NULL;
		} else if (signature == Zip64EndOfCentralDirectory::Signature) {
			Zip64EndOfCentralDirectory dir;
			if (!dir.Read(sig, *pStream)) return NULL;
		} else if (signature == Zip64EndOfCentralDirectoryLocator::Signature) {
			Zip64EndOfCentralDirectoryLocator loc;
			if (!loc.Read(sig, *pStream)) return NULL;
		} else if (signature == EndOfCentralDirectoryRecord::Signature) {
			EndOfCentralDirectoryRecord record;
			if (!record.Read(sig, *pStream)) return NULL;
			break;
		} else {
			sig.SetError(ERR_FormatError, "unknown signature %08x", signature);
			return NULL;
		}
	}
	//pStream->Close();
	Directory *pDirectory = pStructure->GenerateDirectory(sig,
										pParent, pPathName, notFoundMode);
	DirBuilder::Structure::Delete(pStructure);
	return pDirectory;
}

//-----------------------------------------------------------------------------
// utilities
//-----------------------------------------------------------------------------
unsigned short GetDosTime(const DateTime &dt)
{
	return (static_cast<unsigned short>(dt.GetHour()) << 11) |
			(static_cast<unsigned short>(dt.GetMin()) << 5) |
			(static_cast<unsigned short>(dt.GetSec() / 2) << 0);
}

unsigned short GetDosDate(const DateTime &dt)
{
	return (static_cast<unsigned short>(dt.GetYear() - 1980) << 9) |
			(static_cast<unsigned short>(dt.GetMonth()) << 5) |
			(static_cast<unsigned short>(dt.GetDay()) << 0);
}

DateTime MakeDateTimeFromDos(unsigned short dosDate, unsigned short dosTime)
{
	short year = static_cast<short>((dosDate >> 9) + 1980);
	char month = static_cast<char>((dosDate >> 5) & 0xf);
	char day = static_cast<char>((dosDate >> 0) & 0x1f);
	long sec = static_cast<long>((dosTime >> 11) * 3600 +
				((dosTime >> 5) & 0x3f) * 60 + ((dosTime >> 0) & 0x1f) * 2);
	return DateTime(year, month, day, sec, 0);
}

unsigned long SeekCentralDirectory(Signal sig, Stream *pStream)
{
	size_t bytesZIPFile = pStream->GetSize();
	if (bytesZIPFile == InvalidSize) {
		sig.SetError(ERR_IOError, "can't seek end of file");
		return 0;
	}
	unsigned long offsetCentralDirectory = 0;
	if (bytesZIPFile < EndOfCentralDirectoryRecord::MinSize) {
		sig.SetError(ERR_FormatError, "can't find central directory record");
		return 0;
	}
	size_t bytesBuff = 0;
	if (bytesZIPFile <= EndOfCentralDirectoryRecord::MaxSize) {
		bytesBuff = bytesZIPFile;
	} else {
		long offsetStart = static_cast<long>(bytesZIPFile -
									EndOfCentralDirectoryRecord::MaxSize);
		if (!pStream->Seek(sig, offsetStart, Stream::SeekSet)) {
			return 0;
		}
		bytesBuff = EndOfCentralDirectoryRecord::MaxSize;
	}
	OAL::Memory memory;
	char *buff = reinterpret_cast<char *>(memory.Allocate(bytesBuff));
	pStream->Read(sig, buff, bytesBuff);
	if (sig.IsSignalled()) return NULL;
	char *buffAnchor = NULL;
	for (size_t i = 0; i <= bytesBuff - EndOfCentralDirectoryRecord::MinSize; i++) {
		if (::memcmp(buff + i, "\x50\x4b\x05\x06", 4) == 0) {
			buffAnchor = buff + i;
		}
	}
	if (buffAnchor == NULL) {
		sig.SetError(ERR_FormatError, "can't find central directory record");
		return 0;
	}
	EndOfCentralDirectoryRecord record;
	::memcpy(&record, buffAnchor, EndOfCentralDirectoryRecord::MinSize);
	return XUnpackULong(record.GetFields().
			OffsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber);
}

Stream *CreateStream(Signal sig, Stream *pStreamSrc, const CentralFileHeader *pHdr)
{
	Stream_ZIP *pStream = NULL;
	long offset = static_cast<long>(pHdr->GetRelativeOffsetOfLocalHeader());
	pStreamSrc->Seek(sig, offset, Stream::SeekSet);
	if (sig.IsSignalled()) return NULL;
	do {
		unsigned long signature;
		if (!ReadStream(sig, *pStreamSrc, &signature)) return NULL;
		if (signature != LocalFileHeader::Signature) {
			sig.SetError(ERR_FormatError, "invalid ZIP format");
			return NULL;
		}
		LocalFileHeader hdr;
		if (!hdr.Read(sig, *pStreamSrc)) return NULL;
	} while (0);
	//const char *name = pHdr->GetFileName();
	unsigned short compressionMethod = pHdr->GetCompressionMethod();
	//size_t bytesUncompressed = pHdr->GetUncompressedSize();
	//size_t bytesCompressed = pHdr->GetCompressedSize();
	//unsigned long crc32Expected = pHdr->GetCrc32();
	if (compressionMethod == METHOD_Store) {
		pStream = new Stream_ZIP_Store(sig, Stream::Reference(pStreamSrc), *pHdr);
	} else if (compressionMethod == METHOD_Shrink) {
		// unsupported
	} else if (compressionMethod == METHOD_Factor1) {
		// unsupported
	} else if (compressionMethod == METHOD_Factor2) {
		// unsupported
	} else if (compressionMethod == METHOD_Factor3) {
		// unsupported
	} else if (compressionMethod == METHOD_Factor4) {
		// unsupported
	} else if (compressionMethod == METHOD_Implode) {
		// unsupported
	} else if (compressionMethod == METHOD_Factor1) {
		// unsupported
	} else if (compressionMethod == METHOD_Deflate) {
		pStream = new Stream_ZIP_Deflate(sig, Stream::Reference(pStreamSrc), *pHdr);
	} else if (compressionMethod == METHOD_Deflate64) {
		pStream = new Stream_ZIP_Deflate64(sig, Stream::Reference(pStreamSrc), *pHdr);
	} else if (compressionMethod == METHOD_PKWARE) {
		// unsupported
	} else if (compressionMethod == METHOD_BZIP2) {
		// unsupported
	} else if (compressionMethod == METHOD_LZMA) {
		// unsupported
	} else if (compressionMethod == METHOD_TERSA) {
		// unsupported
	} else if (compressionMethod == METHOD_LZ77) {
		// unsupported
	} else if (compressionMethod == METHOD_WavPack) {
		// unsupported
	} else if (compressionMethod == METHOD_PPMd) {
		// unsupported
	}
	if (pStream == NULL) {
		sig.SetError(ERR_FormatError, "unsupported compression method %d",
															compressionMethod);
		return NULL;
	}
	if (!pStream->Initialize(sig)) {
		Stream::Delete(pStream);
		return NULL;
	}
	return pStream;
}

bool SkipStream(Signal sig, Stream &stream, size_t bytes)
{
	return stream.Seek(sig, static_cast<long>(bytes), Stream::SeekCur);
}

bool ReadStream(Signal sig, Stream &stream, void *buff, size_t bytes, size_t offset)
{
	if (bytes == 0) return true;
	size_t bytesRead = stream.Read(sig,
						reinterpret_cast<char *>(buff) + offset, bytes);
	if (sig.IsSignalled()) return false;
	if (bytesRead < bytes) {
		sig.SetError(ERR_FormatError, "invalid ZIP format");
		return false;
	}
	return true;
}

bool ReadStream(Signal sig, Stream &stream, unsigned long *pSignature)
{
	struct {
		XPackedULong_LE(Signature);
	} buff;
	if (!ReadStream(sig, stream, &buff, 4)) return false;
	*pSignature = XUnpackULong(buff.Signature);
	return true;
}

bool ReadStream(Signal sig, Stream &stream, Binary &binary, size_t bytes)
{
	if (bytes == 0) {
		binary = Binary();
		return true;
	}
	char *buff = new char[bytes];
	if (!ReadStream(sig, stream, buff, bytes)) {
		delete[] buff;
		return false;
	}
	binary = Binary(buff, bytes);
	delete[] buff;
	return true;
}

bool WriteStream(Signal sig, Stream &stream, void *buff, size_t bytes)
{
	stream.Write(sig, buff, bytes);
	return !sig.IsSignalled();
}

bool WriteStream(Signal sig, Stream &stream, Binary &binary)
{
	stream.Write(sig, binary.data(), binary.size());
	return !sig.IsSignalled();
}

//-----------------------------------------------------------------------------
// structures
//-----------------------------------------------------------------------------
bool LocalFileHeader::SkipOver(Signal sig, Stream &stream)
{
	if (!ReadStream(sig, stream, &_fields, 30 - 4, 4)) return false;
	if (IsExistDataDescriptor()) {
		sig.SetError(ERR_FormatError,
			"failed to skip local file header because it has data descriptor");
		return false;
	}
	size_t bytesToSkip = 0;
	bytesToSkip += XUnpackUShort(_fields.FileNameLength);
	bytesToSkip += XUnpackUShort(_fields.ExtraFieldLength);
	bytesToSkip += XUnpackULong(_fields.CompressedSize);
	if (!SkipStream(sig, stream, bytesToSkip)) return false;
	return true;
}

AScript_EndModule(zipfile, zipfile)

AScript_RegisterModule(zipfile)
