/** 
 * @file    kDat5Serializer.cpp
 *
 * @internal
 * Copyright (C) 2008-2022 by LMI Technologies Inc.
 * Licensed under the MIT License.
 * Redistributed files must retain the above copyright notice.
 */
#include <kApi/Io/kDat5Serializer.h>
#include <kApi/Data/kArrayList.h>
#include <kApi/Data/kMap.h>
#include <kApi/Io/kFile.h>
#include <kApi/Threads/kLock.h>
#include <stdio.h>

kBeginValueEx(k, xkDat5SerializerTypeInfo)
    kAddField(xkDat5SerializerTypeInfo, kType, type)
    kAddField(xkDat5SerializerTypeInfo, kPointer, version)
    kAddField(xkDat5SerializerTypeInfo, k32u, id)
kEndValueEx()

kBeginFullClassEx(k, kDat5Serializer)    
    kAddVMethod(kDat5Serializer, kObject, VRelease)
    kAddVMethod(kDat5Serializer, kSerializer, VInit)
    kAddVMethod(kDat5Serializer, kSerializer, VWriteObject)
    kAddVMethod(kDat5Serializer, kSerializer, VReadObject)
    kAddPrivateVMethod(kDat5Serializer, kSerializer, VWriteType)
    kAddPrivateVMethod(kDat5Serializer, kSerializer, VReadType)
kEndFullClassEx()

kFx(kStatus) xkDat5Serializer_InitStatic()
{
    kStaticObj(kDat5Serializer); 

    kCheck(kLock_ConstructEx(&sobj->lock, xkLOCK_OPTION_PRIORITY_INHERITANCE, kNULL)); 
    kCheck(kMap_Construct(&sobj->guidToInfo, kTypeOf(k32u), kTypeOf(xkDat5SerializerTypeInfo), 256, kNULL)); 
    kCheck(kMap_Construct(&sobj->guidToCompressor, kTypeOf(kCompressionType), kTypeOf(kPointer), 0, kNULL));

    kCheck(kAssembly_AddLoadHandler(xkDat5Serializer_OnAssemblyLoad, kNULL)); 
    kCheck(kAssembly_AddUnloadHandler(xkDat5Serializer_OnAssemblyUnload, kNULL)); 

    return kOK; 
}

kFx(kStatus) xkDat5Serializer_ReleaseStatic()
{
    kStaticObj(kDat5Serializer); 

    kCheck(kAssembly_RemoveLoadHandler(xkDat5Serializer_OnAssemblyLoad, kNULL)); 
    kCheck(kAssembly_RemoveUnloadHandler(xkDat5Serializer_OnAssemblyUnload, kNULL)); 
    
    kCheck(kDestroyRef(&sobj->guidToCompressor)); 
    kCheck(kDestroyRef(&sobj->guidToInfo)); 
    kCheck(kDestroyRef(&sobj->lock)); 

    return kOK; 
}

