//-----------------------------------------------------------------------------
// AScript hash module
//-----------------------------------------------------------------------------
#include "Module_hash.h"

AScript_BeginModule(hash)

AScript_DeclarePrivSymbol(digest);
AScript_DeclarePrivSymbol(hexdigest);

//-----------------------------------------------------------------------------
// Object_Hash implementation
//-----------------------------------------------------------------------------
Object_Hash::Object_Hash(Environment &env, HashBase *pHash, const char *name) :
					Object_Stream(AScript_PrivClass(Hash), pHash), _name(name)
{
}

Object_Hash::Object_Hash(Class *pClass, HashBase *pHash, const char *name) :
					Object_Stream(pClass, pHash), _name(name)
{
}

Object_Hash::~Object_Hash()
{
}

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

Value Object_Hash::DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag)
{
	Environment &env = *this;
	if (pSymbol->IsIdentical(AScript_PrivSymbol(digest))) {
		evaluatedFlag = true;
		Value result;
		result.InitAsBinary(env, GetHash().GetDigest());
		return result;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(hexdigest))) {
		evaluatedFlag = true;
		const Binary &digest = GetHash().GetDigest();
		String str;
		foreach_const (Binary, p, digest) {
			char buff[8];
			::sprintf(buff, "%02x", static_cast<unsigned char>(*p));
			str += buff;
		}
		return Value(env, str.c_str());
	}
	return Value::Null;
}

String Object_Hash::ToString(Signal sig, bool exprFlag)
{
	String str = "<hash:";
	str += _name;
	str += ">";
	return str;
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Hash
//-----------------------------------------------------------------------------
// hash#init():reduce
AScript_DeclareMethod(Hash, init)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
}

AScript_ImplementMethod(Hash, init)
{
	HashBase &hash = Object_Hash::GetSelfObj(args)->GetHash();
	hash.Init();
	return args.GetSelf();
}

// hash#update(buff:binary):reduce
AScript_DeclareMethod(Hash, update)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "buff", VTYPE_Binary);
}

AScript_ImplementMethod(Hash, update)
{
	HashBase &hash = Object_Hash::GetSelfObj(args)->GetHash();
	const Binary &buff = args.GetBinary(0);
	hash.Write(sig, buff.data(), buff.size());
	return args.GetSelf();
}

// implementation of class Hash
AScript_ImplementPrivClass(Hash)
{
	AScript_AssignMethod(Hash, init);
	AScript_AssignMethod(Hash, update);
}

//-----------------------------------------------------------------------------
// HashBase implementation
//-----------------------------------------------------------------------------
size_t HashBase::DoRead(Signal sig, void *buff, size_t len)
{
	return 0;
}

bool HashBase::DoSeek(Signal sig, long offset, size_t offsetPrev, SeekMode seekMode) { return false; }
bool HashBase::DoFlush(Signal sig) { return true; }
bool HashBase::DoClose(Signal sig) { return true; }

//-----------------------------------------------------------------------------
// Hash_MD5 implementation
//-----------------------------------------------------------------------------
Hash_MD5::Hash_MD5(Signal sig) : HashBase(sig)
{
	_digest.clear();
	::md5_init(&_state);
}

void Hash_MD5::Init()
{
	_digest.clear();
	::md5_init(&_state);
}

const char *Hash_MD5::GetName() const
{
	return "<hash:md5>";
}

size_t Hash_MD5::DoWrite(Signal sig, const void *buff, size_t len)
{
	::md5_append(&_state, reinterpret_cast<const md5_byte_t *>(buff), static_cast<int>(len));
	return len;
}

void Hash_MD5::Finish()
{
	md5_byte_t digest[16];
	::md5_finish(&_state, digest);
	_digest = Binary(reinterpret_cast<char *>(digest), sizeof(digest));
}

const Binary &Hash_MD5::GetDigest()
{
	if (_digest.empty()) Finish();
	return _digest;
}

