/*
===========================================================================
Copyright (C) 2015 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code is free software; you can redistribute it
and/or modify it under 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.

OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

// archive.cpp: OpenMoHAA Archiver

#include "glb_local.h"
#include "archive.h"
#include "level.h"
#include <lz77.h>

#ifdef GAME_DLL
#    include "../fgame/entity.h"
#endif

enum {
    ARC_NULL,
    ARC_Vector,
    ARC_Vec2,
    ARC_Vec3,
    ARC_Vec4,
    ARC_Integer,
    ARC_Unsigned,
    ARC_Byte,
    ARC_Char,
    ARC_Short,
    ARC_UnsignedShort,
    ARC_Float,
    ARC_Double,
    ARC_Boolean,
    ARC_String,
    ARC_Raw,
    ARC_Object,
    ARC_ObjectPointer,
    ARC_SafePointer,
    ARC_EventPointer,
    ARC_Quat,
    ARC_Entity,
    ARC_Bool,
    ARC_Position,
    ARC_Size,
    ARC_NUMTYPES
};

static const char *typenames[] = {
    "NULL",   "vector",        "vec2",           "vec3",         "vec4",       "int",      "unsigned", "byte",
    "char",   "short",         "unsigned short", "float",        "double",     "qboolean", "string",   "raw data",
    "object", "objectpointer", "safepointer",    "eventpointer", "quaternion", "entity",   "bool",     "position",
    "size"};

#define ArchiveHeader  (LittleLong(*(int *)"MHAA"))
#define ArchiveVersion 14                             // This must be changed any time the format changes!
#define ArchiveInfo    "OPENMOHAA Archive Version 14" // This must be changed any time the format changes!

ArchiveFile::ArchiveFile()
{
    length       = 0;
    buffer       = 0;
    pos          = 0;
    bufferlength = 0;
    writing      = 0;
    opened       = 0;
}

ArchiveFile::~ArchiveFile()
{
    Close();
}

void ArchiveFile::Close()
{
    if (writing) {
        gi.FS_WriteFile(filename.c_str(), buffer, length);
    }

    if (buffer) {
        gi.Free((void *)buffer);
        buffer = NULL;
    }

    writing  = false;
    filename = "";
    length   = 0;
    pos      = 0;
}

const char *ArchiveFile::Filename(void)
{
    return filename.c_str();
}

qboolean ArchiveFile::Compress()
{
#ifdef Q3_BIG_ENDIAN
    // FIXME: Decompressing crashes on big-endian architectures
    return false;
#endif

    byte  *tempbuf;
    size_t out_len;
    size_t tempbuf_len;

    tempbuf_len = (length >> 6) + length + 27;
    tempbuf     = (byte *)gi.Malloc(tempbuf_len);

    // Set the signature
    tempbuf[0]               = 'C';
    tempbuf[1]               = 'S';
    tempbuf[2]               = 'V';
    tempbuf[3]               = 'G';
    *(size_t *)(tempbuf + 4) = length;

    // Compress the data
    if (g_lz77.Compress(buffer, length, tempbuf + 8, &out_len)) {
        gi.Error(ERR_DROP, "Compression of SaveGame Failed!\n");
        return false;
    }

    gi.Free(buffer);
    buffer       = tempbuf;
    length       = out_len + 8;
    bufferlength = tempbuf_len;

    return true;
}

size_t ArchiveFile::Length(void)
{
    return length;
}

size_t ArchiveFile::Pos(void)
{
    return pos - buffer;
}

size_t ArchiveFile::Tell(void)
{
    return pos - buffer;
}

qboolean ArchiveFile::Seek(size_t newpos)
{
    if (!buffer) {
        return false;
    }

    if (newpos > length) {
        return false;
    }

    pos = buffer + newpos;

    return true;
}

qboolean ArchiveFile::OpenRead(const char *name)
{
    byte *tempbuf;
    assert(name);

    assert(!buffer);
    Close();

    if (!name) {
        return false;
    }

    length = gi.FS_ReadFile(name, (void **)&tempbuf, qtrue);
    if (length == (size_t)(-1) || length == 0) {
        return false;
    }
    // create our own space
    buffer       = (byte *)gi.Malloc(length);
    bufferlength = length;
    // copy the file over to our space
    memcpy(buffer, tempbuf, length);
    // free the file
    gi.FS_FreeFile(tempbuf);
    // set the file name
    filename = name;

    pos     = buffer;
    writing = false;
    opened  = true;

    char FileHeader[4];
    Read(FileHeader, sizeof(FileHeader));

    if (FileHeader[0] != 'C' || FileHeader[1] != 'S' || FileHeader[2] != 'V' || FileHeader[3] != 'G') {
        pos = buffer;
    } else {
        uint32_t new_len;
        size_t   iCSVGLength;

        new_len = 0;
        Read(&new_len, sizeof(uint32_t));
        new_len = LittleLong(new_len);
        tempbuf = (byte *)gi.Malloc(new_len);

        if (g_lz77.Decompress(pos, length - 8, tempbuf, &iCSVGLength) || iCSVGLength != new_len) {
            gi.Error(ERR_DROP, "Decompression of save game failed\n");
            return false;
        }

        gi.Free(buffer);

        buffer       = tempbuf;
        length       = iCSVGLength;
        bufferlength = length;
        pos          = buffer;
    }

    return true;
}

qboolean ArchiveFile::OpenWrite(const char *name)
{
    this->length = 0;
    // 4 MiB buffer
    this->bufferlength = 4 * 1024 * 1024;
    this->buffer       = (byte *)gi.Malloc(bufferlength);
    this->filename     = name;
    this->pos          = buffer;
    this->writing      = true;
    this->opened       = true;

    return true;
}

qboolean ArchiveFile::Read(void *dest, size_t size)
{
    if (!size) {
        return false;
    }

    if ((pos + size) > (buffer + length)) {
        return false;
    }

    memcpy(dest, pos, size);
    pos += size;

    return true;
}

qboolean ArchiveFile::Write(const void *source, size_t size)
{
    if ((pos + size) > (buffer + bufferlength)) {
        byte *oldbuf;

        do {
            bufferlength *= 2;
        } while ((pos + size) > (buffer + bufferlength));

        oldbuf = buffer;
        // reallocate a bigger buffer
        buffer = (byte *)gi.Malloc(bufferlength);
        memcpy(buffer, oldbuf, length);
        // free the old buffer
        gi.Free(oldbuf);
        // set the position with the new buffer
        pos = buffer + (pos - oldbuf);
    }

    memcpy(pos, source, size);
    pos += size;

    if (length < (pos - buffer)) {
        length = (pos - buffer);
    }

    return true;
}

Archiver::Archiver()
{
    archivemode = ARCHIVE_WRITE;
    fileerror   = false;
    harderror   = true;
    Reset();
    silent = false;

    assert((sizeof(typenames) / sizeof(typenames[0])) == ARC_NUMTYPES);
}

Archiver::~Archiver()
{
    if (archivemode != ARCHIVE_NONE) {
        Close();
    }
}

void Archiver::FileError(const char *fmt, ...)
{
    va_list argptr;
    char    text[1024];

    va_start(argptr, fmt);
    Q_vsnprintf(text, sizeof(text), fmt, argptr);
    va_end(argptr);

    fileerror = true;
    Close();
    if (archivemode == ARCHIVE_READ) {
        if (harderror) {
            gi.Error(ERR_DROP, "Error while loading %s : %s\n", filename.c_str(), text);
        } else if (!silent) {
            gi.Printf("Error while loading %s : %s\n", filename.c_str(), text);
        }
    } else if (archivemode == ARCHIVE_WRITE) {
        if (harderror) {
            gi.Error(ERR_DROP, "Error while writing to %s : %s\n", filename.c_str(), text);
        } else if (!silent) {
            gi.Printf("Error while writing to %s : %s\n", filename.c_str(), text);
        }
    } else {
        if (harderror) {
            gi.Error(ERR_DROP, "Error while neither reading nor writing: %s\n", text);
        } else {
            gi.Printf("Error while neither reading nor writing: %s\n", text);
        }
    }
}

void Archiver::Close(void)
{
    if (archivemode == ARCHIVE_NONE) {
        // nothing to process
        return;
    }

    if (archivemode == ARCHIVE_WRITE) {
        int    numobjects;
        size_t pos;

        // write out the number of classpointers
        pos = archivefile.Tell();
        archivefile.Seek(numclassespos);
        numobjects = classpointerList.NumObjects();
        ArchiveInteger(&numobjects);
        // compress the file
        archivefile.Seek(pos);
        archivefile.Compress();
    }

    archivefile.Close();

    if (archivemode == ARCHIVE_READ) {
        int              i, num;
        pointer_fixup_t *fixup;

        num = fixupList.NumObjects();
        for (i = 1; i <= num; i++) {
            fixup = fixupList.ObjectAt(i);
            if (fixup->type == pointer_fixup_ptr) {
                LightClass **fixupptr;
                fixupptr  = (LightClass **)fixup->ptr;
                *fixupptr = classpointerList.ObjectAt(fixup->index);
            } else if (fixup->type == pointer_fixup_normal) {
                Class **fixupptr;
                fixupptr  = (Class **)fixup->ptr;
                *fixupptr = static_cast<Class *>(classpointerList.ObjectAt(fixup->index));
            } else if (fixup->type == pointer_fixup_safe) {
                SafePtrBase *fixupptr;
                fixupptr = (SafePtrBase *)fixup->ptr;
                fixupptr->InitSafePtr(static_cast<Class *>(classpointerList.ObjectAt(fixup->index)));
            }
            delete fixup;
        }
        fixupList.FreeObjectList();
        classpointerList.FreeObjectList();
    }

    archivemode = ARCHIVE_NONE;
}

/****************************************************************************************

File Read/Write functions

*****************************************************************************************/