kFx(kStatus) xkDat5Serializer_OnAssemblyLoad(kPointer receiver, kAssembly assembly, void* args)
{
    kStaticObj(kDat5Serializer); 
    kSize i, j; 

    kCheck(kLock_Enter(sobj->lock)); 

    kTry
    {
        //build serialization guid-to-info map
        if (kObject_IsShared(sobj->guidToInfo))
        {
            kMap clone = kNULL; 

            kTest(kObject_Clone(&clone, sobj->guidToInfo, kNULL)); 
            kTest(kObject_Destroy(sobj->guidToInfo)); 

            sobj->guidToInfo = clone; 
        }
        
        for (i = 0; i < kAssembly_TypeCount(assembly); ++i)
        {
            kType type = kAssembly_TypeAt(assembly, i); 

            for (j = 0; j < kType_VersionCount(type); ++j)
            {
                kTypeVersion version = kType_VersionAt(type, j); 

                if (kStrEquals(kType_VersionFormat(type, version), xkDAT5_FORMAT))
                {
                    xkDat5SerializerTypeInfo info; 
                    k32u typeId, typeVersionId; 

                    info.type = type; 
                    info.version = version; 

                    if (sscanf(kType_VersionGuid(type, version), "%u-%u", &typeId, &typeVersionId) == 2)
                    {
                        info.id = xkDat5Guid_Create(typeId, typeVersionId); 

                        kTest(kMap_ReplaceT(sobj->guidToInfo, &info.id, &info)); 
                    }
                }
            }
        }

        //build compression guid-to-type map
        if (kObject_IsShared(sobj->guidToCompressor))
        {
            kMap clone = kNULL; 

            kTest(kObject_Clone(&clone, sobj->guidToCompressor, kNULL)); 
            kTest(kObject_Destroy(sobj->guidToCompressor)); 

            sobj->guidToCompressor = clone; 
        }

        for (i = 0; i < kAssembly_TypeCount(assembly); ++i)
        {
            kType type = kAssembly_TypeAt(assembly, i); 
            
            if (kType_Implements(type, kTypeOf(kCompressor)))
            {
                kCompressionType guid = kCast(kCompressorVTable*, kType_IVTable(type, kTypeOf(kCompressor)))->VCompressionType();

                kTest(kMap_ReplaceT(sobj->guidToCompressor, &guid, &type)); 
            }
        }
    }
    kFinally
    {
        kLock_Exit(sobj->lock);
        kEndFinally(); 
    }

    return kOK; 
}

kFx(kStatus) xkDat5Serializer_OnAssemblyUnload(kPointer receiver, kAssembly assembly, void* args)
{
    kStaticObj(kDat5Serializer); 
    kSize i, j; 

    kCheck(kLock_Enter(sobj->lock)); 

    kTry
    {
        //clear serialization guid-to-info map
        if (kObject_IsShared(sobj->guidToInfo))
        {
            kMap clone = kNULL; 

            kTest(kObject_Clone(&clone, sobj->guidToInfo, kNULL)); 
            kTest(kObject_Destroy(sobj->guidToInfo)); 

            sobj->guidToInfo = clone; 
        }
       
        for (i = 0; i < kAssembly_TypeCount(assembly); ++i)
        {
            kType type = kAssembly_TypeAt(assembly, i); 
            kMapItem item = kNULL; 

            for (j = 0; j < kType_VersionCount(type); ++j)
            {
                kTypeVersion version = kType_VersionAt(type, j); 

                if (kStrEquals(kType_VersionFormat(type, version), xkDAT5_FORMAT))
                {                   
                    k32u typeId, typeVersionId, typeGuid; 

                    if (sscanf(kType_VersionGuid(type, version), "%u-%u", &typeId, &typeVersionId) == 2)
                    {
                        typeGuid = xkDat5Guid_Create(typeId, typeVersionId); 

                        if (kSuccess(kMap_FindItemT(sobj->guidToInfo, &typeGuid, &item)))
                        {
                            kTest(kMap_RemoveItem(sobj->guidToInfo, item)); 
                        }
                    }
                }
            }
        }

        //clear compression guid-to-type map
        if (kObject_IsShared(sobj->guidToCompressor))
        {
            kMap clone = kNULL; 

            kTest(kObject_Clone(&clone, sobj->guidToCompressor, kNULL)); 
            kTest(kObject_Destroy(sobj->guidToCompressor)); 

            sobj->guidToCompressor = clone; 
        }

        for (i = 0; i < kAssembly_TypeCount(assembly); ++i)
        {
            kType type = kAssembly_TypeAt(assembly, i); 
            
            if (kType_Implements(type, kTypeOf(kCompressor)))
            {
                kCompressionType guid = kCast(kCompressorVTable*, kType_IVTable(type, kTypeOf(kCompressor)))->VCompressionType();
                kMapItem item = kNULL; 

                if (kSuccess(kMap_FindItemT(sobj->guidToCompressor, &guid, &item)))
                {
                    kTest(kMap_RemoveItem(sobj->guidToCompressor, item));
                }
            }
        }
    }
    kFinally
    {
        kLock_Exit(sobj->lock);
        kEndFinally(); 
    }

    return kOK; 
}

