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

#include "ReadStream.h"
#include "WriteStream.h"
#include "JsonWriters.h"
#include "JsonSerializer.h"
#include "MemoryReadStream.h"
#include "Engine/Core/Types/CommonValue.h"
#include "Engine/Core/Types/Variant.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Content/Asset.h"
#include "Engine/Core/Cache.h"
#include "Engine/Debug/Exceptions/JsonParseException.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/Internal/ManagedSerialization.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h"

void ReadStream::Read(StringAnsi& data)
{
    int32 length;
    ReadInt32(&length);
    if (length < 0 || length > STREAM_MAX_STRING_LENGTH)
    {
        _hasError = true;
        data = "";
        return;
    }

    data.ReserveSpace(length);
    if (length == 0)
        return;
    char* ptr = data.Get();
    ASSERT(ptr != nullptr);
    ReadBytes(ptr, length);
}

void ReadStream::Read(StringAnsi& data, int8 lock)
{
    int32 length;
    ReadInt32(&length);
    if (length < 0 || length > STREAM_MAX_STRING_LENGTH)
    {
        _hasError = true;
        data = "";
        return;
    }

    data.ReserveSpace(length);
    if (length == 0)
        return;
    char* ptr = data.Get();
    ASSERT(ptr != nullptr);
    ReadBytes(ptr, length);

    for (int32 i = 0; i < length; i++)
    {
        *ptr = *ptr ^ lock;
        ptr++;
    }
}

void ReadStream::Read(String& data)
{
    int32 length;
    ReadInt32(&length);
    if (length <= 0 || length > STREAM_MAX_STRING_LENGTH)
    {
        if (length != 0)
            _hasError = true;
        data.Clear();
        return;
    }

    data.ReserveSpace(length);
    Char* ptr = data.Get();
    ASSERT(ptr != nullptr);
    ReadBytes(ptr, length * sizeof(Char));
}

void ReadStream::Read(String& data, int16 lock)
{
    int32 length;
    ReadInt32(&length);
    if (length <= 0 || length > STREAM_MAX_STRING_LENGTH)
    {
        if (length != 0)
            _hasError = true;
        data.Clear();
        return;
    }

    data.ReserveSpace(length);
    Char* ptr = data.Get();
    ASSERT(ptr != nullptr);
    ReadBytes(ptr, length * sizeof(Char));

    for (int32 i = 0; i < length; i++)
    {
        *ptr = *ptr ^ lock;
        ptr++;
    }
}

void ReadStream::Read(CommonValue& data)
{
    byte type;
    ReadByte(&type);
    switch (static_cast<CommonType>(type))
    {
    case CommonType::Bool:
        data.Set(ReadBool());
        break;
    case CommonType::Integer:
    {
        int32 v;
        ReadInt32(&v);
        data.Set(v);
    }
    break;
    case CommonType::Float:
    {
        float v;
        ReadFloat(&v);
        data.Set(v);
    }
    break;
    case CommonType::Vector2:
    {
        Float2 v;
        Read(v);
        data.Set(v);
    }
    break;
    case CommonType::Vector3:
    {
        Float3 v;
        Read(v);
        data.Set(v);
    }
    break;
    case CommonType::Vector4:
    {
        Float4 v;
        Read(v);
        data.Set(v);
    }
    break;
    case CommonType::Color:
    {
        Color v;
        Read(v);
        data.Set(v);
    }
    break;
    case CommonType::Guid:
    {
        Guid v;
        Read(v);
        data.Set(v);
    }
    break;
    case CommonType::String:
    {
        String v;
        ReadString(&v, 953);
        data.Set(v);
    }
    break;
    case CommonType::Box:
    {
        BoundingBox v;
        ReadBoundingBox(&v);
        data.Set(v);
    }
    break;
    case CommonType::Rotation:
    {
        Quaternion v;
        Read(v);
        data.Set(v);
    }
    break;
    case CommonType::Transform:
    {
        Transform v;
        ReadTransform(&v);
        data.Set(v);
    }
    break;
    case CommonType::Sphere:
    {
        BoundingSphere v;
        ReadBoundingSphere(&v);
        data.Set(v);
    }
    break;
    case CommonType::Rectangle:
    {
        Rectangle v;
        Read(v);
        data.Set(v);
    }
    case CommonType::Ray:
    {
        Ray v;
        ReadRay(&v);
        data.Set(v);
    }
    break;
    case CommonType::Matrix:
    {
        Matrix v;
        Read(v);
        data.Set(v);
    }
    break;
    case CommonType::Blob:
    {
        int32 length;
        Read(length);
        data.SetBlob(length);
        if (length > 0)
        {
            ReadBytes(data.AsBlob.Data, length);
        }
    }
    break;
    default: CRASH;
    }
}

