//
// The developer of the original code and/or files is Tripwire, Inc.
// Portions created by Tripwire, Inc. are copyright (C) 2000 Tripwire,
// Inc. Tripwire is a registered trademark of Tripwire, Inc.  All rights
// reserved.
// 
// This program is free software.  The contents of this file are subject
// to the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your
// option) any later version.  You may redistribute it and/or modify it
// only in compliance with the GNU General Public License.
// 
// This program is distributed in the hope that it will be useful.
// However, this program is distributed AS-IS WITHOUT ANY
// WARRANTY; INCLUDING THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS
// FOR A PARTICULAR PURPOSE.  Please see the GNU General Public License
// for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.
// 
// Nothing in the GNU General Public License or any other license to use
// the code or files shall permit you to use Tripwire's trademarks,
// service marks, or other intellectual property without Tripwire's
// prior written consent.
// 
// If you have any questions, please contact Tripwire, Inc. at either
// info@tripwire.org or www.tripwire.org.
//
// serializerimpl.cpp
#include "stdcore.h"
#include "serializerimpl.h"
#include "debug.h"
#include "archive.h"
#include "ntmbs.h"         // for: eCharacterEncoding
#include "crc32.h"

// static members
cSerializerImpl::SerMap			cSerializerImpl::mSerCreateMap ;
cSerializerImpl::SerRefCountMap	cSerializerImpl::mSerRefCountCreateMap ;


// RAD:09/01/99 -- No longer needed!
///////////////////////////////////////////////////////////////////////////////
// util_TstrToStr -- converts the passed in tstring into a narrow string. maxSize
//		indicates the size of the narrow buffer
///////////////////////////////////////////////////////////////////////////////
//	static inline void util_TstrToStr(char* pStr, const TCHAR* pTstr, int maxSize)
//	{
//	#ifdef _UNICODE
//	    ASSERT( maxSize >= wcstombs( 0, pTstr
//	    wcstombs(pStr, pTstr, maxSize);
//	#else
//	    strncpy( pStr, pTstr, maxSize );
//	#endif
//	}
//	


///////////////////////////////////////////////////////////////////////////////
// util_GetCrc -- calculates the crc for the narrow version of the type's AsString()
///////////////////////////////////////////////////////////////////////////////
static uint32 util_GetCRC( const cType& type )
{
	// 
	// convert this to narrow...
	//

    // NOTE:RAD -- Fixed bug when going from TCHAR is Wide to Multibyte
    // 09/01/99  - Increased performance by returning byte len instead of
    //             recalculating again in the call to crcUpdate.
    //           - Increased performance by removing superflous conversion
    //             when type.AsString() is already narrow.
    //
#ifdef _UNICODE

    char sz[256];
	const uint8* pszType = (const uint8*)(&sz[0]);
    const wchar_t* wsz = type.AsString();

    ASSERT( countof(sz) >= ::wcstombs( 0, wsz, size_t(-1) ) );

    int nBytes = (int)::wcstombs( (char*)pszType, wsz, countof(sz) );
    if ( nBytes == -1 )     // Bad character conversion!
    {
        throw eCharacterEncoding( 
            TSS_GetString( cCore, core::STR_ERR_BADCHAR ) );
    }

#else//!_UNICODE

    // We only need to count the characters
    // RAD: Yeesh! This is already done for us in cType::mString!!!
    const uint8* pszType = (const uint8*)( type.AsString() );
    int nBytes = ::strlen( (const char*)pszType );

#endif//_UNICODE

    ASSERT( sizeof(uint8) == sizeof(byte) );
    ASSERT( pszType && *pszType );

    //
	// calculate the crc...
	//
	CRC_INFO crc;
	crcInit( crc );
    crcUpdate( crc, pszType, nBytes );
	crcFinit( crc );

	return crc.crc;
}

//#############################################################################
// class cSerializerImpl
//#############################################################################
cSerializerImpl::cSerializerImpl(cArchive& archive, Mode action, const TSTRING& fileName) :
	mpArchive(&archive),
	mMode(action),
	mbInit(false),
	mFileName( fileName )
{
}

cSerializerImpl::~cSerializerImpl()
{
}

bool cSerializerImpl::IsWriting() const
{
	return (mMode == S_WRITE);
}


///////////////////////////////////////////////////////////////////////////////
// static object registration functions; Exactly one of these should be called for
//		every object that is to be serialized.
///////////////////////////////////////////////////////////////////////////////

// TODO:dmb - following line doesn't do anything???
//std::allocator< std::pair< unsigned long, iTypedSerializable*(*)() > >;

