//-----------------------------------------------------------------------------
// AScript gzip module
//-----------------------------------------------------------------------------
#include <ascript.h>
#include "ascript/ZLibHelper.h"

AScript_BeginModule(gzip)

//-----------------------------------------------------------------------------
// utilities
//-----------------------------------------------------------------------------
Object_Stream *GenerateDecompressor(Environment &env, Signal sig,
										Stream &stream, int windowBits)
{
	ZLib::Stream_Inflater *pStream = 
		new ZLib::Stream_Inflater(sig, Stream::Reference(&stream), InvalidSize);
	if (!pStream->Initialize(sig, windowBits)) {
		Stream::Delete(pStream);
		return NULL;
	}
	return new Object_Stream(env, pStream);
}

Object_Stream *GenerateCompressor(Environment &env, Signal sig,
										Stream &stream, int windowBits)
{
	ZLib::Stream_Deflater *pStream =
		new ZLib::Stream_Deflater(sig, Stream::Reference(&stream));
	if (!pStream->Initialize(sig, windowBits, 8)) {
		Stream::Delete(pStream);
		return NULL;
	}
	return new Object_Stream(env, pStream);
}

//-----------------------------------------------------------------------------
// AScript module functions: gzip
//-----------------------------------------------------------------------------
// bzip2.open(name:string, mode?:string, encoding:string => "utf-8")
AScript_DeclareFunction(open)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "name", VTYPE_String);
	DeclareArg(env, "mode", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareArg(env, "encoding", VTYPE_String, OCCUR_Once, false, false,
												new Expr_String("utf-8"));
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(open)
{
	unsigned long attr = Stream::ATTR_Readable;
	const char *name = args.GetString(0);
	if (*name == '>') {
		attr = Stream::ATTR_Writable;
		name++;
	}
	if (args.IsString(1)) {
		const char *p = args.GetString(1);
		if (*p == 'r') {
			attr = Stream::ATTR_Readable;
		} else if (*p == 'w') {
			attr = Stream::ATTR_Writable;
		} else {
			sig.SetError(ERR_IOError, "invalid open mode");
			return NULL;
		}
	}
	Stream *pStream = Directory::OpenStream(env, sig, name, attr, args.GetString(2));
	if (sig.IsSignalled()) return Value::Null;
	int windowBits = 31;
	Object_Stream *pObjStream;
	if (pStream->IsWritable()) {
		pObjStream = GenerateCompressor(env, sig, *pStream, windowBits);
	} else {
		pObjStream = GenerateDecompressor(env, sig, *pStream, windowBits);
	}
	if (sig.IsSignalled()) return Value::Null;
	Value result(pObjStream);
	if (args.IsBlockSpecified()) {
		const Function *pFuncBlock =
						args.GetBlockFunc(env, sig, GetSymbolForBlock());
		if (pFuncBlock == NULL) return Value::Null;
		ValueList valListArg(result);
		Args argsSub(valListArg);
		pFuncBlock->Eval(env, sig, argsSub);
		result = Value::Null;	// object is destroyed here
	}
	return result;
}

// gzip.decomp(stream:stream, winbits?:number)
AScript_DeclareFunction(decomp)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "stream", VTYPE_Stream);
	DeclareArg(env, "winbits", VTYPE_Number, OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(decomp)
{
	Stream &stream = args.GetStream(0);
	int windowBits = args.IsNumber(1)? args.GetInt(1) : 31;
	Object_Stream *pObjStream = GenerateDecompressor(env, sig, stream, windowBits);
	if (sig.IsSignalled()) return Value::Null;
	return Value(pObjStream);
}

// gzip.comp(stream:stream, winbits?:number)
AScript_DeclareFunction(comp)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "stream", VTYPE_Stream);
	DeclareArg(env, "winbits", VTYPE_Number, OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(comp)
{
	Stream &stream = args.GetStream(0);
	int windowBits = args.IsNumber(1)? args.GetInt(1) : 31;
	Object_Stream *pObjStream = GenerateCompressor(env, sig, stream, windowBits);
	if (sig.IsSignalled()) return Value::Null;
	return Value(pObjStream);
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Stream
//-----------------------------------------------------------------------------
// stream#gzipdecomp(winbits?:number)
AScript_DeclareMethod(Stream, gzipdecomp)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "winbits", VTYPE_Number, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Stream, gzipdecomp)
{
	Stream &stream = Object_Stream::GetSelfObj(args)->GetStream();
	int windowBits = args.IsNumber(1)? args.GetInt(1) : 31;
	Object_Stream *pObjStream = GenerateDecompressor(env, sig, stream, windowBits);
	if (sig.IsSignalled()) return Value::Null;
	return Value(pObjStream);
}

// stream#gzipcomp(winbits?:number)
AScript_DeclareMethod(Stream, gzipcomp)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "winbits", VTYPE_Number, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Stream, gzipcomp)
{
	Stream &stream = Object_Stream::GetSelfObj(args)->GetStream();
	int windowBits = args.IsNumber(1)? args.GetInt(1) : 31;
	Object_Stream *pObjStream = GenerateCompressor(env, sig, stream, windowBits);
	if (sig.IsSignalled()) return Value::Null;
	return Value(pObjStream);
}

// Module entry
AScript_ModuleEntry()
{
	// function assignment
	AScript_AssignFunction(open);
	AScript_AssignFunction(decomp);
	AScript_AssignFunction(comp);
	// method assignment to stream type
	AScript_AssignMethodTo(VTYPE_Stream, Stream, gzipdecomp);
	AScript_AssignMethodTo(VTYPE_Stream, Stream, gzipcomp);
}

AScript_ModuleTerminate()
{
}

AScript_EndModule(gzip, gzip)

AScript_RegisterModule(gzip)