void ReadStream::Read(VariantType& data)
{
    data = VariantType((VariantType::Types)ReadByte());
    int32 typeNameLength;
    ReadInt32(&typeNameLength);
    if (typeNameLength == MAX_int32)
    {
        ReadInt32(&typeNameLength);
        if (typeNameLength == 0)
            return;
        data.TypeName = static_cast<char*>(Allocator::Allocate(typeNameLength + 1));
        char* ptr = data.TypeName;
        ReadBytes(ptr, typeNameLength);
        for (int32 i = 0; i < typeNameLength; i++)
        {
            *ptr = *ptr ^ 77;
            ptr++;
        }
        *ptr = 0;
    }
    else if (typeNameLength > 0)
    {
        // [Deprecated on 27.08.2020, expires on 27.08.2021]
        ASSERT(typeNameLength < STREAM_MAX_STRING_LENGTH);
        Array<Char> chars;
        chars.Resize(typeNameLength + 1);
        Char* ptr = chars.Get();
        ReadBytes(ptr, typeNameLength * sizeof(Char));
        for (int32 i = 0; i < typeNameLength; i++)
        {
            *ptr = *ptr ^ 77;
            ptr++;
        }
        *ptr = 0;
        data.TypeName = static_cast<char*>(Allocator::Allocate(typeNameLength + 1));
        StringUtils::ConvertUTF162ANSI(chars.Get(), data.TypeName, typeNameLength);
        data.TypeName[typeNameLength] = 0;
    }
}

