/*
===========================================================================
Copyright (C) 2023 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
===========================================================================
*/

#include "cg_local.h"
#include "memarchiver.h"
#include "../qcommon/vector.h"
#include "../qcommon/str.h"

MemArchiver::MemArchiver()
{
    state         = MEMARC_WRITING;
    buffer        = 0;
    allocatedSize = 0;
    bufferSize    = 0;
}

MemArchiver::~MemArchiver()
{
    if (state == MEMARC_WRITING && buffer) {
        cgi.Free(buffer);
    }
}

void MemArchiver::SetupForWriting(size_t initialSize)
{
    this->state         = MEMARC_WRITING;
    this->buffer        = (byte *)cgi.Malloc(initialSize);
    this->allocatedSize = initialSize;
    this->bufferSize    = 0;
}

void MemArchiver::SetupForReading(byte *buffer, size_t size)
{
    this->state         = MEMARC_READING;
    this->buffer        = buffer;
    this->allocatedSize = size;
    this->bufferSize    = 0;
}

void MemArchiver::SetBaseTime(unsigned int time)
{
    this->svsTime = time;
}

size_t MemArchiver::BufferSize() const
{
    return bufferSize;
}

byte *MemArchiver::ConfiscateBuffer()
{
    byte *confiscatedBuffer = buffer;

    buffer        = NULL;
    allocatedSize = 0;
    bufferSize    = 0;

    return confiscatedBuffer;
}

bool MemArchiver::IsReading() const
{
    return !IsWriting();
}

bool MemArchiver::IsWriting() const
{
    return state == MEMARC_WRITING;
}

bool MemArchiver::FinishedReading() const
{
    return bufferSize == allocatedSize;
}

void MemArchiver::ArchiveByte(byte *value)
{
    ArchiveRaw(value, sizeof(byte));
}

void MemArchiver::ArchiveBoolean(qboolean *value)
{
    ArchiveRaw(value, sizeof(qboolean));
}

void MemArchiver::ArchiveChar(char *value)
{
    ArchiveRaw(value, sizeof(char));
}

void MemArchiver::ArchiveUChar(unsigned char *value)
{
    ArchiveRaw(value, sizeof(unsigned char));
}

void MemArchiver::ArchiveShort(short *value)
{
    ArchiveRaw(value, sizeof(short));
}

void MemArchiver::ArchiveInteger(int *value)
{
    ArchiveRaw(value, sizeof(int));
}

void MemArchiver::ArchiveSize(size_t *value)
{
    ArchiveRaw(value, sizeof(size_t));
}

void MemArchiver::ArchiveFloat(float *value)
{
    ArchiveRaw(value, sizeof(float));
}

void MemArchiver::ArchiveVec3(vec3_t value)
{
    ArchiveRaw(value, sizeof(vec3_t));
}

void MemArchiver::ArchiveVector(Vector *value)
{
    ArchiveFloat(&value->x);
    ArchiveFloat(&value->y);
    ArchiveFloat(&value->z);
}

void MemArchiver::ArchiveString(str *value)
{
    char tmp[4096];
    int  current = 0;

    if (IsReading()) {
        for (current = 0; current < 4096; current++) {
            ArchiveReadRaw(&tmp[current], sizeof(char));
            if (!tmp[current]) {
                break;
            }
        }

        if (current == 4096) {
            tmp[current - 1] = 0;
        }

        *value = tmp;
    } else {
        ArchiveWriteRaw(value->c_str(), value->length() + 1);
    }
}

void MemArchiver::ArchiveTime(int *value)
{
    int time;

    if (IsReading()) {
        ArchiveReadRaw(&time, sizeof(time));

        if (time) {
            time += svsTime;
        }

        *value = time;
    } else {
        time = *value;
        if (time) {
            time -= svsTime;
        }

        ArchiveWriteRaw(&time, sizeof(time));
    }
}

void MemArchiver::ArchiveRaw(void *buffer, size_t size)
{
    if (IsReading()) {
        ArchiveReadRaw(buffer, size);
    } else {
        ArchiveWriteRaw(buffer, size);
    }
}

void MemArchiver::ArchiveReadRaw(void *buffer, size_t size)
{
    if (!size) {
        return;
    }

    if (size + this->bufferSize > this->allocatedSize) {
        Com_Error(ERR_DROP, "MemArchiver: tried to read past end of buffer");
    }

    memcpy(buffer, this->buffer + this->bufferSize, size);
    this->bufferSize += size;
}

void MemArchiver::ArchiveWriteRaw(const void *buffer, size_t size)
{
    if (!size) {
        return;
    }

    if (size + this->bufferSize > this->allocatedSize) {
        if (!this->allocatedSize) {
            this->allocatedSize = 1024;
            this->buffer        = (byte *)cgi.Malloc(this->allocatedSize);
        } else {
            byte *newBuffer;

            do {
                this->allocatedSize *= 2;
            } while (size + this->bufferSize > this->allocatedSize);

            newBuffer = (byte *)cgi.Malloc(this->allocatedSize);
            memcpy(newBuffer, this->buffer, this->bufferSize);
            cgi.Free(this->buffer);
            this->buffer = newBuffer;
        }
    }

    memcpy(this->buffer + this->bufferSize, buffer, size);
    this->bufferSize += size;
}
