/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.

#include "stdafx.h"
#include "DataWriter.h"
#include "CryEndian.h"

DataWriter::DataWriter()
{
    m_outputBuffer = NULL;
    Reset();
}

DataWriter::~DataWriter()
{
    this->Reset();
}

void DataWriter::Reset()
{
    if (m_outputBuffer)
    {
        free(m_outputBuffer);
    }

    m_currentBufferSize = 0;
    m_outputBuffer = NULL;
    m_writtenBytes = 0;
    m_labelMap.clear();
    m_offsetLocations.clear();
    m_bClosed = false;
    m_bSwapEndian = false;
}

void DataWriter::SetSwapEndian(bool bEnable)
{
    m_bSwapEndian = bEnable;
}

void* DataWriter::GetDataAndTakeOwnership()
{
    if (m_bClosed && m_outputBuffer)
    {
        void* returnData = m_outputBuffer;
        m_outputBuffer = NULL;
        Reset();
        return returnData;
    }
    return NULL;
}

uint32 DataWriter::GetDataSize() const
{
    return (m_bClosed && m_outputBuffer) ? m_writtenBytes : 0;
}

void DataWriter::BeginWriting()
{
    ExpandBuffer(1);
    AddLabel("fileStart");
}

void DataWriter::WriteUInt8(const uint8 v)
{
    ExpandBuffer(sizeof(v));
    uint8* out = (uint8*)(((uint8*)m_outputBuffer) + m_writtenBytes);
    *out = v;
    SwapEndian(*out, m_bSwapEndian);
    m_writtenBytes += sizeof(v);
}

void DataWriter::WriteInt8(const int8 v)
{
    ExpandBuffer(sizeof(v));
    int8* out = (int8*)(((uint8*)m_outputBuffer) + m_writtenBytes);
    *out = v;
    SwapEndian(*out, m_bSwapEndian);
    m_writtenBytes += sizeof(v);
}

void DataWriter::WriteUInt16(const uint16 v)
{
    ExpandBuffer(sizeof(v));
    uint16* out = (uint16*)(((uint8*)m_outputBuffer) + m_writtenBytes);
    *out = v;
    SwapEndian(*out, m_bSwapEndian);
    m_writtenBytes += sizeof(v);
}

void DataWriter::WriteInt16(const int16 v)
{
    ExpandBuffer(sizeof(v));
    int16* out = (int16*)(((uint8*)m_outputBuffer) + m_writtenBytes);
    *out = v;
    SwapEndian(*out, m_bSwapEndian);
    m_writtenBytes += sizeof(v);
}

void DataWriter::WriteUInt32(const uint32 v)
{
    ExpandBuffer(sizeof(v));
    uint32* out = (uint32*)(((uint8*)m_outputBuffer) + m_writtenBytes);
    *out = v;
    SwapEndian(*out, m_bSwapEndian);
    m_writtenBytes += sizeof(v);
}

void DataWriter::WriteInt32(const int32 v)
{
    ExpandBuffer(sizeof(v));
    int32* out = (int32*)(((uint8*)m_outputBuffer) + m_writtenBytes);
    *out = v;
    SwapEndian(*out, m_bSwapEndian);
    m_writtenBytes += sizeof(v);
}

void DataWriter::WriteFloat(const float v)
{
    ExpandBuffer(sizeof(v));
    float* out = (float*)(((uint8*)m_outputBuffer) + m_writtenBytes);
    *out = v;
    SwapEndian(*out, m_bSwapEndian);
    m_writtenBytes += sizeof(v);
}

void DataWriter::WriteData(const void* v, const uint32 size)
{
    ExpandBuffer(size);
    void* out = (void*)(((uint8*)m_outputBuffer) + m_writtenBytes);
    memcpy(out, v, size);
    m_writtenBytes += size;
}

void DataWriter::WriteAlign(const uint32 alignBytes)
{
    const uint32 a = m_writtenBytes % alignBytes;
    if (a != 0)
    {
        m_writtenBytes += (alignBytes - a);
    }
    ExpandBuffer();
}