void ReadStream::Read(Variant& data)
{
    VariantType type;
    ReadVariantType(&type);
    data.SetType(MoveTemp(type));
    switch (data.Type.Type)
    {
    case VariantType::Null:
    case VariantType::Void:
        break;
    case VariantType::Bool:
        data.AsBool = ReadBool();
        break;
    case VariantType::Int16:
        ReadInt16(&data.AsInt16);
        break;
    case VariantType::Uint16:
        ReadUint16(&data.AsUint16);
        break;
    case VariantType::Int:
        ReadInt32(&data.AsInt);
        break;
    case VariantType::Uint:
        ReadUint32(&data.AsUint);
        break;
    case VariantType::Int64:
        ReadInt64(&data.AsInt64);
        break;
    case VariantType::Uint64:
    case VariantType::Enum:
        ReadUint64(&data.AsUint64);
        break;
    case VariantType::Float:
        ReadFloat(&data.AsFloat);
        break;
    case VariantType::Double:
        ReadDouble(&data.AsDouble);
        break;
    case VariantType::Pointer:
    {
        uint64 asUint64;
        ReadUint64(&asUint64);
        data.AsPointer = (void*)(uintptr)asUint64;
        break;
    }
    case VariantType::String:
    {
        int32 length;
        ReadInt32(&length);
        ASSERT(length < STREAM_MAX_STRING_LENGTH);
        const int32 dataLength = length * sizeof(Char) + 2;
        if (data.AsBlob.Length != dataLength)
        {
            Allocator::Free(data.AsBlob.Data);
            data.AsBlob.Data = dataLength > 0 ? Allocator::Allocate(dataLength) : nullptr;
            data.AsBlob.Length = dataLength;
        }
        Char* ptr = (Char*)data.AsBlob.Data;
        ReadBytes(ptr, length * sizeof(Char));
        for (int32 i = 0; i < length; i++)
        {
            *ptr = *ptr ^ -14;
            ptr++;
        }
        *ptr = 0;
        break;
    }
    case VariantType::Object:
    {
        Guid id;
        Read(id);
        data.SetObject(FindObject(id, ScriptingObject::GetStaticClass()));
        break;
    }
    case VariantType::ManagedObject:
    case VariantType::Structure:
    {
        const byte format = ReadByte();
        if (format == 0)
        {
            // No data
        }
        else if (format == 1)
        {
            // Json
            StringAnsi json;
            ReadStringAnsi(&json, -71);
#if USE_CSHARP
            MCore::Thread::Attach();
            MClass* klass = MUtils::GetClass(data.Type);
            if (!klass)
            {
                LOG(Error, "Invalid variant type {0}", data.Type);
                return;
            }
            MObject* obj = MCore::Object::New(klass);
            if (!obj)
            {
                LOG(Error, "Failed to managed instance of the variant type {0}", data.Type);
                return;
            }
            if (!klass->IsValueType())
                MCore::Object::Init(obj);
            ManagedSerialization::Deserialize(json, obj);
            if (data.Type.Type == VariantType::ManagedObject)
                data.SetManagedObject(obj);
            else
                data = MUtils::UnboxVariant(obj);
#endif
        }
        else
        {
            LOG(Error, "Invalid Variant {0) format {1}", data.Type.ToString(), format);
        }
        break;
    }
    case VariantType::Blob:
    {
        int32 length;
        ReadInt32(&length);
        data.SetBlob(length);
        ReadBytes(data.AsBlob.Data, length);
        break;
    }
    case VariantType::Asset:
    {
        Guid id;
        Read(id);
        data.SetAsset(LoadAsset(id, Asset::TypeInitializer));
        break;
    }
    case VariantType::Float2:
        ReadBytes(&data.AsData, sizeof(Float2));
        break;
    case VariantType::Float3:
        ReadBytes(&data.AsData, sizeof(Float3));
        break;
    case VariantType::Float4:
        ReadBytes(&data.AsData, sizeof(Float4));
        break;
    case VariantType::Double2:
        ReadBytes(&data.AsData, sizeof(Double2));
        break;
    case VariantType::Double3:
        ReadBytes(&data.AsData, sizeof(Double3));
        break;
    case VariantType::Double4:
        ReadBytes(data.AsBlob.Data, sizeof(Double4));
        break;
    case VariantType::Color:
        ReadBytes(&data.AsData, sizeof(Color));
        break;
    case VariantType::Guid:
        ReadBytes(&data.AsData, sizeof(Guid));
        break;
    case VariantType::BoundingBox:
        ReadBoundingBox(&data.AsBoundingBox());
        break;
    case VariantType::BoundingSphere:
        ReadBoundingSphere(&data.AsBoundingSphere());
        break;
    case VariantType::Quaternion:
        ReadBytes(&data.AsData, sizeof(Quaternion));
        break;
    case VariantType::Transform:
        ReadTransform(&data.AsTransform());
        break;
    case VariantType::Rectangle:
        ReadBytes(&data.AsData, sizeof(Rectangle));
        break;
    case VariantType::Ray:
        ReadRay(&data.AsRay());
        break;
    case VariantType::Matrix:
        ReadBytes(data.AsBlob.Data, sizeof(Matrix));
        break;
    case VariantType::Array:
    {
        int32 count;
        ReadInt32(&count);
        auto& array = *(Array<Variant>*)data.AsData;
        array.Resize(count);
        for (int32 i = 0; i < count; i++)
            ReadVariant(&array[i]);
        break;
    }
    case VariantType::Dictionary:
    {
        int32 count;
        ReadInt32(&count);
        auto& dictionary = *data.AsDictionary;
        dictionary.Clear();
        dictionary.EnsureCapacity(count);
        for (int32 i = 0; i < count; i++)
        {
            Variant key;
            ReadVariant(&key);
            ReadVariant(&dictionary[MoveTemp(key)]);
        }
        break;
    }
    case VariantType::Typename:
    {
        int32 length;
        ReadInt32(&length);
        ASSERT(length < STREAM_MAX_STRING_LENGTH);
        const int32 dataLength = length + 1;
        if (data.AsBlob.Length != dataLength)
        {
            Allocator::Free(data.AsBlob.Data);
            data.AsBlob.Data = dataLength > 0 ? Allocator::Allocate(dataLength) : nullptr;
            data.AsBlob.Length = dataLength;
        }
        char* ptr = (char*)data.AsBlob.Data;
        ReadBytes(ptr, length);
        for (int32 i = 0; i < length; i++)
        {
            *ptr = *ptr ^ -14;
            ptr++;
        }
        *ptr = 0;
        break;
    }
    default:
        CRASH;
    }
}