void cSerializerImpl::RegisterSerializable(const cType& type, iTypedSerializable::CreateFunc pFunc)
{
	uint32 crc = util_GetCRC( type );

	if( cSerializerImpl::mSerCreateMap.find( crc ) != cSerializerImpl::mSerCreateMap.end() )
	{
		// duplicate entry!
		//
		ASSERT( false );
		TOSTRINGSTREAM str;
		str << _T("Duplicate entry in type table: ") << type.AsString() << std::endl;
		throw eInternal(str.str());
	}
 	cSerializerImpl::mSerCreateMap[crc] = pFunc;
}

void cSerializerImpl::RegisterSerializableRefCt(const cType& type, iSerRefCountObj::CreateFunc pFunc)
{
	uint32 crc = util_GetCRC( type );

	if( cSerializerImpl::mSerRefCountCreateMap.find( crc ) != cSerializerImpl::mSerRefCountCreateMap.end() )
	{
		// duplicate entry!
		//
		ASSERT( false );
		TOSTRINGSTREAM str;
		str << _T("Duplicate entry in type table: ") << type.AsString() << std::ends;
		throw eInternal(str.str());
	}
 	cSerializerImpl::mSerRefCountCreateMap[crc] = pFunc;
}


///////////////////////////////////////////////////////////////////////////////
// Init -- the job of init is to clear out the RefCountObj table, fill out the
//		mTypeArray, and  and write the header information
///////////////////////////////////////////////////////////////////////////////
void cSerializerImpl::Init()
{
	cDebug d("cSerializerImpl::Init");
	d.TraceDetail("Entering; IsWriting = %s\n", IsWriting() ? "true" : "false");

	mRefCtObjTbl.Clear();

	mbInit = true;
}

///////////////////////////////////////////////////////////////////////////////
// Finit 
///////////////////////////////////////////////////////////////////////////////
void cSerializerImpl::Finit()
{
	cDebug d("cSerializerImpl::Finit");
	d.TraceDetail("Exiting; IsWriting = %s\n", IsWriting() ? "true" : "false");
	mbInit = false;
}

///////////////////////////////////////////////////////////////////////////////
// WriteObjectDynCreate
///////////////////////////////////////////////////////////////////////////////
void cSerializerImpl::WriteObjectDynCreate(const iTypedSerializable* pObj)
{
	ASSERT(mbInit);
	ASSERT(IsWriting());
	cDebug d("cSerializerImpl::WriteObjectDynCreate");
	// CurrentPos() only works with bidir archive
	//d.TraceDetail("Entering...  Archive Offset = %d\n", mpArchive->CurrentPos());
	d.TraceDetail(_T("             Object Type    = %s\n"), pObj->GetType().AsString());
	// first, we write out the header, which consists of the following:
	// uint32	crc of the object's type
	// int32	version of stored data
	// int32	size of the chunk (counting from this point; not including the previous int32
	// int32	index into mRefCountObjTbl, or 0 if it isn't refrence counted.
	//				if the index already exists, then no data follows; a refrence
	//				should just be added to the existing object.
	ASSERT(mpArchive != 0);

	// get the ident for this class type
	//
	uint32 crc = util_GetCRC( pObj->GetType() );

	//
	// make sure this type is registered, and figure out if it is refrence counted
	//
	SerRefCountMap::iterator i = mSerRefCountCreateMap.find( crc );
	bool bRefCount = true;
	if( i == mSerRefCountCreateMap.end() )
	{
		//
		// maybe it is not refrence counted...
		//
		SerMap::iterator si = mSerCreateMap.find( crc );
		if( si == mSerCreateMap.end() )
		{
			d.TraceError("Attempt to serialize unregistered type : %s\n", pObj->GetType().AsString());
			ThrowAndAssert(eSerializerUnknownType( pObj->GetType().AsString(), mFileName, eSerializer::TY_FILE ));
		}
		bRefCount = false;
	}

	mpArchive->WriteInt32(crc);
	mpArchive->WriteInt32(pObj->Version());
	// write a placeholder for the size; we will come back and fill in the right value later...
	mpArchive->WriteInt32(0xffffffff);
	
	if(bRefCount)
	{
		// see if the object has already been serialized...
		int idx;
		if((idx = mRefCtObjTbl.Lookup(static_cast<const iSerRefCountObj*>(pObj))) == 0)
		{
			// it is not in the table yet; add it and serialize the object
			idx = mRefCtObjTbl.Add(static_cast<const iSerRefCountObj*>(pObj));
			d.TraceDetail("Object [%d] written for first time...\n", idx);
			mpArchive->WriteInt32(idx);
			pObj->Write(this);
		}
		else
		{
			// since it is already in the table, just write the index number
			d.TraceDetail("Object [%d] already serialized; incrementing refrence count.\n", idx);
			mpArchive->WriteInt32(idx);
		}
	}
	else
	{
		// not reference counted ... just write 0
		mpArchive->WriteInt32(0);
		// ... then write the object.
		pObj->Write(this);

	}
}