void DataWriter::AddLabel(const string& label)
{
    const std::map<string, uint32>::iterator f = m_labelMap.find(label);
    if (f == m_labelMap.end())
    {
        m_labelMap.insert(std::make_pair(label, m_writtenBytes));
    }
}

void DataWriter::WriteOffsetInt32(const string& label)
{
    this->WriteOffsetInt32("", label);
}

void DataWriter::WriteOffsetInt32(const string& fromLabel, const string& label)
{
    SOffsetLocation loc;
    loc.b16Bit = false;
    loc.offset = m_writtenBytes;
    loc.labelName = label;
    loc.fromLabelName = fromLabel;
    m_offsetLocations.push_back(loc);
    WriteInt32(0);   // Make space to write the offset into later
}

void DataWriter::WriteOffsetInt16(const string& label)
{
    this->WriteOffsetInt16("", label);
}

void DataWriter::WriteOffsetInt16(const string& fromLabel, const string& label)
{
    SOffsetLocation loc;
    loc.b16Bit = true;
    loc.offset = m_writtenBytes;
    loc.labelName = label;
    loc.fromLabelName = fromLabel;
    m_offsetLocations.push_back(loc);
    WriteInt16(0);   // Make space to write the offset into later
}

bool DataWriter::EndWriting()
{
    m_missingLabels.clear();

    for (int iOffset = 0; iOffset < m_offsetLocations.size(); iOffset++)
    {
        const SOffsetLocation* loc = &m_offsetLocations[iOffset];
        const std::map<string, uint32>::iterator itLabel = m_labelMap.find(loc->labelName);

        if (itLabel != m_labelMap.end())
        {
            const uint32 labelLoc = (*itLabel).second;
            uint32 fromLoc = 0;
            if (loc->fromLabelName.length() > 0)
            {
                const std::map<string, uint32>::iterator itFromLabel = m_labelMap.find(loc->fromLabelName);
                if (itFromLabel != m_labelMap.end())
                {
                    fromLoc = (*itFromLabel).second;
                }
                else
                {
                    m_missingLabels.push_back(loc->fromLabelName);
                }
            }

            if (loc->b16Bit)
            {
                const int16 offset = (int16)(labelLoc - fromLoc);
                int16* out = (int16*)(((uint8*)m_outputBuffer) + loc->offset);
                *out = offset;
                SwapEndian(*out, m_bSwapEndian);
            }
            else
            {
                const int32 offset = labelLoc - fromLoc;
                int32* out = (int32*)(((uint8*)m_outputBuffer) + loc->offset);
                *out = offset;
                SwapEndian(*out, m_bSwapEndian);
            }
        }
        else
        {
            m_missingLabels.push_back(loc->labelName);
        }
    }

    m_bClosed = true;

    return m_missingLabels.empty();
}

uint32 DataWriter::CalculateSize(const string& fromLabel, const string& toLabel)
{
    const std::map<string, uint32>::iterator itFromLabel = m_labelMap.find(fromLabel);
    const std::map<string, uint32>::iterator itToLabel = m_labelMap.find(toLabel);

    uint32 size = 0;
    if (itFromLabel != m_labelMap.end() && itToLabel != m_labelMap.end())
    {
        const uint32 fromLoc = (*itFromLabel).second;
        const uint32 toLoc = (*itToLabel).second;
        if (fromLoc < toLoc)
        {
            size = toLoc - fromLoc;
        }
        else
        {
            size = 0;
        }
    }
    return size;
}

#define BUFFERINCREASESIZE (1024 * 1024)

void DataWriter::ExpandBuffer(const uint32 addBytes)
{
    if (m_writtenBytes + addBytes > m_currentBufferSize)
    {
        const uint32 newBufferSize = m_currentBufferSize + BUFFERINCREASESIZE;
        if (m_outputBuffer)
        {
            m_outputBuffer = realloc(m_outputBuffer, newBufferSize);
        }
        else
        {
            m_outputBuffer = malloc(newBufferSize);
        }
        m_currentBufferSize = newBufferSize;
    }
}