//
// The developer of the original code and/or files is Tripwire, Inc.
// Portions created by Tripwire, Inc. are copyright (C) 2000-2018 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;

///////////////////////////////////////////////////////////////////////////////
// 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.
    //

    // 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);

    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;
}