///////////////////////////////////////////////////////////////////////////////
// ReadObjectDynCreate
///////////////////////////////////////////////////////////////////////////////
iTypedSerializable*	cSerializerImpl::ReadObjectDynCreate()
{
	cDebug d("cSerializerImpl::ReadObjectDynCreate");
	//d.TraceDetail("Entering... archive offset = %d\n", mpArchive->CurrentPos());

	int32 size, objIdx; 
	uint32 crc;
	// first, get the type...
	mpArchive->ReadInt32(reinterpret_cast<int32&>(crc));

	// read in the version
	int32 version;
	mpArchive->ReadInt32(version);

	// read in the size and the index...
	mpArchive->ReadInt32(size);

	// Save the position so we can seek correctly later on
	//int64 sizePos = mpArchive->CurrentPos();

	mpArchive->ReadInt32(objIdx);
	if(objIdx == 0)
	{
		// the object is not reference counted; create and read in the object
		// first, we need to create the object.
		SerMap::iterator si;
		si = mSerCreateMap.find(crc);
		if(si == mSerCreateMap.end())
		{
			// unable to find the creation function...
			d.TraceError("Unable to find creation function for non-ref counted object %d\n", crc);
			TOSTRINGSTREAM str;
            #ifdef _DEBUG
            // Let's only report the actual crc in debug mode
			str << (int32)crc << std::ends;
            #endif
			ThrowAndAssert(eSerializerUnknownType(str.str(), mFileName, eSerializer::TY_FILE));
		}
		iTypedSerializable* pObj = ((*si).second)();
		d.TraceDetail("Created non-ref counted object %s(%p)\n", pObj->GetType().AsString(), pObj);
		pObj->Read(this, version);

		// seek past this object in case filepos is not correct!
		//mpArchive->Seek(sizePos + size, cBidirArchive::BEGINNING);
		
		return pObj;
	}
	else
	{
		// refrence counted...
		iSerRefCountObj* pObj;
		pObj = mRefCtObjTbl.Lookup(objIdx);
		if(pObj == NULL)
		{
			// not in table yet...first find the creation function
			SerRefCountMap::iterator rci;
			rci = mSerRefCountCreateMap.find(crc);
			if(rci == mSerRefCountCreateMap.end())
			{
				// unable to find the creation function...
				d.TraceError("Unable to find creation function for ref counted object %d\n", crc);
				TOSTRINGSTREAM str;
				str << (int32)crc << std::ends;
				ThrowAndAssert(eSerializerUnknownType(str.str(), mFileName, eSerializer::TY_FILE));
			}
			pObj = ((*rci).second)();
			d.TraceDetail("Creating Ref-Coutnted object [%d] %s(%p)\n", objIdx, pObj->GetType().AsString(), pObj);
			pObj->Read(this);
			mRefCtObjTbl.Add(pObj, objIdx);
		
			// seek past this object in case filepos is not correct!
			//mpArchive->Seek(sizePos + size, cBidirArchive::BEGINNING);

			return pObj;
		}
		else
		{
			// already serialized; just return this object.
			d.TraceDetail("Adding refrence to previously serialized object [%d] %s(%p)\n", objIdx, pObj->GetType().AsString(), pObj);
			pObj->AddRef();
		}
		
		// seek past this object in case filepos is not correct!
		//mpArchive->Seek(sizePos + size, cBidirArchive::BEGINNING);

		return pObj;
	}
}