kFx(kStatus) xkDat5Serializer_GetTypeLookups(kMap* guidToInfo, kMap* guidToCompressor)
{
    kStaticObj(kDat5Serializer); 
    
    kCheck(kLock_Enter(sobj->lock)); 

    kTry
    {
        kTest(kObject_Share(sobj->guidToInfo)); 
        *guidToInfo = sobj->guidToInfo; 

        kTest(kObject_Share(sobj->guidToCompressor)); 
        *guidToCompressor = sobj->guidToCompressor; 
    }
    kFinally
    {
        kLock_Exit(sobj->lock);
        kEndFinally(); 
    }

    return kOK; 
}

kFx(kStatus) kDat5Serializer_Construct(kDat5Serializer* serializer, kStream stream, kAlloc allocator)
{
    kAlloc alloc = kAlloc_Fallback(allocator);
    kStatus status; 

    kCheck(kAlloc_GetObject(alloc, kTypeOf(kDat5Serializer), serializer)); 

    if (!kSuccess(status = kDat5Serializer_VInit(*serializer, kTypeOf(kDat5Serializer), stream, alloc)))
    {
        kAlloc_FreeRef(alloc, serializer); 
    }

    return status; 
} 

kFx(kStatus) kDat5Serializer_VInit(kDat5Serializer serializer, kType type, kStream stream, kAlloc allocator)
{
    kObjR(kDat5Serializer, serializer);
    kStatus status; 
    
    kCheck(kSerializer_VInit(serializer, type, stream, allocator)); 

    obj->objectDepth = 0;
    obj->guidToInfo = kNULL;
    obj->guidToCompressor = kNULL;
    obj->typeToInfo = kNULL;
    obj->currentCompressionAlgorithm = kCOMPRESSION_TYPE_NULL;
    obj->currentCompressionPreset = 0;
    obj->compressStream = kNULL;
    obj->currentDecompressionAlgorithm = kCOMPRESSION_TYPE_NULL;
    obj->decompressStream = kNULL;

    kStrCopy(obj->base.format, kCountOf(obj->base.format), xkDAT5_FORMAT);     

    kTry
    {
        kTest(xkDat5Serializer_GetTypeLookups(&obj->guidToInfo, &obj->guidToCompressor)); 
        kTest(kMap_Construct(&obj->typeToInfo, kTypeOf(kType), kTypeOf(xkDat5SerializerTypeInfo), 64, allocator)); 
    }
    kCatch(&status)
    {
        kDat5Serializer_VRelease(serializer); 
        kEndCatch(status); 
    }

    return kOK; 
}

kFx(kStatus) kDat5Serializer_VRelease(kDat5Serializer serializer)
{
    kObj(kDat5Serializer, serializer); 
        
    kCheck(kDestroyRef(&obj->compressStream)); 
    kCheck(kDestroyRef(&obj->decompressStream)); 

    kCheck(kObject_Destroy(obj->guidToInfo)); 
    kCheck(kObject_Destroy(obj->guidToCompressor)); 
    kCheck(kObject_Destroy(obj->typeToInfo)); 

    kCheck(kSerializer_VRelease(serializer)); 

    return kOK; 
}

