#include "slang-rtti-util.h"

#include "slang-allocator.h"

namespace Slang
{

static void* _mallocArray(Index count, size_t elementSize)
{
    if (count < 0)
    {
        return nullptr;
    }

    const size_t unsignedCount = size_t(count);
    if (unsignedCount > 0 && elementSize > size_t(-1) / unsignedCount)
    {
        return nullptr;
    }

    return StandardAllocator::allocate(unsignedCount * elementSize);
}

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! RttiTypeFuncs Impls
 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

struct ListFuncs
{
    static void ctorArray(
        RttiTypeFuncsMap* typeMap,
        const RttiInfo* rttiInfo,
        void* inDst,
        Index count)
    {
        SLANG_UNUSED(typeMap);
        SLANG_UNUSED(rttiInfo);
        SLANG_ASSERT(rttiInfo->m_kind == RttiInfo::Kind::List);

        // We don't care about the element type, as we can just initialize them all as List<Byte>
        // const ListRttiInfo* listRttiInfo = static_cast<const ListRttiInfo*>(rttiInfo);
        typedef List<Byte> Type;

        Type* dst = (Type*)inDst;

        for (Index i = 0; i < count; ++i)
        {
            new (dst + i) Type;
        }
    }

    static void copyArray(
        RttiTypeFuncsMap* typeMap,
        const RttiInfo* rttiInfo,
        void* inDst,
        const void* inSrc,
        Index count)
    {
        SLANG_ASSERT(rttiInfo->m_kind == RttiInfo::Kind::List);
        const ListRttiInfo* listRttiInfo = static_cast<const ListRttiInfo*>(rttiInfo);
        const auto elementType = listRttiInfo->m_elementType;

        // We need to get the type funcs
        auto typeFuncs = typeMap->getFuncsForType(elementType);
        SLANG_ASSERT(typeFuncs.isValid());

        // We need a type that we can get information from the list from - List<Byte> gives us the
        // functions we need.
        typedef List<Byte> Type;

        Type* dst = (Type*)inDst;
        const Type* src = (const Type*)inSrc;

        for (Index i = 0; i < count; ++i)
        {
            auto& dstList = dst[i];
            auto& srcList = src[i];

            const Index srcCount = srcList.getCount();

            if (srcCount > dstList.getCount())
            {
                // Allocate new memory
                void* newBuffer = _mallocArray(srcCount, elementType->m_size);
                SLANG_RELEASE_ASSERT(newBuffer);

                const Index dstCapacity = dstList.getCapacity();
                void* oldBuffer = dstList.detachBuffer();

                // Initialize it all first
                typeFuncs.ctorArray(typeMap, elementType, newBuffer, srcCount);
                typeFuncs.copyArray(typeMap, elementType, newBuffer, srcList.getBuffer(), srcCount);

                // Attach the new buffer
                dstList.attachBuffer((Byte*)newBuffer, srcCount, srcCount);

                // Free the old buffer
                if (oldBuffer)
                {
                    typeFuncs.dtorArray(typeMap, elementType, oldBuffer, dstCapacity);

                    StandardAllocator::deallocate(oldBuffer);
                }
            }
            else
            {
                typeFuncs.copyArray(
                    typeMap,
                    elementType,
                    dstList.getBuffer(),
                    srcList.getBuffer(),
                    srcCount);
                dstList.unsafeShrinkToCount(srcCount);
            }
        }
    }

    static void dtorArray(
        RttiTypeFuncsMap* typeMap,
        const RttiInfo* rttiInfo,
        void* inDst,
        Index count)
    {
        SLANG_ASSERT(rttiInfo->m_kind == RttiInfo::Kind::List);
        const ListRttiInfo* listRttiInfo = static_cast<const ListRttiInfo*>(rttiInfo);

        const auto elementType = listRttiInfo->m_elementType;

        // We need to get the type funcs
        auto typeFuncs = typeMap->getFuncsForType(elementType);
        SLANG_ASSERT(typeFuncs.isValid());

        typedef List<Byte> Type;
        Type* dst = (Type*)inDst;

        for (Index i = 0; i < count; ++i)
        {
            auto& dstList = dst[i];

            const Index capacity = dstList.getCapacity();
            Byte* buffer = dstList.detachBuffer();

            if (buffer)
            {
                typeFuncs.dtorArray(typeMap, elementType, buffer, capacity);
                StandardAllocator::deallocate(buffer);
            }
        }
    }