void ReadStream::ReadJson(ISerializable* obj)
{
    int32 engineBuild, size;
    ReadInt32(&engineBuild);
    ReadInt32(&size);
    if (obj)
    {
        if (const auto memoryStream = dynamic_cast<MemoryReadStream*>(this))
        {
            JsonSerializer::LoadFromBytes(obj, Span<byte>((byte*)memoryStream->Move(size), size), engineBuild);
        }
        else
        {
            void* data = Allocator::Allocate(size);
            ReadBytes(data, size);
            JsonSerializer::LoadFromBytes(obj, Span<byte>((byte*)data, size), engineBuild);
            Allocator::Free(data);
        }
    }
    else
        SetPosition(GetPosition() + size);
}

void ReadStream::ReadStringAnsi(StringAnsi* data)
{
    Read(*data);
}

void ReadStream::ReadStringAnsi(StringAnsi* data, int8 lock)
{
    Read(*data, lock);
}

void ReadStream::ReadString(String* data)
{
    Read(*data);
}

void ReadStream::ReadString(String* data, int16 lock)
{
    Read(*data, lock);
}

void ReadStream::ReadCommonValue(CommonValue* data)
{
    Read(*data);
}

void ReadStream::ReadVariantType(VariantType* data)
{
    Read(*data);
}

void ReadStream::ReadVariant(Variant* data)
{
    Read(*data);
}

void ReadStream::ReadBoundingBox(BoundingBox* box, bool useDouble)
{
#if USE_LARGE_WORLDS
    if (useDouble)
        Read(box);
    else
    {
        Float3 min, max;
        Read(min);
        Read(max);
        box->Minimum = min;
        box->Maximum = max;
    }
#else
    if (useDouble)
    {
        Double3 min, max;
        Read(min);
        Read(max);
        box->Minimum = min;
        box->Maximum = max;
    }
    else
        Read(*box);
#endif
}

void ReadStream::ReadBoundingSphere(BoundingSphere* sphere, bool useDouble)
{
#if USE_LARGE_WORLDS
    if (useDouble)
        Read(*sphere);
    else
    {
        Float3 center;
        float radius;
        Read(center);
        Read(radius);
        sphere->Center = center;
        sphere->Radius = radius;
    }
#else
    if (useDouble)
    {
        Double3 center;
        double radius;
        Read(center);
        Read(radius);
        sphere->Center = center;
        sphere->Radius = (float)radius;
    }
    else
        Read(*sphere);
#endif
}

void ReadStream::ReadTransform(Transform* transform, bool useDouble)
{
#if USE_LARGE_WORLDS
    if (useDouble)
        Read(*transform);
    else
    {
        Float3 translation;
        Read(translation);
        Read(transform->Orientation);
        Read(transform->Scale);
        transform->Translation = translation;
    }
#else
    if (useDouble)
    {
        Double3 translation;
        Read(translation);
        Read(transform->Orientation);
        Read(transform->Scale);
        transform->Translation = translation;
    }
    else
        Read(*transform);
#endif
}

void ReadStream::ReadRay(Ray* ray, bool useDouble)
{
#if USE_LARGE_WORLDS
    if (useDouble)
        Read(*ray);
    else
    {
        Float3 position, direction;
        Read(position);
        Read(direction);
        ray->Position = position;
        ray->Direction = direction;
    }
#else
    if (useDouble)
    {
        Double3 position, direction;
        Read(position);
        Read(direction);
        ray->Position = position;
        ray->Direction = direction;
    }
    else
        Read(*ray);
#endif
}