kFx(kStatus) kDat5Serializer_WriteData(kDat5Serializer serializer, kObject object, const kChar* label)
{
    kObj(kDat5Serializer, serializer); 
    kText16 dummyLabel = { 0 }; 
    kStream storedWriteStream = obj->base.writeStream;
    kBool isOutermost = obj->objectDepth == 0; 
    kBool shouldCompress = isOutermost && (obj->currentCompressionAlgorithm != kCOMPRESSION_TYPE_NULL);  

    obj->objectDepth++; 

    kTry
    {
        if (isOutermost)
        {
            if (shouldCompress)
            {
                kTest(kSerializer_WriteByte(serializer, xkDAT5_SERIALIZER_CMD_COMPRESSED_OBJECT));
                kTest(xkDat5Serializer_BeginCompression(serializer));
            }
            else
            {
                kTest(kSerializer_WriteByte(serializer, xkDAT5_SERIALIZER_CMD_OBJECT));
            }
        }

        if (kIsNull(object))
        {
            kTest(kSerializer_Write32s(serializer, xkDAT5_SERIALIZER_CMD_NULL)); 
            kTest(kSerializer_Write32u(serializer, 0)); 
        }
        else
        {
            kType type = kObject_Type(object); 
            kTypeVersion version = kNULL; 
            
            kTest(kSerializer_WriteType(serializer, type, &version));        

            kTest(kSerializer_InvokeObjectSerializer(serializer, type, version, object)); 

            kTest(kSerializer_Write32u(serializer, 0));    //kData version

            if (!kIsNull(label))
            {
                if (!kSuccess(kStrCopy(dummyLabel, sizeof(dummyLabel), label))) {/* don't care */}
            }

            kTest(kSerializer_WriteCharArray(serializer, dummyLabel, sizeof(dummyLabel)));  
        }

        if (shouldCompress)
        {
            kTest(xkDat5Serializer_EndCompression(serializer));
        }
    }
    kFinally
    {
        obj->objectDepth--; 

        //precaution, in case of errors in compression; ensure write stream is restored
        obj->base.writeStream = storedWriteStream; 

        kEndFinally(); 
    }

    if (obj->objectDepth == 0)
    {
        kCheck(kSerializer_Flush(serializer)); 
        kCheckState(kArrayList_Count(obj->base.writeSections) == 0); 
    }

    return kOK; 
}

kFx(kStatus) kDat5Serializer_VWriteObject(kDat5Serializer serializer, kObject object)
{
    return kDat5Serializer_WriteData(serializer, object, kNULL);
}

kFx(kStatus) kDat5Serializer_ReadData(kDat5Serializer serializer, kObject* object, kChar* label, kSize capacity, kAlloc allocator)
{
    kObj(kDat5Serializer, serializer); 
    kAlloc alloc = kAlloc_Fallback(allocator); 
    kType type = kNULL; 
    kTypeVersion version = kNULL; 
    kByte objectCommand = 0;
    kObject output = kNULL;
    kText16 dummyLabel; 
    k32u dataVersion; 
    kStatus status; 
    kStream storedReadStream = obj->base.readStream;

    obj->objectDepth++; 

    kTry
    {
        obj->base.readAlloc = alloc; 

        if (obj->objectDepth == 1)
        {
            kTest(kSerializer_ReadByte(serializer, &objectCommand)); 

            if (objectCommand == xkDAT5_SERIALIZER_CMD_COMPRESSED_OBJECT)
            {
               kTest(xkDat5Serializer_BeginDecompression(serializer));
            }
            else
            {
                kTestState(objectCommand == xkDAT5_SERIALIZER_CMD_OBJECT);
            }
        }

        kTest(kSerializer_ReadType(serializer, &type, &version)); 

        if (kIsNull(type))
        {
            *object = kNULL; 
        }
        else
        {
            kTest(kAlloc_GetObject(alloc, type, &output)); 

            kTest(kSerializer_InvokeObjectDeserializer(serializer, type, version, output, alloc));

            kTest(kSerializer_Read32u(serializer, &dataVersion));
            kTest(kSerializer_ReadCharArray(serializer, dummyLabel, kCountOf(dummyLabel))); 

            if (!kIsNull(label) && capacity != 0)
            {
                kTest(kStrCopy(label, capacity, dummyLabel));
            }
        }

        if (objectCommand == xkDAT5_SERIALIZER_CMD_COMPRESSED_OBJECT)
        {
            kTest(xkDat5Serializer_EndDecompression(serializer));
        }
    }
    kCatchEx(&status)
    {
        if (!kIsNull(output)) kAlloc_FreeRef(alloc, &output); 
        kEndCatchEx(status);
    }
    kFinallyEx
    {
        obj->objectDepth--; 

        //precaution, in case of errors in compression; ensure read stream is restored
        obj->base.readStream = storedReadStream; 

        kEndFinally(); 
    }

    if (obj->objectDepth == 0)
    {
        kCheckState(kArrayList_Count(obj->base.readSections) == 0); 
    }

    *object = output;
    return kOK; 
}