///////////////////////////////////////////////////////////////////////////////
// WriteObect, ReadObject
///////////////////////////////////////////////////////////////////////////////
void cSerializerImpl::WriteObject(const iTypedSerializable* pObj)
{
	ASSERT(pObj != 0);
	//ASSERT(mbInit); // this isn't needed if we are not writing the type array
	ASSERT(IsWriting());
	cDebug d("cSerializerImpl::WriteObject");
	//d.TraceDetail("Entering...  Archive Offset = %d\n", mpArchive->CurrentPos());
	d.TraceDetail("             Object Type    = %s\n", pObj->GetType().AsString());
	// first, we write out the header, which consists of the following:
	// int32 refrence into mTypeArray, indicating the object's type
	// int32 version of stored data
	// int32 size of the chunk (counting from this point; not including the previous int32
	// data  the object data
	ASSERT(mpArchive != 0);

	
	// 5Nov 98 mdb -- I am removing the read and write of type info for this method; it is never used, since
	// the object is already created when ReadObject() happens. The only good it might serve is in asserting that
	// the input stream is valid, but I can do that with the 0xffffffff size below (asserting that it is the same
	// when it is read back in)
/*	int i = 0;
	for(; i < mTypeArray.size(); i++)
	{
		if(pObj->GetType() == *mTypeArray[i])
		{
			// aha! this is it!
			break;
		}
	}
	if(i == mTypeArray.size())
	{
		// the type was not registered; 
		// a debate exists in my mind as to whether this should be an exception or an assertion -- mdb
		d.TraceError("Attempt to serialize unregistered type : %s\n", pObj->GetType().AsString());
		ThrowAndAssert(eSerializerUnknownType(pObj->GetType().AsString()));
	}
	mpArchive->WriteInt32(i);
*/

	mpArchive->WriteInt32(0);	// place holder for type array index
	mpArchive->WriteInt32(pObj->Version());
	// write a placeholder for the size; we will come back and fill in the right value later...
	//int64 sizePos = mpArchive->CurrentPos();
	mpArchive->WriteInt32(0xffffffff);
	
	// write out the object! 
	pObj->Write(this);

	// finally, we need to go back and patch up the size...
	//int64 returnPos = mpArchive->CurrentPos();
	//mpArchive->Seek(sizePos, cBidirArchive::BEGINNING);
	//mpArchive->WriteInt32((int32)(returnPos - sizePos - sizeof(int32)));
	//mpArchive->Seek(returnPos, cBidirArchive::BEGINNING);
}

void cSerializerImpl::ReadObject(iTypedSerializable* pObj)
{
	cDebug d("cSerializerImpl::ReadObjectDynCreate");
	//d.TraceDetail("Entering... archive offset = %d\n", mpArchive->CurrentPos());

	// NOTE -- type index stuff is gone; see the comment in WriteObject()
	int32 /*typeIdx,*/ size; 
	// first, get the type...
	/*
	mpArchive->ReadInt32(typeIdx);
	if((typeIdx < 0) || (typeIdx >= mTypeArray.size()))
	{
		// unknown type index!
		d.TraceError("Encountered bad index into TypeArray: %d\n", typeIdx);
		ThrowAndAssert(eSerializerInputStremTypeArray());
	}
	const cType* pType = mTypeArray[typeIdx];
	*/

	// read in the version
	int32 dummy, version;
	mpArchive->ReadInt32(dummy);	// old type array index
	mpArchive->ReadInt32(version);

	// read in the size and the index...
	mpArchive->ReadInt32(size);

	// use the size to assert that the file format is correct
	if(size != (int)0xffffffff)
	{
		// unknown type index!
		d.TraceError("Encountered bad size: %d\n", size);
		ThrowAndAssert(eSerializerInputStremTypeArray(_T(""), mFileName, eSerializer::TY_FILE));
	}

	// remember current position
	//int64 sizePos = mpArchive->CurrentPos();

	// read in the object!
	pObj->Read(this, version);

	// seek past this object in case filepos is not correct!
	//mpArchive->Seek(sizePos + size, cBidirArchive::BEGINNING);
}


///////////////////////////////////////////////////////////////////////////////
// archive wrapper
///////////////////////////////////////////////////////////////////////////////
void cSerializerImpl::ReadInt16(int16& ret)
{
	mpArchive->ReadInt16(ret);
}

void cSerializerImpl::ReadInt32(int32& ret)
{
	mpArchive->ReadInt32(ret);
}

void cSerializerImpl::ReadInt64(int64& ret)
{
	mpArchive->ReadInt64(ret);
}

void cSerializerImpl::ReadString(TSTRING& ret)
{
	mpArchive->ReadString(ret);
}

int cSerializerImpl::ReadBlob(void* pBlob, int count)
{
	return mpArchive->ReadBlob(pBlob, count);
}

void cSerializerImpl::WriteInt16(int16 i)
{
	mpArchive->WriteInt16(i);
}

void cSerializerImpl::WriteInt32(int32 i)
{
	mpArchive->WriteInt32(i);
}

void cSerializerImpl::WriteInt64(int64 i)
{
	mpArchive->WriteInt64(i);
}

void cSerializerImpl::WriteString(const TSTRING& s)
{
	mpArchive->WriteString(s);
}

void cSerializerImpl::WriteBlob(const void* pBlob, int count)
{
	mpArchive->WriteBlob(pBlob, count);
}

///////////////////////////////////////////////////////////////////////////////
// GetFileName
///////////////////////////////////////////////////////////////////////////////
TSTRING cSerializerImpl::GetFileName() const
{
	return mFileName;	
}