void WriteStream::WriteText(const StringView& text)
{
    WriteBytes(text.Get(), sizeof(Char) * text.Length());
}

void WriteStream::WriteText(const StringAnsiView& text)
{
    WriteBytes(text.Get(), sizeof(char) * text.Length());
}

void WriteStream::Write(const StringView& data)
{
    const int32 length = data.Length();
    ASSERT(length < STREAM_MAX_STRING_LENGTH);
    WriteInt32(length);
    WriteBytes(*data, length * sizeof(Char));
}

void WriteStream::Write(const StringView& data, int16 lock)
{
    ASSERT(data.Length() < STREAM_MAX_STRING_LENGTH);
    WriteInt32(data.Length());
    for (int32 i = 0; i < data.Length(); i++)
        WriteUint16((uint16)((uint16)data[i] ^ lock));
}

void WriteStream::Write(const StringAnsiView& data)
{
    const int32 length = data.Length();
    ASSERT(length < STREAM_MAX_STRING_LENGTH);
    WriteInt32(length);
    WriteBytes(data.Get(), length);
}

void WriteStream::Write(const StringAnsiView& data, int8 lock)
{
    const int32 length = data.Length();
    ASSERT(length < STREAM_MAX_STRING_LENGTH);
    WriteInt32(length);
    for (int32 i = 0; i < length; i++)
        WriteUint8((uint8)((uint8)data[i] ^ lock));
}

void WriteStream::Write(const CommonValue& data)
{
    WriteByte(static_cast<byte>(data.Type));
    switch (data.Type)
    {
    case CommonType::Bool:
        WriteBool(data.AsBool);
        break;
    case CommonType::Integer:
        WriteInt32(data.AsInteger);
        break;
    case CommonType::Float:
        WriteFloat(data.AsFloat);
        break;
    case CommonType::Vector2:
        Write(data.AsVector2);
        break;
    case CommonType::Vector3:
        Write(data.AsVector3);
        break;
    case CommonType::Vector4:
        Write(data.AsVector4);
        break;
    case CommonType::Color:
        Write(data.AsColor);
        break;
    case CommonType::Guid:
        Write(data.AsGuid);
        break;
    case CommonType::String:
        WriteString(data.AsString, 953);
        break;
    case CommonType::Box:
        WriteBoundingBox(data.AsBox);
        break;
    case CommonType::Rotation:
        Write(data.AsRotation);
        break;
    case CommonType::Transform:
        WriteTransform(data.AsTransform);
        break;
    case CommonType::Sphere:
        WriteBoundingSphere(data.AsSphere);
        break;
    case CommonType::Rectangle:
        Write(data.AsRectangle);
        break;
    case CommonType::Ray:
        WriteRay(data.AsRay);
        break;
    case CommonType::Matrix:
        Write(data.AsMatrix);
        break;
    case CommonType::Blob:
        WriteInt32(data.AsBlob.Length);
        if (data.AsBlob.Length > 0)
            WriteBytes(data.AsBlob.Data, data.AsBlob.Length);
        break;
    default: CRASH;
    }
}

void WriteStream::Write(const VariantType& data)
{
    WriteByte((byte)data.Type);
    WriteInt32(MAX_int32);
    WriteStringAnsi(StringAnsiView(data.TypeName), 77);
}