kFx(kStatus) kDat5Serializer_VReadObject(kDat5Serializer serializer, kObject* object, kAlloc allocator)
{
    return kDat5Serializer_ReadData(serializer, object, kNULL, 0, allocator);
}

kFx(kStatus) xkDat5Serializer_VWriteType(kDat5Serializer serializer, kType type, kTypeVersion* version)
{
    kObj(kDat5Serializer, serializer); 
    xkDat5SerializerTypeInfo info; 
    k32u typeId, typeVersionId; 

    if (!kSuccess(kMap_FindT(obj->typeToInfo, &type, &info)))
    {    
        kCheck(kSerializer_FindCompatibleVersion(serializer, type, version)); 

        if (sscanf(kType_VersionGuid(type, *version), "%u-%u", &typeId, &typeVersionId) != 2)
        {
            return kERROR_FORMAT; 
        }

        info.type = type; 
        info.version = *version;        
        info.id = xkDat5Guid_Create(typeId, typeVersionId); 

        kCheck(kMap_AddT(obj->typeToInfo, &type, &info));         
    }
    else
    {
        typeId = xkDat5Guid_Type(info.id); 
        typeVersionId = xkDat5Guid_Version(info.id); 
    }

    kCheck(kSerializer_Write32u(serializer, typeId)); 
    kCheck(kSerializer_Write32u(serializer, typeVersionId)); 

    *version = info.version; 

    return kOK; 
}

kFx(kStatus) xkDat5Serializer_VReadType(kDat5Serializer serializer, kType* type, kTypeVersion* version)
{
    kObj(kDat5Serializer, serializer); 
    const xkDat5SerializerTypeInfo* info = kNULL; 
    kMapItem item = kNULL; 
    k32s typeId; 
    k32s typeVersionId; 
    k32u guid; 

    kCheck(kSerializer_Read32s(serializer, &typeId)); 
    kCheck(kSerializer_Read32s(serializer, &typeVersionId)); 

    if (typeId == xkDAT5_SERIALIZER_CMD_NULL)
    {
        //null is treated as special case
        *type = kNULL; 
        *version = kNULL; 

        return kOK; 
    }
    else if (typeId == xkDAT5_SERIALIZER_CMD_REF)
    {
        //references not supported
        return kERROR_FORMAT; 
    }

    guid = xkDat5Guid_Create((k32u)typeId, (k32u)typeVersionId); 

    kCheck(kMap_FindItemT(obj->guidToInfo, &guid, &item));
    info = kMap_ValueT(obj->guidToInfo, item, xkDat5SerializerTypeInfo); 

    *type = info->type; 
    *version = info->version; 
  
    return kOK; 
}

