// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.

#include "Engine/Platform/Platform.h"
#include "Engine/Platform/File.h"
#include "MemoryWriteStream.h"

MemoryWriteStream::MemoryWriteStream()
    : _buffer(nullptr)
    , _position(nullptr)
    , _capacity(0)
{
}

MemoryWriteStream::MemoryWriteStream(uint32 capacity)
    : _capacity(capacity)
{
    if (capacity > 0)
    {
        _buffer = (byte*)Allocator::Allocate(capacity);
        if (_buffer == nullptr)
        {
            OUT_OF_MEMORY;
        }
    }
    else
    {
        _buffer = nullptr;
    }
    _position = _buffer;
}

MemoryWriteStream::~MemoryWriteStream()
{
    Allocator::Free(_buffer);
}

void* MemoryWriteStream::Move(uint32 bytes)
{
    const uint32 position = GetPosition();

    // Check if there is need to update a buffer size
    if (_capacity - position < bytes)
    {
        // Perform reallocation
        uint32 newCapacity = _capacity != 0 ? _capacity * 2 : 256;
        while (newCapacity < position + bytes)
            newCapacity *= 2;
        byte* newBuf = (byte*)Allocator::Allocate(newCapacity);
        if (newBuf == nullptr)
        {
            OUT_OF_MEMORY;
        }
        Platform::MemoryCopy(newBuf, _buffer, _capacity);
        Allocator::Free(_buffer);

        // Update state
        _buffer = newBuf;
        _capacity = newCapacity;
        _position = _buffer + position;
    }

    // Skip bytes
    _position += bytes;

    // Return pointer to begin
    return _buffer + position;
}

void MemoryWriteStream::Reset(uint32 capacity)
{
    // Check if resize
    if (capacity > _capacity)
    {
        Allocator::Free(_buffer);
        _buffer = (byte*)Allocator::Allocate(capacity);
        if (_buffer == nullptr)
        {
            OUT_OF_MEMORY;
        }
        _capacity = capacity;
    }

    // Reset pointer
    _position = _buffer;
}

bool MemoryWriteStream::SaveToFile(const StringView& path) const
{
    // Open file for writing
    auto file = File::Open(path, FileMode::CreateAlways, FileAccess::Write, FileShare::Read);
    if (file == nullptr)
    {
        return true;
    }

    // Write data
    uint32 bytesWritten;
    file->Write(GetHandle(), GetPosition(), &bytesWritten);

    Delete(file);
    return false;
}

void MemoryWriteStream::Flush()
{
    // Nothing to do
}

void MemoryWriteStream::Close()
{
    Allocator::Free(_buffer);
    _buffer = nullptr;
    _position = nullptr;
    _capacity = 0;
}

uint32 MemoryWriteStream::GetLength()
{
    return _capacity;
}

uint32 MemoryWriteStream::GetPosition()
{
    return static_cast<uint32>(_position - _buffer);
}

void MemoryWriteStream::SetPosition(uint32 seek)
{
    _position = _buffer + seek;
}

void MemoryWriteStream::WriteBytes(const void* data, uint32 bytes)
{
    // Calculate current position
    const uint32 position = GetPosition();

    // Check if there is need to update a buffer size
    if (_capacity - position < bytes)
    {
        // Perform reallocation
        uint32 newCapacity = _capacity != 0 ? _capacity * 2 : 256;
        while (newCapacity < position + bytes)
            newCapacity *= 2;
        byte* newBuf = (byte*)Allocator::Allocate(newCapacity);
        if (newBuf == nullptr)
        {
            OUT_OF_MEMORY;
        }
        Platform::MemoryCopy(newBuf, _buffer, _capacity);
        Allocator::Free(_buffer);

        // Update state
        _buffer = newBuf;
        _capacity = newCapacity;
        _position = _buffer + position;
    }

    // Copy data
    Platform::MemoryCopy(_position, data, bytes);
    _position += bytes;
}