void WriteStream::Write(const Variant& data)
{
    WriteVariantType(data.Type);
    Guid id;
    switch (data.Type.Type)
    {
    case VariantType::Null:
    case VariantType::Void:
        break;
    case VariantType::Bool:
        WriteBool(data.AsBool);
        break;
    case VariantType::Int16:
        WriteInt16(data.AsInt16);
        break;
    case VariantType::Uint16:
        WriteUint16(data.AsUint16);
        break;
    case VariantType::Int:
        WriteInt32(data.AsInt);
        break;
    case VariantType::Uint:
        WriteUint32(data.AsUint);
        break;
    case VariantType::Int64:
        WriteInt64(data.AsInt64);
        break;
    case VariantType::Uint64:
    case VariantType::Enum:
        WriteUint64(data.AsUint64);
        break;
    case VariantType::Float:
        WriteFloat(data.AsFloat);
        break;
    case VariantType::Double:
        WriteDouble(data.AsDouble);
        break;
    case VariantType::Pointer:
        WriteUint64((uint64)(uintptr)data.AsPointer);
        break;
    case VariantType::String:
        WriteString((StringView)data, -14);
        break;
    case VariantType::Object:
        id = data.AsObject ? data.AsObject->GetID() : Guid::Empty;
        Write(id);
        break;
    case VariantType::Blob:
        WriteInt32(data.AsBlob.Length);
        WriteBytes(data.AsBlob.Data, data.AsBlob.Length);
        break;
    case VariantType::BoundingBox:
        WriteBoundingBox(data.AsBoundingBox());
        break;
    case VariantType::Transform:
        WriteTransform(data.AsTransform());
        break;
    case VariantType::Ray:
        WriteRay(data.AsRay());
        break;
    case VariantType::Matrix:
        WriteBytes(data.AsBlob.Data, sizeof(Matrix));
        break;
    case VariantType::Asset:
        id = data.AsAsset ? data.AsAsset->GetID() : Guid::Empty;
        Write(id);
        break;
    case VariantType::Float2:
        WriteBytes(data.AsData, sizeof(Float2));
        break;
    case VariantType::Float3:
        WriteBytes(data.AsData, sizeof(Float3));
        break;
    case VariantType::Float4:
        WriteBytes(data.AsData, sizeof(Float4));
        break;
    case VariantType::Double2:
        WriteBytes(data.AsData, sizeof(Double2));
        break;
    case VariantType::Double3:
        WriteBytes(data.AsData, sizeof(Double3));
        break;
    case VariantType::Double4:
        WriteBytes(data.AsBlob.Data, sizeof(Double4));
        break;
    case VariantType::Color:
        WriteBytes(data.AsData, sizeof(Color));
        break;
    case VariantType::Guid:
        WriteBytes(data.AsData, sizeof(Guid));
        break;
    case VariantType::Quaternion:
        WriteBytes(data.AsData, sizeof(Quaternion));
        break;
    case VariantType::Rectangle:
        WriteBytes(data.AsData, sizeof(Rectangle));
        break;
    case VariantType::BoundingSphere:
        WriteBoundingSphere(data.AsBoundingSphere());
        break;
    case VariantType::Array:
        id.A = ((Array<Variant>*)data.AsData)->Count();
        WriteInt32(id.A);
        for (uint32 i = 0; i < id.A; i++)
            WriteVariant(((Array<Variant>*)data.AsData)->At(i));
        break;
    case VariantType::Dictionary:
        WriteInt32(data.AsDictionary->Count());
        for (auto i = data.AsDictionary->Begin(); i.IsNotEnd(); ++i)
        {
            WriteVariant(i->Key);
            WriteVariant(i->Value);
        }
        break;
    case VariantType::Typename:
        WriteStringAnsi((StringAnsiView)data, -14);
        break;
    case VariantType::ManagedObject:
    case VariantType::Structure:
    {
#if USE_CSHARP
        MObject* obj;
        if (data.Type.Type == VariantType::Structure)
            obj = MUtils::BoxVariant(data);
        else
            obj = (MObject*)data;
        if (obj)
        {
            WriteByte(1);
            rapidjson_flax::StringBuffer json;
            CompactJsonWriter writerObj(json);
            MCore::Thread::Attach();
            ManagedSerialization::Serialize(writerObj, obj);
            WriteStringAnsi(StringAnsiView(json.GetString(), (int32)json.GetSize()), -71);
        }
        else
#endif
        {
            WriteByte(0);
        }
        break;
    }
    default:
        CRASH;
    }
}

