/**
 * @file    GoSerializer.c
 *
 * @internal
 * Copyright (C) 2016-2022 by LMI Technologies Inc.
 * Licensed under the MIT License.
 * Redistributed files must retain the above copyright notice.
 */
#include <GoSdk/Internal/GoSerializer.h>
#include <GoSdk/Messages/GoDataSet.h>
#include <GoSdk/GoSdkLib.h>
#include <stdlib.h>

kBeginValueEx(Go, GoSerializerTypeInfo)
    kAddField(GoSerializerTypeInfo, kType, type)
    kAddField(GoSerializerTypeInfo, kPointer, version)
    kAddField(GoSerializerTypeInfo, k16u, id)
kEndValueEx()

kBeginClassEx(Go, GoSerializer)
    kAddVMethod(GoSerializer, kObject, VRelease)
    kAddVMethod(GoSerializer, kSerializer, VInit)
    kAddVMethod(GoSerializer, kSerializer, VWriteObject)
    kAddVMethod(GoSerializer, kSerializer, VReadObject)
kEndClassEx()

GoFx(kStatus) GoSerializer_Construct(GoSerializer* serializer, kStream stream, kAlloc allocator)
{
    kAlloc alloc = kAlloc_Fallback(allocator);
    kStatus status;

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

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

    return status;
}

GoFx(kStatus) GoSerializer_VInit(GoSerializer serializer, kType type, kStream stream, kAlloc allocator)
{
    kObjR(GoSerializer, serializer);
    kStatus status;

    kCheck(kSerializer_VInit(serializer, type, stream, allocator));

    obj->typeToInfo = kNULL;
    obj->idToType = kNULL;

    kTry
    {
        kTest(kStrCopy(obj->base.format, kCountOf(obj->base.format), GO_SERIALIZATION_FORMAT_NAME));
        kTest(kSerializer_SetVersion(serializer, kNULL, GoSdk_ProtocolVersion()));

        kTest(kMap_Construct(&obj->typeToInfo, kTypeOf(kType), kTypeOf(GoSerializerTypeInfo), 0, allocator));
        kTest(kMap_Construct(&obj->idToType, kTypeOf(k16u), kTypeOf(GoSerializerTypeInfo), 0, allocator));

        kTest(GoSerializer_BuildIdToTypeMap(serializer));
    }
    kCatch(&status)
    {
        GoSerializer_VRelease(serializer);
        kEndCatch(status);
    }

    return kOK;
}

GoFx(kStatus) GoSerializer_VRelease(GoSerializer serializer)
{
    kObj(GoSerializer, serializer);

    kCheck(kObject_Destroy(obj->typeToInfo));
    kCheck(kObject_Destroy(obj->idToType));

    kCheck(kSerializer_VRelease(serializer));

    return kOK;
}

GoFx(kStatus) GoSerializer_VWriteObject(GoSerializer serializer, kObject object)
{
    kSize count;
    kSize i;

    kCheckArgs(!kIsNull(object) && (kObject_Type(object) == kTypeOf(GoDataSet)));

    count = GoDataSet_Count(object);

    for (i = 0; i < count; ++i)
    {
        kObject item = GoDataSet_At(object, i);
        kBool isLast = (i == (count-1));
        kSerializerObjectSerializeFx serializeFx = kNULL;

        kCheck(kSerializer_BeginWrite(serializer, kTypeOf(k32u), kTRUE));
        kCheck(GoSerializer_WriteTypeId(serializer, kObject_Type(item), isLast, &serializeFx));

        kCheck(serializeFx(item, serializer));

        kCheck(kSerializer_EndWrite(serializer));
    }

    kCheck(kSerializer_Flush(serializer));

    return kOK;
}