qboolean Archiver::Read(const char *name, qboolean harderror)
{
    unsigned header;
    unsigned version;
    str      info;
    int      num;
    int      i;
    Class   *null;

    this->harderror   = harderror;
    this->fileerror   = false;
    this->archivemode = ARCHIVE_READ;
    this->filename    = name;

    if (!archivefile.OpenRead(filename.c_str())) {
        if (harderror) {
            FileError("Couldn't open file.");
        }
        return false;
    }

    ArchiveUnsigned(&header);
    if (header != ArchiveHeader) {
        archivefile.Close();
        FileError("Not a valid MOHAA archive.");
        return false;
    }

    ArchiveUnsigned(&version);
    if (version > ArchiveVersion) {
        archivefile.Close();
        FileError("Archive is from version %u.  Check http://www.x-null.net for an update.", version);
        return false;
    }

    if (version < ArchiveVersion) {
        archivefile.Close();
        FileError("Archive is out of date.");
        return false;
    }

    ArchiveString(&info);
    gi.DPrintf("%s\n", info.c_str());

    // setup out class pointers
    ArchiveInteger(&num);
    classpointerList.Resize(num);
    null = NULL;
    for (i = 1; i <= num; i++) {
        classpointerList.AddObject(null);
    }

    return true;
}

qboolean Archiver::Create(const char *name, qboolean harderror)
{
    unsigned header;
    unsigned version;
    str      info;
    int      numZero = 0;

    this->harderror   = harderror;
    this->fileerror   = false;
    this->archivemode = ARCHIVE_WRITE;
    this->filename    = name;

    if (!archivefile.OpenWrite(filename.c_str())) {
        FileError("Couldn't open file.");
        return false;
    }

    header = ArchiveHeader;
    ArchiveUnsigned(&header);
    version = ArchiveVersion;
    ArchiveUnsigned(&version);
    info = ArchiveInfo;
    ArchiveString(&info);

    numclassespos = archivefile.Tell();
    ArchiveInteger(&numZero);
    Reset();

    return true;
}