kFx(kStatus) xkDat5Serializer_WriteTypeWithoutVersion(kDat5Serializer serializer, kType type, kTypeVersion* version)
{
    kObj(kDat5Serializer, serializer); 
    xkDat5SerializerTypeInfo info; 
    k32u typeId, typeVersionId; 

    if (!kSuccess(kMap_FindT(obj->typeToInfo, &type, &info)))
    {    
        kCheck(kSerializer_FindCompatibleVersion(serializer, type, version)); 

        if (sscanf(kType_VersionGuid(type, *version), "%u-%u", &typeId, &typeVersionId) != 2)
        {
            return kERROR_FORMAT; 
        }

        info.type = type; 
        info.version = *version;        
        info.id = xkDat5Guid_Create(typeId, typeVersionId); 

        kCheck(kMap_AddT(obj->typeToInfo, &type, &info));         
    }
    else
    {
        typeId = xkDat5Guid_Type(info.id); 
        typeVersionId = xkDat5Guid_Version(info.id); 
    }

    kCheck(kSerializer_Write32u(serializer, typeId)); 

    *version = info.version; 

    return kOK; 
}

kFx(kStatus) xkDat5Serializer_ReadTypeExplicitVersion(kDat5Serializer serializer, k32u typeVersionId, kType* type, kTypeVersion* version)
{
    kObj(kDat5Serializer, serializer); 
    const xkDat5SerializerTypeInfo* info = kNULL; 
    kMapItem item = kNULL; 
    k32s typeId; 
    k32u guid; 

    kCheck(kSerializer_Read32s(serializer, &typeId)); 
    
    if (typeId == xkDAT5_SERIALIZER_CMD_NULL)
    {
        //null is treated as special case
        *type = kNULL; 
        *version = kNULL; 

        return kOK; 
    }
    else if (typeId == xkDAT5_SERIALIZER_CMD_REF)
    {
        //references not supported
        return kERROR_FORMAT; 
    }

    guid = xkDat5Guid_Create((k32u)typeId, typeVersionId); 

    kCheck(kMap_FindItemT(obj->guidToInfo, &guid, &item));
    info = kMap_ValueT(obj->guidToInfo, item, xkDat5SerializerTypeInfo); 

    *type = info->type; 
    *version = info->version; 
  
    return kOK; 
}


kFx(kStatus) xkDat5Serializer_BeginCompression(kDat5Serializer serializer)
{
    kObj(kDat5Serializer, serializer);

    kCheck(kSerializer_BeginWrite(serializer, kTypeOf(k16u), kTRUE));
    kCheck(kSerializer_Write8u(serializer, (k8u)obj->currentCompressionAlgorithm));
    kCheck(kSerializer_EndWrite(serializer));

    kCheck(xkSerializer_FlushEx(serializer, kFALSE));

    //replace underlying stream with compression stream
    obj->base.writeStream = obj->compressStream;

    return kOK;
}

kFx(kStatus) xkDat5Serializer_EndCompression(kDat5Serializer serializer)
{
    kObj(kDat5Serializer, serializer);

    //flush serializer to compression stream, but don't flush the compression stream itself
    kCheck(xkSerializer_FlushEx(serializer, kFALSE));

    //finish writing the current compression stream
    kCheck(kCompressor_FinishWrite(obj->compressStream)); 
                
    //restore underlying stream 
    obj->base.writeStream = obj->base.stream;

    return kOK;
}

kFx(kStatus) xkDat5Serializer_BeginDecompression(kDat5Serializer serializer)
{
    kObj(kDat5Serializer, serializer);
    kCompressionType algorithm = kCOMPRESSION_TYPE_NULL;
    kType decompressorType = kNULL; 
    k8u temp8u = 0;

    //read out decompression configuration
    kCheck(kSerializer_BeginRead(serializer, kTypeOf(k16u), kTRUE)); 
    kCheck(kSerializer_Read8u(serializer, &temp8u));            
    kCheck(kSerializer_EndRead(serializer)); 

    algorithm = temp8u;

    //change compressor, if necessary
    if (obj->currentDecompressionAlgorithm != algorithm)
    {
        if (algorithm != kCOMPRESSION_TYPE_NULL)
        {
            kCheck(kMap_FindT(obj->guidToCompressor, &algorithm, &decompressorType)); 

            //destroy the previous decompressor, if a new & different decompressor has been specified            
            if (!kIsNull(obj->decompressStream))
            {
                if (!kObject_Is(obj->decompressStream, decompressorType))
                {
                    kDestroyRef(&obj->decompressStream);
                }
            }

            //construct a new decompressor, if needed
            if (kIsNull(obj->decompressStream))
            {
                kCheck(kCast(kCompressorVTable*, kType_IVTable(decompressorType, kTypeOf(kCompressor)))->VConstruct(&obj->decompressStream, obj->base.stream, kFALSE, 0, kObject_Alloc(serializer)));
            }
        }
    }

    obj->currentDecompressionAlgorithm = algorithm; 

    //replace underlying stream with compression stream
    obj->base.readStream = obj->decompressStream;

    return kOK;
}