GoFx(kStatus) GoSerializer_VReadObject(GoSerializer serializer, kObject* object, kAlloc allocator)
{
    kAlloc alloc = kAlloc_Fallback(allocator);
    GoDataSet output = kNULL;
    kObject item = kNULL;
    kBool isLast = kFALSE;
    kStatus status;

    kTry
    {
        kTest(GoDataSet_Construct(&output, alloc));

        while (!isLast)
        {
            kType itemType = kNULL;
            kSerializerObjectDeserializeFx deserializeFx = kNULL;

            kTest(kSerializer_BeginRead(serializer, kTypeOf(k32u), kTRUE));

            kTest(GoSerializer_ReadTypeId(serializer, &itemType, &isLast, &deserializeFx));

            kTest(GoSerializer_ConstructItem(serializer, &item, itemType, deserializeFx, alloc));

            kTest(kSerializer_EndRead(serializer));

            kTest(GoDataSet_Add(output, item));
            item = kNULL;
        }

        *object = output;
    }
    kCatch(&status)
    {
        kObject_Dispose(item);
        kObject_Dispose(output);
        kEndCatch(status);
    }

    return kOK;
}

GoFx(kStatus) GoSerializer_WriteTypeId(GoSerializer serializer, kType type, kBool isLast, kSerializerObjectSerializeFx* fx)
{
    kObj(GoSerializer, serializer);
    GoSerializerTypeInfo info;
    k16u typeId;

    if (!kSuccess(kMap_FindT(obj->typeToInfo, &type, &info)))
    {
        info.type = type;
        kCheck(kSerializer_FindCompatibleVersion(serializer, type, &info.version));
        info.id = (k16u) atoi(kType_VersionGuid(info.type, info.version));

        kCheck(kMap_AddT(obj->typeToInfo, &type, &info));
    }

    typeId = (k16u) ((isLast << 15) | info.id);

    kCheck(kSerializer_Write16u(serializer, typeId));

    *fx = (kSerializerObjectSerializeFx) kType_VersionSerializeFx(info.type, info.version);

    return kOK;
}

GoFx(kStatus) GoSerializer_ReadTypeId(GoSerializer serializer, kType* type, kBool* isLast, kSerializerObjectDeserializeFx* fx)
{
    kObj(GoSerializer, serializer);
    GoSerializerTypeInfo info;
    k16u typeId;
    k16u id;

    kCheck(kSerializer_Read16u(serializer, &typeId));

    id = typeId & 0x7FFF;
    *isLast = (typeId >> 15);

    kCheck(kMap_FindT(obj->idToType, &id, &info));

    *type = info.type;
    *fx = (kSerializerObjectDeserializeFx) kType_VersionDeserializeFx(info.type, info.version);

    return kOK;
}

GoFx(kStatus) GoSerializer_ConstructItem(GoSerializer serializer, kObject* item, kType type, kSerializerObjectDeserializeFx initializer, kAlloc alloc)
{
    kStatus status;

    kCheck(kAlloc_GetObject(alloc, type, item));

    if (!kSuccess(status = initializer(*item, serializer, alloc)))
    {
        kAlloc_FreeRef(alloc, item);
    }

    return status;
}

GoFx(kStatus) GoSerializer_BuildIdToTypeMap(GoSerializer serializer)
{
    kObj(GoSerializer, serializer);
    kAssembly assembly = kAssemblyOf(GoSdk);
    kSize typeCount = kAssembly_TypeCount(assembly);
    GoSerializerTypeInfo info;
    kSize i, j;

    kCheck(kMap_Clear(obj->idToType));

    for (i = 0; i < typeCount; ++i)
    {
        kType type = kAssembly_TypeAt(assembly, i);
        kSize versionCount = kType_VersionCount(type);

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

           if (kStrEquals(kType_VersionFormat(type, version), GO_SERIALIZATION_FORMAT_NAME))
           {
               const kChar* guid = kType_VersionGuid(type, version);

               info.type = type;
               info.version = version;
               info.id = (k16u) atoi(guid);

               kCheck(kMap_AddT(obj->idToType, &info.id, &info));
           }
        }
    }

    return kOK;
}