/****************************************************************************************

File Archive functions

*****************************************************************************************/

//#define ARCHIVE_USE_TYPES 1

template<typename v>
void ArchiveSwapValue(v* value) {
    LittleSwap(value, sizeof(v));
}

template<typename v>
void ArchiveSwapValue(v* value, size_t size) {
    for (size_t i = 0; i < size; i++) {
        LittleSwap(&value[i], sizeof(value[i]));
    }
}

template<>
void ArchiveSwapValue<Vector>(Vector* value) {
    for (int i = 0; i < 3; i++) {
        (*value)[i] = LittleFloat((*value)[i]);
    }
}

template<>
void ArchiveSwapValue<Quat>(Quat* value) {
    for (int i = 0; i < 4; i++) {
        (*value)[i] = LittleFloat((*value)[i]);
    }
}

#define ARCHIVE(func, type)                             \
    void Archiver::Archive##func(type *v)               \
    {                                                   \
        if (archivemode == ARCHIVE_WRITE) {             \
            type nv = *v;                               \
            ArchiveSwapValue(&nv);                      \
            ArchiveData(ARC_##func, &nv, sizeof(type)); \
        } else {                                        \
            ArchiveData(ARC_##func, v, sizeof(type));   \
            ArchiveSwapValue(v);                        \
        }                                               \
    }

ARCHIVE(Vector, Vector);
ARCHIVE(Integer, int);
ARCHIVE(Unsigned, unsigned);
ARCHIVE(Byte, byte);
ARCHIVE(Char, char);
ARCHIVE(Short, short);
ARCHIVE(UnsignedShort, unsigned short);
ARCHIVE(Float, float);
ARCHIVE(Double, double);
ARCHIVE(Boolean, qboolean);
ARCHIVE(Quat, Quat);
ARCHIVE(Bool, bool);
ARCHIVE(Position, int);
ARCHIVE(Size, long);

void Archiver::ArchiveSvsTime(int *time)
{
#ifdef GAME_DLL
    if (archivemode == ARCHIVE_READ) {
        ArchiveInteger(time);
        gi.AddSvsTimeFixup(time);
    } else {
        *time -= level.svsTime;
        ArchiveInteger(time);
        *time += level.svsTime;
    }
#endif
}

void Archiver::ArchiveVec2(vec2_t vec)
{
    if (archivemode == ARCHIVE_WRITE) {
        vec2_t nv = { vec[0], vec[1] };
        ArchiveSwapValue(nv, 2);
        ArchiveData(ARC_Vec2, nv, sizeof(vec2_t));
    } else {
        ArchiveData(ARC_Vec2, vec, sizeof(vec2_t));
        ArchiveSwapValue(vec, 2);
    }
}

void Archiver::ArchiveVec3(vec3_t vec)
{
    if (archivemode == ARCHIVE_WRITE) {
        vec3_t nv = { vec[0], vec[1], vec[2]};
        ArchiveSwapValue(nv, 3);
        ArchiveData(ARC_Vec3, nv, sizeof(vec3_t));
    } else {
        ArchiveData(ARC_Vec3, vec, sizeof(vec3_t));
        ArchiveSwapValue(vec, 3);
    }
}

void Archiver::ArchiveVec4(vec4_t vec)
{
    if (archivemode == ARCHIVE_WRITE) {
        vec4_t nv = { vec[0], vec[1], vec[2], vec[3] };
        ArchiveSwapValue(nv, 4);
        ArchiveData(ARC_Vec4, nv, sizeof(vec4_t));
    } else {
        ArchiveData(ARC_Vec4, vec, sizeof(vec4_t));
        ArchiveSwapValue(vec, 4);
    }
}

void Archiver::ArchiveObjectPointer(LightClass **ptr)
{
    int index = 0;

    if (archivemode == ARCHIVE_READ) {
        pointer_fixup_t *fixup;

        ArchiveData(ARC_ObjectPointer, &index, sizeof(index));
        index = LittleLong(index);

        //
        // see if the variable was NULL
        //
        if (index == ARCHIVE_NULL_POINTER) {
            *ptr = NULL;
        } else {
            // init the pointer with NULL until we can fix it
            *ptr = NULL;

            fixup        = new pointer_fixup_t;
            fixup->ptr   = (void **)ptr;
            fixup->index = index;
            fixup->type  = pointer_fixup_ptr;
            fixupList.AddObject(fixup);
        }
    } else {
        if (*ptr) {
            index = classpointerList.AddUniqueObject(*ptr);
        } else {
            index = ARCHIVE_NULL_POINTER;
        }

        index = LittleLong(index);
        ArchiveData(ARC_ObjectPointer, &index, sizeof(index));
    }
}

void Archiver::ArchiveObjectPointer(Class **ptr)
{
    int index = 0;

    if (archivemode == ARCHIVE_READ) {
        pointer_fixup_t *fixup;

        ArchiveData(ARC_ObjectPointer, &index, sizeof(index));
        index = LittleLong(index);

        //
        // see if the variable was NULL
        //
        if (index == ARCHIVE_NULL_POINTER) {
            *ptr = NULL;
        } else {
            // init the pointer with NULL until we can fix it
            *ptr = NULL;

            fixup        = new pointer_fixup_t;
            fixup->ptr   = (void **)ptr;
            fixup->index = index;
            fixup->type  = pointer_fixup_normal;
            fixupList.AddObject(fixup);
        }
    } else {
        if (*ptr) {
            index = classpointerList.AddUniqueObject(*ptr);
        } else {
            index = ARCHIVE_NULL_POINTER;
        }

        index = LittleLong(index);
        ArchiveData(ARC_ObjectPointer, &index, sizeof(index));
    }
}

void Archiver::ArchiveSafePointer(SafePtrBase *ptr)
{
    int index = 0;

    if (archivemode == ARCHIVE_READ) {
        pointer_fixup_t *fixup;

        ArchiveData(ARC_SafePointer, &index, sizeof(index));
        index = LittleLong(index);

        //
        // see if the variable was NULL
        //
        if (index == ARCHIVE_NULL_POINTER) {
            ptr->InitSafePtr(NULL);
        } else {
            // init the pointer with NULL until we can fix it
            ptr->InitSafePtr(NULL);

            // Add new fixup
            fixup        = new pointer_fixup_t;
            fixup->ptr   = (void **)ptr;
            fixup->index = index;
            fixup->type  = pointer_fixup_safe;
            fixupList.AddObject(fixup);
        }
    } else {
        if (ptr->Pointer()) {
            Class *obj;

            obj   = ptr->Pointer();
            index = classpointerList.AddUniqueObject(obj);
        } else {
            index = ARCHIVE_NULL_POINTER;
        }
        index = LittleLong(index);
        ArchiveData(ARC_SafePointer, &index, sizeof(index));
    }
}

void Archiver::ArchiveEventPointer(Event **ev)
{
    int index;

#ifdef ARCHIVE_USE_TYPES
    CheckType(ARC_EventPointer);
#endif

    if (archivemode == ARCHIVE_READ) {
#ifndef NDEBUG
        CheckRead();
#endif
        ArchiveInteger(&index);

        if (!fileerror) {
            if (index == ARCHIVE_POINTER_VALID) {
                *ev = new Event;
                (*ev)->Archive(*this);
            } else {
                (*ev) = NULL;
            }
        }
    } else {
#ifndef NDEBUG
        CheckWrite();
#endif
        if (*ev) {
            index = ARCHIVE_POINTER_VALID;
        } else {
            index = ARCHIVE_NULL_POINTER;
        }

        ArchiveInteger(&index);
        if (*ev) {
            (*ev)->Archive(*this);
        }
    }
}

void Archiver::ArchiveRaw(void *data, size_t size)
{
    ArchiveData(ARC_Raw, data, size);
}

void Archiver::ArchiveString(str *string)
{
#ifdef ARCHIVE_USE_TYPES
    CheckType(ARC_String);
#endif

    if (archivemode == ARCHIVE_READ) {
        fileSize_t s;
        char      *data;

#ifndef NDEBUG
        CheckRead();
#endif
        if (!fileerror) {
            s = ReadSize();
            if (!fileerror) {
                data = new char[s + 1];
                if (data) {
                    if (s) {
                        archivefile.Read(data, s);
                    }
                    data[s] = 0;

                    *string = data;

                    delete[] data;
                }
            }
        }
    } else {
#ifndef NDEBUG
        CheckWrite();
#endif
        WriteSize(string->length());
        archivefile.Write(string->c_str(), string->length());
    }
}

Class *Archiver::ReadObject(void)
{
    ClassDef *cls;
    Class    *obj;
    str       classname;
    size_t    objstart;
    size_t    endpos;
    int       index;
    size_t    size;
    qboolean  isent;
    int       type;

    CheckRead();

    type = ReadType();
    if ((type != ARC_Object) && (type != ARC_Entity)) {
        FileError("Expecting %s or %s", typenames[ARC_Object], typenames[ARC_Entity]);
    }

    size = ReadSize();
    ArchiveString(&classname);

    cls = getClass(classname.c_str());
    if (!cls) {
        FileError("Invalid class %s.", classname.c_str());
    }

#if defined(GAME_DLL)
    isent = checkInheritance(&Entity::ClassInfo, cls);
    if (type == ARC_Entity) {
        if (!isent) {
            FileError("Non-Entity class object '%s' saved as an Entity based object.", classname.c_str());
        }

        ArchiveInteger(&level.spawn_entnum);
        //
        // make sure to setup spawnflags properly
        //
        ArchiveInteger(&level.spawnflags);
    } else if (isent) {
        FileError("Entity class object '%s' saved as non-Entity based object.", classname.c_str());
    }
#else
    isent = false;
#endif

    ArchiveInteger(&index);
    objstart = archivefile.Pos();

    obj = (Class *)cls->newInstance();
    if (!obj) {
        FileError("Failed to on new instance of class %s.", classname.c_str());
    } else {
        obj->Archive(*this);
    }

    if (!fileerror) {
        endpos = archivefile.Pos();
        if ((endpos - objstart) > size) {
            FileError("Object read past end of object's data");
        } else if ((endpos - objstart) < size) {
            FileError("Object didn't read entire data from file");
        }
    }

    //
    // register this pointer with our list
    //
    classpointerList.AddObjectAt(index, obj);

    return obj;
}

void Archiver::ArchiveObject(Class *obj)
{
    str        classname;
    int        index;
    fileSize_t size;
    qboolean   isent;

    if (archivemode == ARCHIVE_READ) {
        ClassDef *cls;
        size_t    objstart;
        size_t    endpos;
        int       type;

        CheckRead();
        type = ReadType();
        if ((type != ARC_Object) && (type != ARC_Entity)) {
            FileError("Expecting %s or %s", typenames[ARC_Object], typenames[ARC_Entity]);
        }

        size = ReadSize();
        ArchiveString(&classname);

        cls = getClass(classname.c_str());
        if (!cls) {
            FileError("Invalid class %s.", classname.c_str());
        }

        if (obj->classinfo() != cls) {
            FileError(
                "Archive has a '%s' object, but was expecting a '%s' object.", classname.c_str(), obj->getClassname()
            );
        }

#if defined(GAME_DLL)
        isent = obj->isSubclassOf(Entity);
        if (type == ARC_Entity) {
            int entnum;
            if (!isent) {
                FileError("Non-Entity class object '%s' saved as an Entity based object.", classname.c_str());
            }

            ArchiveInteger(&entnum);
            ((Entity *)obj)->entnum = entnum;
            //
            // make sure to setup spawnflags properly
            //
            ArchiveInteger(&level.spawnflags);
        } else if (isent) {
            FileError("Entity class object '%s' saved as non-Entity based object.", classname.c_str());
        }
#else
        isent = false;
#endif

        ArchiveInteger(&index);
        objstart = archivefile.Pos();

        obj->Archive(*this);

        if (!fileerror) {
            endpos = archivefile.Pos();
            if ((endpos - objstart) > size) {
                FileError("Object read past end of object's data");
            } else if ((endpos - objstart) < size) {
                FileError("Object didn't read entire data from file");
            }
        }

        //
        // register this pointer with our list
        //
        classpointerList.AddObjectAt(index, obj);
    } else {
        long sizepos;
        long objstart = 0;
        long endpos;

        assert(obj);
        if (!obj) {
            FileError("NULL object in WriteObject");
        }

#if defined(GAME_DLL)
        isent = obj->isSubclassOf(Entity);
#else
        isent = false;
#endif

        CheckWrite();
        if (isent) {
            WriteType(ARC_Entity);
        } else {
            WriteType(ARC_Object);
        }

        sizepos = archivefile.Tell();
        size    = 0;
        WriteSize(size);

        classname = obj->getClassname();
        ArchiveString(&classname);

#if defined(GAME_DLL)
        if (isent) {
            // Write out the entity number
            ArchiveInteger(&((Entity *)obj)->entnum);
            //
            // make sure to setup spawnflags properly
            //
            ArchiveInteger(&((Entity *)obj)->spawnflags);
        }
#endif

        // write out pointer index for this class pointer
        index = classpointerList.AddUniqueObject(obj);
        ArchiveInteger(&index);

        if (!fileerror) {
            objstart = archivefile.Tell();
            obj->Archive(*this);
        }

        if (!fileerror) {
            endpos = archivefile.Tell();
            size   = endpos - objstart;
            archivefile.Seek(sizepos);
            WriteSize(size);

            if (!fileerror) {
                archivefile.Seek(archivefile.Length());
            }
        }
    }
}

void Archiver::ArchiveObject(SafePtrBase* obj)
{
    ArchiveSafePointer(obj);
}

void Archiver::ArchiveObjectPosition(LightClass *obj)
{
    int index = 0;

    if (archivemode == ARCHIVE_READ) {
        ArchivePosition(&index);
        classpointerList.AddObjectAt(index, (Class *)obj);
    } else {
        index = classpointerList.AddUniqueObject((Class *)obj);
        ArchivePosition(&index);
    }
}

qboolean Archiver::ObjectPositionExists(void *obj)
{
    return classpointerList.IndexOfObject((Class *)obj) != 0;
}

void Archiver::CheckRead(void)
{
    assert(archivemode == ARCHIVE_READ);
    if (!fileerror && (archivemode != ARCHIVE_READ)) {
        FileError("File read during a write operation.");
    }
}

void Archiver::CheckWrite(void)
{
    assert(archivemode == ARCHIVE_WRITE);
    if (!fileerror && (archivemode != ARCHIVE_WRITE)) {
        FileError("File write during a read operation.");
    }
}

qboolean Archiver::Read(str& name, qboolean harderror)
{
    return Read(name.c_str(), harderror);
}

qboolean Archiver::Create(str& name, qboolean harderror)
{
    return Create(name.c_str(), harderror);
}

qboolean Archiver::Loading(void)
{
    return (archivemode == ARCHIVE_READ) ? qtrue : qfalse;
}

qboolean Archiver::Saving(void)
{
    return (archivemode == ARCHIVE_WRITE) ? qtrue : qfalse;
}

qboolean Archiver::NoErrors(void)
{
    return fileerror ? qfalse : qtrue;
}

size_t Archiver::Counter() const
{
    return m_iNumBytesIO;
}

void Archiver::Reset()
{
    m_iNumBytesIO = 0;
}

fileSize_t Archiver::ReadSize(void)
{
    fileSize_t s;

    s = 0;
    if (!fileerror) {
        archivefile.Read(&s, sizeof(s));
        LittleSwap(&s, sizeof(s));
    }

    return s;
}

void Archiver::CheckSize(int type, fileSize_t size)
{
    fileSize_t s;

    if (!fileerror) {
        s = ReadSize();

        if (size != s) {
            FileError("Invalid data size of %d on %s.", s, typenames[type]);
        }
    }
}

void Archiver::WriteSize(fileSize_t size)
{
    LittleSwap(&size, sizeof(size));
    archivefile.Write(&size, sizeof(fileSize_t));
}

int Archiver::ReadType(void)
{
    int t;

    if (!fileerror) {
        archivefile.Read(&t, sizeof(t));
        t = LittleLong(t);

        return t;
    }

    return ARC_NULL;
}

void Archiver::WriteType(int type)
{
    int nt = LittleLong(type);
    archivefile.Write(&nt, sizeof(nt));
}

void Archiver::CheckType(int type)
{
    int t;

    assert((type >= 0) && (type < ARC_NUMTYPES));

    if (archivemode == ARCHIVE_READ) {
        if (!fileerror) {
            t = ReadType();
            if (t != type) {
                if (t < ARC_NUMTYPES) {
                    FileError("Expecting %s, Should be %s", typenames[type], typenames[t]);
                    assert(0);
                }
                else {
                    FileError("Expecting %s, Should be %i (Unknown type)", typenames[type], t);
                }
            }
        }
    } else {
        WriteType(type);
    }
}

void Archiver::ArchiveData(int type, void *data, size_t size)
{
#ifdef ARCHIVE_USE_TYPES
    CheckType(type);
#endif

    if (archivemode == ARCHIVE_READ) {
#ifndef NDEBUG
        CheckRead();
#endif
        if (!fileerror && size) {
            m_iNumBytesIO += size;
            archivefile.Read(data, size);
        }
    } else {
#ifndef NDEBUG
        CheckWrite();
#endif

        if (!fileerror && size) {
            m_iNumBytesIO += size;
            archivefile.Write(data, size);
        }
    }
}

void Archiver::ArchiveConfigString(int cs)
{
    str s;

    if (archivemode == ARCHIVE_READ) {
        ArchiveString(&s);
        gi.setConfigstring(cs, s.c_str());
    } else {
        s = gi.getConfigstring(cs);
        ArchiveString(&s);
    }
}

void Archiver::SetSilent(bool bSilent)
{
    silent = bSilent;
}