kFx(kStatus) xkDat5Serializer_EndDecompression(kDat5Serializer serializer)
{
    kObj(kDat5Serializer, serializer);

    //finish reading the current compression stream
    kCheck(kCompressor_FinishRead(obj->decompressStream)); 
                
    //restore underlying stream 
    obj->base.readStream = obj->base.stream;

    return kOK;
}

kFx(kStatus) kDat5Serializer_EnableCompression(kDat5Serializer serializer, kCompressionType algorithm, k32s level)
{
    kObj(kDat5Serializer, serializer); 
    kType compressorType = kNULL;

    //check args
    if (algorithm != kCOMPRESSION_TYPE_NULL)
    {
        kType compressorType = kNULL; 
        kVersion requestedVersion, supportedVersion; 

        //check that the requested compression algorithm is available
        kCheck(kMap_FindT(obj->guidToCompressor, &algorithm, &compressorType));
        
        //check that the requested compression algorithm is supported in the platform version that we're aiming for compatibility with
        requestedVersion = kSerializer_FindRequestedMinVersion(serializer, compressorType); 
        supportedVersion = kCast(kCompressorVTable*, kType_IVTable(compressorType, kTypeOf(kCompressor)))->VRequiredVersion();

        kCheckTrue(requestedVersion >= supportedVersion, kERROR_VERSION);
    }

    //change compressor, if necessary
    if ((obj->currentCompressionAlgorithm != algorithm) || (obj->currentCompressionPreset != level))
    {        
        if (algorithm != kCOMPRESSION_TYPE_NULL)
        {
            kCheck(kMap_FindT(obj->guidToCompressor, &algorithm, &compressorType)); 

            //destroy the previous compressor, if a new & different compressor has been specified            
            if (!kIsNull(obj->compressStream))
            {
                if (!kObject_Is(obj->compressStream, compressorType) || (level != obj->currentCompressionPreset))
                {
                    kDestroyRef(&obj->compressStream);
                }
            }

            //construct a new compressor, if needed
            if (kIsNull(obj->compressStream))
            {
                kCheck(kCast(kCompressorVTable*, kType_IVTable(compressorType, kTypeOf(kCompressor)))->VConstruct(&obj->compressStream, obj->base.stream, kTRUE, level, kObject_Alloc(serializer)));
            }

            obj->currentCompressionPreset = level;
        }

        obj->currentCompressionAlgorithm = algorithm;
    }    
    
    return kOK; 
} 

kFx(kStatus) kDat5Serializer_SaveCompressed(kObject object, const kChar* filePath, kCompressionType algorithm, k32s level)
{
    kSerializer writer = kNULL; 
    kFile stream = kNULL; 

    kTry
    {        
        kTest(kFile_Construct(&stream, filePath, kFILE_MODE_WRITE, kNULL)); 

        kTest(kDat5Serializer_Construct(&writer, stream, kNULL)); 
        kTest(kDat5Serializer_EnableCompression(writer, algorithm, level)); 

        kTest(kSerializer_WriteObject(writer, object)); 
    }
    kFinally
    {
        kCheck(kObject_Destroy(writer)); 
        kCheck(kObject_Destroy(stream)); 

        kEndFinally(); 
    }

    return kOK; 
}