void WriteStream::WriteJson(ISerializable* obj, const void* otherObj)
{
    WriteInt32(FLAXENGINE_VERSION_BUILD);
    if (obj)
    {
        rapidjson_flax::StringBuffer buffer;
        CompactJsonWriter writer(buffer);
        writer.StartObject();
        obj->Serialize(writer, otherObj);
        writer.EndObject();

        WriteInt32((int32)buffer.GetSize());
        WriteBytes((byte*)buffer.GetString(), (int32)buffer.GetSize());
    }
    else
        WriteInt32(0);
}

void WriteStream::WriteString(const StringView& data)
{
    Write(data);
}

void WriteStream::WriteString(const StringView& data, int16 lock)
{
    Write(data, lock);
}

void WriteStream::WriteStringAnsi(const StringAnsiView& data)
{
    Write(data);
}

void WriteStream::WriteStringAnsi(const StringAnsiView& data, int8 lock)
{
    Write(data, lock);
}

void WriteStream::WriteCommonValue(const CommonValue& data)
{
    Write(data);
}

void WriteStream::WriteVariantType(const VariantType& data)
{
    Write(data);
}

void WriteStream::WriteVariant(const Variant& data)
{
    Write(data);
}

void WriteStream::WriteBoundingBox(const BoundingBox& box, bool useDouble)
{
#if USE_LARGE_WORLDS
    if (useDouble)
        Write(box);
    else
    {
        Float3 min = box.Minimum, max = box.Maximum;
        Write(min);
        Write(max);
    }
#else
    if (useDouble)
    {
        Double3 min = box.Minimum, max = box.Maximum;
        Write(min);
        Write(max);
    }
    else
        Write(box);
#endif
}

void WriteStream::WriteBoundingSphere(const BoundingSphere& sphere, bool useDouble)
{
#if USE_LARGE_WORLDS
    if (useDouble)
        Write(sphere);
    else
    {
        Float3 center = sphere.Center;
        float radius = (float)sphere.Radius;
        Write(center);
        Write(radius);
    }
#else
    if (useDouble)
    {
        Double3 center = sphere.Center;
        float radius = (float)sphere.Radius;
        Write(center);
        Write(radius);
    }
    else
        Write(sphere);
#endif
}

void WriteStream::WriteTransform(const Transform& transform, bool useDouble)
{
#if USE_LARGE_WORLDS
    if (useDouble)
        Write(transform);
    else
    {
        Float3 translation = transform.Translation;
        Write(translation);
        Write(transform.Orientation);
        Write(transform.Scale);
    }
#else
    if (useDouble)
    {
        Double3 translation = transform.Translation;
        Write(translation);
        Write(transform.Orientation);
        Write(transform.Scale);
    }
    else
        Write(transform);
#endif
}

void WriteStream::WriteRay(const Ray& ray, bool useDouble)
{
#if USE_LARGE_WORLDS
    if (useDouble)
        Write(ray);
    else
    {
        Float3 position = ray.Position, direction = ray.Direction;
        Write(position);
        Write(direction);
    }
#else
    if (useDouble)
    {
        Double3 position = ray.Position, direction = ray.Direction;
        Write(position);
        Write(direction);
    }
    else
        Write(ray);
#endif
}

Array<byte> JsonSerializer::SaveToBytes(ISerializable* obj)
{
    Array<byte> result;
    if (obj)
    {
        rapidjson_flax::StringBuffer buffer;
        CompactJsonWriter writer(buffer);
        writer.StartObject();
        obj->Serialize(writer, nullptr);
        writer.EndObject();
        result.Set((byte*)buffer.GetString(), (int32)buffer.GetSize());
    }
    return result;
}

void JsonSerializer::LoadFromBytes(ISerializable* obj, const Span<byte>& data, int32 engineBuild)
{
    if (!obj || data.Length() == 0)
        return;

    ISerializable::SerializeDocument document;
    {
        PROFILE_CPU_NAMED("Json.Parse");
        document.Parse((const char*)data.Get(), data.Length());
    }
    if (document.HasParseError())
    {
        Log::JsonParseException(document.GetParseError(), document.GetErrorOffset());
        return;
    }

    auto modifier = Cache::ISerializeModifier.Get();
    modifier->EngineBuild = engineBuild;
    obj->Deserialize(document, modifier.Value);
}