    static RttiTypeFuncs getFuncs()
    {
        RttiTypeFuncs funcs;
        funcs.copyArray = &copyArray;
        funcs.dtorArray = &dtorArray;
        funcs.ctorArray = &ctorArray;
        return funcs;
    }
};

struct StructArrayFuncs
{
    static void ctorArray(
        RttiTypeFuncsMap* typeMap,
        const RttiInfo* rttiInfo,
        void* inDst,
        Index count)
    {
        return RttiUtil::ctorArray(typeMap, rttiInfo, inDst, rttiInfo->m_size, count);
    }

    static void copyArray(
        RttiTypeFuncsMap* typeMap,
        const RttiInfo* rttiInfo,
        void* inDst,
        const void* inSrc,
        Index count)
    {
        return RttiUtil::copyArray(typeMap, rttiInfo, inDst, inSrc, rttiInfo->m_size, count);
    }

    static void dtorArray(
        RttiTypeFuncsMap* typeMap,
        const RttiInfo* rttiInfo,
        void* inDst,
        Index count)
    {
        return RttiUtil::dtorArray(typeMap, rttiInfo, inDst, rttiInfo->m_size, count);
    }

    static RttiTypeFuncs getFuncs()
    {
        RttiTypeFuncs funcs;
        funcs.copyArray = copyArray;
        funcs.dtorArray = dtorArray;
        funcs.ctorArray = ctorArray;
        return funcs;
    }
};

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! RttiUtil !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

RttiTypeFuncs RttiUtil::getDefaultTypeFuncs(const RttiInfo* rttiInfo)
{
    if (rttiInfo->isBuiltIn())
    {
        switch (rttiInfo->m_size)
        {
        case 1:
            return GetRttiTypeFuncsForZeroPod<uint8_t>::getFuncs();
        case 2:
            return GetRttiTypeFuncsForZeroPod<uint16_t>::getFuncs();
        case 4:
            return GetRttiTypeFuncsForZeroPod<uint32_t>::getFuncs();
        case 8:
            return GetRttiTypeFuncsForZeroPod<uint64_t>::getFuncs();
        }
        return RttiTypeFuncs::makeEmpty();
    }

    switch (rttiInfo->m_kind)
    {
    case RttiInfo::Kind::String:
        return GetRttiTypeFuncs<String>::getFuncs();
    case RttiInfo::Kind::UnownedStringSlice:
        return GetRttiTypeFuncs<UnownedStringSlice>::getFuncs();
    case RttiInfo::Kind::List:
        return ListFuncs::getFuncs();
    case RttiInfo::Kind::Struct:
    case RttiInfo::Kind::Optional:
        return StructArrayFuncs::getFuncs();
    default:
        break;
    }

    return RttiTypeFuncs::makeEmpty();
}

/* static */ SlangResult RttiUtil::setInt(int64_t value, const RttiInfo* rttiInfo, void* dst)
{
    SLANG_ASSERT(rttiInfo->isIntegral());

    // We could check ranges are appropriate, but for now we just write.
    // Passing in rttiInfo allows for other more complex types to be econverted
    switch (rttiInfo->m_kind)
    {
    case RttiInfo::Kind::I32:
        *(int32_t*)dst = int32_t(value);
        break;
    case RttiInfo::Kind::U32:
        *(uint32_t*)dst = uint32_t(value);
        break;
    case RttiInfo::Kind::I64:
        *(int64_t*)dst = int64_t(value);
        break;
    case RttiInfo::Kind::U64:
        *(uint64_t*)dst = uint64_t(value);
        break;
    default:
        return SLANG_FAIL;
    }
    return SLANG_OK;
}

/* static */ int64_t RttiUtil::getInt64(const RttiInfo* rttiInfo, const void* src)
{
    SLANG_ASSERT(rttiInfo->isIntegral());

    switch (rttiInfo->m_kind)
    {
    case RttiInfo::Kind::I32:
        return *(const int32_t*)src;
    case RttiInfo::Kind::U32:
        return *(const uint32_t*)src;
    case RttiInfo::Kind::I64:
        return *(const int64_t*)src;
    case RttiInfo::Kind::U64:
        return *(const uint64_t*)src;
    default:
        break;
    }

    SLANG_ASSERT(!"Not integral!");
    return -1;
}

/* static */ double RttiUtil::asDouble(const RttiInfo* rttiInfo, const void* src)
{
    if (rttiInfo->isIntegral())
    {
        return (double)getInt64(rttiInfo, src);
    }
    else if (rttiInfo->isFloat())
    {
        switch (rttiInfo->m_kind)
        {
        case RttiInfo::Kind::F32:
            return *(const float*)src;
        case RttiInfo::Kind::F64:
            return *(const double*)src;
        default:
            break;
        }
    }

    SLANG_ASSERT(!"Cannot convert to float");
    return 0.0;
}

/* static */ SlangResult RttiUtil::setFromDouble(double v, const RttiInfo* rttiInfo, void* dst)
{
    if (rttiInfo->isIntegral())
    {
        return setInt(int64_t(v), rttiInfo, dst);
    }
    else if (rttiInfo->isFloat())
    {
        switch (rttiInfo->m_kind)
        {
        case RttiInfo::Kind::F32:
            *(float*)dst = float(v);
            return SLANG_OK;
        case RttiInfo::Kind::F64:
            *(double*)dst = v;
            return SLANG_OK;
        default:
            break;
        }
    }

    return SLANG_FAIL;
}

/* static */ bool RttiUtil::asBool(const RttiInfo* rttiInfo, const void* src)
{
    if (rttiInfo->m_kind == RttiInfo::Kind::Bool)
    {
        return *(const bool*)src;
    }

    if (rttiInfo->isIntegral())
    {
        return getInt64(rttiInfo, src) != 0;
    }
    else if (rttiInfo->isFloat())
    {
        return asDouble(rttiInfo, src) != 0.0;
    }

    SLANG_ASSERT(!"Cannot convert to bool");
    return false;
}

static int64_t _getIntDefaultValue(RttiDefaultValue value)
{
    switch (value)
    {
    default:
    case RttiDefaultValue::Normal:
        return 0;
    case RttiDefaultValue::One:
        return 1;
    case RttiDefaultValue::MinusOne:
        return -1;
    }
}

static bool _isStructDefault(const StructRttiInfo* type, const void* src)
{
    if (type->m_super)
    {
        if (!_isStructDefault(type->m_super, src))
        {
            return false;
        }
    }

    const Byte* base = (const Byte*)src;

    const Index count = type->m_fieldCount;
    for (Index i = 0; i < count; ++i)
    {
        const auto& field = type->m_fields[i];

        const RttiDefaultValue defaultValue =
            RttiDefaultValue(field.m_flags & uint8_t(RttiDefaultValue::Mask));

        if (!RttiUtil::isDefault(defaultValue, field.m_type, base + field.m_offset))
        {
            return false;
        }
    }

    return true;
}

/* static */ bool RttiUtil::isDefault(
    RttiDefaultValue defaultValue,
    const RttiInfo* rttiInfo,
    const void* src)
{
    if (rttiInfo->isIntegral())
    {
        const auto value = getInt64(rttiInfo, src);
        return _getIntDefaultValue(defaultValue) == value;
    }
    else if (rttiInfo->isFloat())
    {
        const auto value = asDouble(rttiInfo, src);
        return _getIntDefaultValue(defaultValue) == value;
    }

    switch (rttiInfo->m_kind)
    {
    case RttiInfo::Kind::Invalid:
        return true;
    case RttiInfo::Kind::Bool:
        return *(const bool*)src == (_getIntDefaultValue(defaultValue) != 0);
    case RttiInfo::Kind::String:
        {
            return ((const String*)src)->getLength() == 0;
        }
    case RttiInfo::Kind::UnownedStringSlice:
        {
            return ((const UnownedStringSlice*)src)->getLength() == 0;
        }
    case RttiInfo::Kind::Struct:
        {
            return _isStructDefault(static_cast<const StructRttiInfo*>(rttiInfo), src);
        }
    case RttiInfo::Kind::Enum:
        {
            SLANG_ASSERT(!"Not implemented yet");
            return false;
        }
    case RttiInfo::Kind::List:
        {
            const auto& v = *(const List<Byte>*)src;
            return v.getCount() == 0;
        }
    case RttiInfo::Kind::Dictionary:
        {
            const auto& v = *(const Dictionary<Byte, Byte>*)src;
            return v.getCount() == 0;
        }
    case RttiInfo::Kind::Other:
        {
            const OtherRttiInfo* otherRttiInfo = static_cast<const OtherRttiInfo*>(rttiInfo);
            return otherRttiInfo->m_isDefaultFunc && otherRttiInfo->m_isDefaultFunc(rttiInfo, src);
        }
    case RttiInfo::Kind::Optional:
        {
            return *(const bool*)src == (_getIntDefaultValue(defaultValue) != 0);
        }
    default:
        {
            return false;
        }
    }
}

/* static */ SlangResult RttiUtil::setListCount(
    RttiTypeFuncsMap* typeMap,
    const RttiInfo* elementType,
    void* dst,
    Index count)
{
    // NOTE! The following only works because List<T> has capacity initialized members, and
    // setting the count if it is <= capacity just sets the count (ie things aren't released(!)).

    List<Byte>& dstList = *(List<Byte>*)dst;

    const auto typeFuncs = typeMap->getFuncsForType(elementType);
    SLANG_ASSERT(typeFuncs.isValid());

    if (count < 0)
    {
        return SLANG_E_INVALID_ARG;
    }

    void* newBuffer = _mallocArray(count, elementType->m_size);
    if (!newBuffer && count != 0)
    {
        return SLANG_E_OUT_OF_MEMORY;
    }

    // Initialize it all first
    typeFuncs.ctorArray(typeMap, elementType, newBuffer, count);

    // Attach the new buffer
    dstList.attachBuffer((Byte*)newBuffer, count, count);

    return SLANG_OK;
}

/* static */ bool RttiUtil::canMemCpy(const RttiInfo* type)
{
    switch (type->m_kind)
    {
    case RttiInfo::Kind::RefPtr:
    case RttiInfo::Kind::String:
    case RttiInfo::Kind::Invalid:
        {
            return false;
        }
    case RttiInfo::Kind::UnownedStringSlice:
    case RttiInfo::Kind::Ptr:
    case RttiInfo::Kind::Enum:
        {
            return true;
        }
    case RttiInfo::Kind::FixedArray:
        {
            const FixedArrayRttiInfo* fixedArrayRttiInfo =
                static_cast<const FixedArrayRttiInfo*>(type);
            return canMemCpy(fixedArrayRttiInfo->m_elementType);
        }
    case RttiInfo::Kind::Other:
    case RttiInfo::Kind::List:
    case RttiInfo::Kind::Dictionary:
        {
            return false;
        }
    case RttiInfo::Kind::Struct:
        {
            const StructRttiInfo* structRttiInfo = static_cast<const StructRttiInfo*>(type);

            do
            {
                // If all the fields can be zero inited, struct can be
                const auto fieldCount = structRttiInfo->m_fieldCount;
                const auto fields = structRttiInfo->m_fields;

                for (Index i = 0; i < fieldCount; ++i)
                {
                    const auto& field = fields[i];
                    if (!canMemCpy(field.m_type))
                    {
                        return false;
                    }
                }
                structRttiInfo = structRttiInfo->m_super;
            } while (structRttiInfo);

            return true;
        }
    case RttiInfo::Kind::Optional:
        {
            const OptionalRttiInfo* optionalRttiInfo = static_cast<const OptionalRttiInfo*>(type);
            return canMemCpy(optionalRttiInfo->m_elementType);
        }
    default:
        {
            return type->isBuiltIn();
        }
    }
}

/* static */ bool RttiUtil::canZeroInit(const RttiInfo* type)
{
    switch (type->m_kind)
    {
    case RttiInfo::Kind::Invalid:
        {
            return true;
        }
    case RttiInfo::Kind::String:
        {
            // As it stands we can zero init String, but if impl changes that might not
            // be true
            return true;
        }
    case RttiInfo::Kind::UnownedStringSlice:
    case RttiInfo::Kind::Ptr:
    case RttiInfo::Kind::RefPtr:
    case RttiInfo::Kind::Enum:
        {
            return true;
        }
    case RttiInfo::Kind::FixedArray:
        {
            const FixedArrayRttiInfo* fixedArrayRttiInfo =
                static_cast<const FixedArrayRttiInfo*>(type);
            return canZeroInit(fixedArrayRttiInfo->m_elementType);
        }
    case RttiInfo::Kind::Other:
    case RttiInfo::Kind::List:
    case RttiInfo::Kind::Dictionary:
        {
            return false;
        }
    case RttiInfo::Kind::Struct:
        {
            const StructRttiInfo* structRttiInfo = static_cast<const StructRttiInfo*>(type);

            do
            {
                // If all the fields can be zero inited, struct can be
                const auto fieldCount = structRttiInfo->m_fieldCount;
                const auto fields = structRttiInfo->m_fields;

                for (Index i = 0; i < fieldCount; ++i)
                {
                    const auto& field = fields[i];
                    if (!canZeroInit(field.m_type))
                    {
                        return false;
                    }
                }
                structRttiInfo = structRttiInfo->m_super;
            } while (structRttiInfo);

            return true;
        }
    case RttiInfo::Kind::Optional:
        {
            const OptionalRttiInfo* optionalRttiInfo = static_cast<const OptionalRttiInfo*>(type);
            return canZeroInit(optionalRttiInfo->m_elementType);
        }
    default:
        {
            return type->isBuiltIn();
        }
    }
}

/* static */ bool RttiUtil::hasDtor(const RttiInfo* type)
{
    switch (type->m_kind)
    {
    case RttiInfo::Kind::Invalid:
        {
            return false;
        }
    case RttiInfo::Kind::String:
    case RttiInfo::Kind::RefPtr:
        {
            return true;
        }
    case RttiInfo::Kind::UnownedStringSlice:
    case RttiInfo::Kind::Ptr:
    case RttiInfo::Kind::Enum:
        {
            return false;
        }
    case RttiInfo::Kind::FixedArray:
        {
            const FixedArrayRttiInfo* fixedArrayRttiInfo =
                static_cast<const FixedArrayRttiInfo*>(type);
            return hasDtor(fixedArrayRttiInfo->m_elementType);
        }
    case RttiInfo::Kind::Other:
    case RttiInfo::Kind::List:
    case RttiInfo::Kind::Dictionary:
        {
            return true;
        }
    case RttiInfo::Kind::Struct:
        {
            const StructRttiInfo* structRttiInfo = static_cast<const StructRttiInfo*>(type);

            do
            {
                // If all the fields can be zero inited, struct can be
                const auto fieldCount = structRttiInfo->m_fieldCount;
                const auto fields = structRttiInfo->m_fields;

                for (Index i = 0; i < fieldCount; ++i)
                {
                    const auto& field = fields[i];
                    if (hasDtor(field.m_type))
                    {
                        return true;
                    }
                }
                structRttiInfo = structRttiInfo->m_super;
            } while (structRttiInfo);
            return false;
        }
    case RttiInfo::Kind::Optional:
        {
            const OptionalRttiInfo* optionalRttiInfo = static_cast<const OptionalRttiInfo*>(type);
            return hasDtor(optionalRttiInfo->m_elementType);
        }
    default:
        {
            return !type->isBuiltIn();
        }
    }
}

/* static */ void RttiUtil::ctorArray(
    RttiTypeFuncsMap* typeMap,
    const RttiInfo* rttiInfo,
    void* inDst,
    ptrdiff_t stride,
    Index count)
{
    if (count <= 0)
    {
        return;
    }

    Byte* dst = (Byte*)inDst;
    if (canZeroInit(rttiInfo))
    {
        if (stride == rttiInfo->m_size)
        {
            ::memset(dst, 0, count * stride);
        }
        else
        {
            const size_t size = rttiInfo->m_size;
            for (Index i = 0; i < count; ++i, dst += stride)
            {
                ::memset(dst, 0, size);
            }
        }
        return;
    }

    switch (rttiInfo->m_kind)
    {
    case RttiInfo::Kind::FixedArray:
        {
            const FixedArrayRttiInfo* fixedArrayRttiInfo =
                static_cast<const FixedArrayRttiInfo*>(rttiInfo);

            if (fixedArrayRttiInfo->m_size == stride)
            {
                // It's contiguous do in one go
                ctorArray(
                    typeMap,
                    fixedArrayRttiInfo->m_elementType,
                    dst,
                    fixedArrayRttiInfo->m_elementType->m_size,
                    fixedArrayRttiInfo->m_elementCount * count);
            }
            else
            {
                // Do it in array runs
                for (Index i = 0; i < count; ++i, dst += stride)
                {
                    ctorArray(
                        typeMap,
                        fixedArrayRttiInfo->m_elementType,
                        dst,
                        fixedArrayRttiInfo->m_elementType->m_size,
                        fixedArrayRttiInfo->m_elementCount);
                }
            }
            return;
        }
    case RttiInfo::Kind::List:
    case RttiInfo::Kind::Dictionary:
    case RttiInfo::Kind::Other:
        {
            auto funcs = typeMap->getFuncsForType(rttiInfo);
            SLANG_ASSERT(funcs.isValid());

            const OtherRttiInfo* otherRttiInfo = static_cast<const OtherRttiInfo*>(rttiInfo);
            if (otherRttiInfo->m_size == stride)
            {
                funcs.ctorArray(typeMap, rttiInfo, dst, count);
            }
            else
            {
                // Do it in array runs
                for (Index i = 0; i < count; ++i, dst += stride)
                {
                    funcs.ctorArray(typeMap, rttiInfo, dst, 1);
                }
            }
            return;
        }
    case RttiInfo::Kind::Struct:
        {
            const StructRttiInfo* structRttiInfo = static_cast<const StructRttiInfo*>(rttiInfo);

            do
            {
                // If all the fields can be zero inited, struct can be
                const auto fieldCount = structRttiInfo->m_fieldCount;
                const auto fields = structRttiInfo->m_fields;

                for (Index i = 0; i < fieldCount; ++i)
                {
                    const auto& field = fields[i];
                    ctorArray(typeMap, field.m_type, dst + field.m_offset, stride, count);
                }
                structRttiInfo = structRttiInfo->m_super;
            } while (structRttiInfo);

            return;
        }
    case RttiInfo::Kind::Optional:
        {
            const OptionalRttiInfo* optionalRttiInfo =
                static_cast<const OptionalRttiInfo*>(rttiInfo);
            ctorArray(typeMap, GetRttiInfo<bool>::get(), dst, stride, count);
            ctorArray(
                typeMap,
                optionalRttiInfo->m_elementType,
                dst + optionalRttiInfo->m_valueOffset,
                stride,
                count);
            return;
        }
    }

    SLANG_ASSERT(!"Unexpected");
}

/* static */ void RttiUtil::copyArray(
    RttiTypeFuncsMap* typeMap,
    const RttiInfo* rttiInfo,
    void* inDst,
    const void* inSrc,
    ptrdiff_t stride,
    Index count)
{
    if (count <= 0)
    {
        return;
    }

    const size_t size = rttiInfo->m_size;

    Byte* dst = (Byte*)inDst;
    const Byte* src = (const Byte*)inSrc;
    if (canMemCpy(rttiInfo))
    {
        if (stride == ptrdiff_t(size))
        {
            ::memcpy(dst, src, count * stride);
        }
        else
        {

            for (Index i = 0; i < count; ++i, dst += stride, src += stride)
            {
                ::memcpy(dst, src, size);
            }
        }
        return;
    }

    switch (rttiInfo->m_kind)
    {
    case RttiInfo::Kind::FixedArray:
        {
            const FixedArrayRttiInfo* fixedArrayRttiInfo =
                static_cast<const FixedArrayRttiInfo*>(rttiInfo);
            const auto elementType = fixedArrayRttiInfo->m_elementType;
            const auto elementSize = elementType->m_size;
            const auto elementCount = fixedArrayRttiInfo->m_elementCount;

            if (ptrdiff_t(size) == stride)
            {
                // It's contiguous do in one go
                copyArray(typeMap, elementType, dst, src, elementSize, elementCount * count);
            }
            else
            {
                // Do it in array runs
                for (Index i = 0; i < count; ++i, dst += stride, src += stride)
                {
                    copyArray(typeMap, elementType, dst, src, elementSize, elementCount);
                }
            }
            return;
        }
    case RttiInfo::Kind::List:
    case RttiInfo::Kind::Dictionary:
    case RttiInfo::Kind::Other:
        {
            auto funcs = typeMap->getFuncsForType(rttiInfo);
            SLANG_ASSERT(funcs.isValid());

            const OtherRttiInfo* otherRttiInfo = static_cast<const OtherRttiInfo*>(rttiInfo);
            if (otherRttiInfo->m_size == stride)
            {
                funcs.copyArray(typeMap, rttiInfo, dst, src, count);
            }
            else
            {
                for (Index i = 0; i < count; ++i, dst += stride, src += stride)
                {
                    funcs.copyArray(typeMap, rttiInfo, dst, src, 1);
                }
            }
            return;
        }
    case RttiInfo::Kind::Struct:
        {
            const StructRttiInfo* structRttiInfo = static_cast<const StructRttiInfo*>(rttiInfo);

            do
            {
                // If all the fields can be zero inited, struct can be
                const auto fieldCount = structRttiInfo->m_fieldCount;
                const auto fields = structRttiInfo->m_fields;

                for (Index i = 0; i < fieldCount; ++i)
                {
                    const auto& field = fields[i];
                    copyArray(
                        typeMap,
                        field.m_type,
                        dst + field.m_offset,
                        src + field.m_offset,
                        stride,
                        count);
                }
                structRttiInfo = structRttiInfo->m_super;
            } while (structRttiInfo);

            return;
        }
    case RttiInfo::Kind::Optional:
        {
            const OptionalRttiInfo* optionalRttiInfo =
                static_cast<const OptionalRttiInfo*>(rttiInfo);
            copyArray(typeMap, GetRttiInfo<bool>::get(), dst, src, stride, count);
            copyArray(
                typeMap,
                optionalRttiInfo->m_elementType,
                dst + optionalRttiInfo->m_valueOffset,
                src + optionalRttiInfo->m_valueOffset,
                stride,
                count);
            return;
        }
    }

    SLANG_ASSERT(!"Unexpected");
}

/* static */ void RttiUtil::dtorArray(
    RttiTypeFuncsMap* typeMap,
    const RttiInfo* rttiInfo,
    void* inDst,
    ptrdiff_t stride,
    Index count)
{
    if (count <= 0 || !hasDtor(rttiInfo))
    {
        return;
    }

    const size_t size = rttiInfo->m_size;
    Byte* dst = (Byte*)inDst;

    switch (rttiInfo->m_kind)
    {
    case RttiInfo::Kind::FixedArray:
        {
            const FixedArrayRttiInfo* fixedArrayRttiInfo =
                static_cast<const FixedArrayRttiInfo*>(rttiInfo);
            const auto elementType = fixedArrayRttiInfo->m_elementType;
            const auto elementSize = elementType->m_size;
            const auto elementCount = fixedArrayRttiInfo->m_elementCount;

            if (ptrdiff_t(size) == stride)
            {
                // It's contiguous do in one go
                dtorArray(typeMap, elementType, dst, elementSize, elementCount * count);
            }
            else
            {
                // Do it in array runs
                for (Index i = 0; i < count; ++i, dst += stride)
                {
                    dtorArray(typeMap, elementType, dst, elementSize, elementCount);
                }
            }
            return;
        }
    case RttiInfo::Kind::List:
    case RttiInfo::Kind::Dictionary:
    case RttiInfo::Kind::Other:
        {
            auto funcs = typeMap->getFuncsForType(rttiInfo);
            SLANG_ASSERT(funcs.isValid());

            const OtherRttiInfo* otherRttiInfo = static_cast<const OtherRttiInfo*>(rttiInfo);
            if (otherRttiInfo->m_size == stride)
            {
                funcs.dtorArray(typeMap, rttiInfo, dst, count);
            }
            else
            {
                for (Index i = 0; i < count; ++i, dst += stride)
                {
                    funcs.dtorArray(typeMap, rttiInfo, dst, 1);
                }
            }
            return;
        }
    case RttiInfo::Kind::Struct:
        {
            const StructRttiInfo* structRttiInfo = static_cast<const StructRttiInfo*>(rttiInfo);

            do
            {
                // If all the fields can be zero inited, struct can be
                const auto fieldCount = structRttiInfo->m_fieldCount;
                const auto fields = structRttiInfo->m_fields;

                for (Index i = 0; i < fieldCount; ++i)
                {
                    const auto& field = fields[i];
                    dtorArray(typeMap, field.m_type, dst + field.m_offset, stride, count);
                }
                structRttiInfo = structRttiInfo->m_super;
            } while (structRttiInfo);

            return;
        }
    case RttiInfo::Kind::Optional:
        {
            const OptionalRttiInfo* optionalRttiInfo =
                static_cast<const OptionalRttiInfo*>(rttiInfo);
            dtorArray(typeMap, GetRttiInfo<bool>::get(), dst, stride, count);
            dtorArray(
                typeMap,
                optionalRttiInfo->m_elementType,
                dst + optionalRttiInfo->m_valueOffset,
                stride,
                count);
            return;
        }
    }

    SLANG_ASSERT(!"Unexpected");
}

} // namespace Slang