//-----------------------------------------------------------------------------
// Hash_SHA1 implementation
//-----------------------------------------------------------------------------
Hash_SHA1::Hash_SHA1(Signal sig) : HashBase(sig)
{
	_digest.clear();
}

void Hash_SHA1::Init()
{
	_digest.clear();
}

const char *Hash_SHA1::GetName() const
{
	return "<hash:sha1>";
}

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

void Hash_SHA1::Finish()
{
	//_digest = Binary(reinterpret_cast<char *>(digest), sizeof(digest));
}

const Binary &Hash_SHA1::GetDigest()
{
	if (_digest.empty()) Finish();
	return _digest;
}

//-----------------------------------------------------------------------------
// Hash_CRC32 implementation
//-----------------------------------------------------------------------------
Hash_CRC32::Hash_CRC32(Signal sig) : HashBase(sig)
{
	_digest.clear();
}

void Hash_CRC32::Init()
{
	_digest.clear();
	_crc32.Initialize();
}

const char *Hash_CRC32::GetName() const
{
	return "<hash:crc32>";
}

size_t Hash_CRC32::DoWrite(Signal sig, const void *buff, size_t len)
{
	_crc32.Update(buff, len);
	return len;
}

void Hash_CRC32::Finish()
{
	unsigned char digest[4];
	unsigned long result = _crc32.GetResult();
	digest[0] = static_cast<unsigned char>(result >> 24);
	digest[1] = static_cast<unsigned char>(result >> 16);
	digest[2] = static_cast<unsigned char>(result >> 8);
	digest[3] = static_cast<unsigned char>(result >> 0);
	_digest = Binary(reinterpret_cast<char *>(digest), sizeof(digest));
}

const Binary &Hash_CRC32::GetDigest()
{
	if (_digest.empty()) Finish();
	return _digest;
}

//-----------------------------------------------------------------------------
// AScript module functions: hash
//-----------------------------------------------------------------------------
// hash.md5(buff?:binary)
AScript_DeclareFunction(md5)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "buff", VTYPE_Binary, OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(md5)
{
	Object_Hash *pObj = new Object_Hash(env, new Hash_MD5(sig), "md5");
	if (args.IsBinary(0)) {
		const Binary &buff = args.GetBinary(0);
		HashBase &hash = pObj->GetHash();
		hash.Write(sig, buff.data(), buff.size());
	}
	return Value(pObj);
}

// hash.sha1(buff?:binary)
AScript_DeclareFunction(sha1)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "buff", VTYPE_Binary, OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(sha1)
{
	sig.SetError(ERR_SystemError, "not implemented yet"); return Value::Null;
#if 0
	Object_Hash *pObj = new Object_Hash(env, new Hash_SHA1(), "sha1");
	if (args.IsBinary(0)) {
		const Binary &buff = args.GetBinary(0);
		HashBase &hash = pObj->GetHash();
		hash.Write(sig, buff.data(), buff.size());
	}
	return Value(pObj);
#endif
}

// hash.crc32(buff?:binary)
AScript_DeclareFunction(crc32)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "buff", VTYPE_Binary, OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(crc32)
{
	Object_Hash *pObj = new Object_Hash(env, new Hash_CRC32(sig), "crc32");
	if (args.IsBinary(0)) {
		const Binary &buff = args.GetBinary(0);
		HashBase &hash = pObj->GetHash();
		hash.Write(sig, buff.data(), buff.size());
	}
	return Value(pObj);
}

// Module entry
AScript_ModuleEntry()
{
	AScript_RealizePrivSymbol(digest);
	AScript_RealizePrivSymbol(hexdigest);
	// function assignment
	AScript_AssignFunction(md5);
	AScript_AssignFunction(sha1);
	AScript_AssignFunction(crc32);
	// class realization
	AScript_RealizePrivClass(Hash, "hash", env.LookupClass(VTYPE_Stream));
}

AScript_ModuleTerminate()
{
}

AScript_EndModule(hash, hash)

AScript_RegisterModule(hash)
