#include "il2cpp-config.h"
#include "gc/GCHandle.h"
#include "il2cpp-object-internals.h"
#include "GarbageCollector.h"
#include "os/Mutex.h"
#include "utils/Memory.h"
#include <memory>

namespace il2cpp
{
namespace gc
{
#if IL2CPP_SIZEOF_VOID_P == 4
#define HANDLE_COUNT 992
#define HANDLE_DATA_ALIGNMENT 4096
#else
#define HANDLE_COUNT 992
#define HANDLE_DATA_ALIGNMENT 8192
#endif

    typedef struct HandleData HandleData;
    struct HandleData
    {
        HandleData *next; //immutable
        HandleData *next_free; // next free
        uint32_t   *bitmap;
        uint32_t   in_use;
        uint32_t   size;
        uint8_t    type;
        uint32_t   slot_hint : 24; /* starting slot for search in bitmap */
        void*      entries[HANDLE_COUNT];
    };


    static HandleData* gc_handles[HANDLE_PINNED + 1];
    static HandleData* gc_handles_free[HANDLE_PINNED + 1];

    inline bool HandleTypeIsWeak(GCHandleType type)
    {
        return type == GCHandleType::HANDLE_WEAK || type == GCHandleType::HANDLE_WEAK_TRACK;
    }

#define BITMAP_SIZE (sizeof (*((HandleData *)NULL)->bitmap) * CHAR_BIT)

    static bool
    slot_occupied(HandleData* handles, uint32_t slot)
    {
        return handles->bitmap[slot / BITMAP_SIZE] & (1 << (slot % BITMAP_SIZE));
    }

    static void
    vacate_slot(HandleData* handles, uint32_t slot)
    {
        handles->in_use--;
        handles->bitmap[slot / BITMAP_SIZE] &= ~(1 << (slot % BITMAP_SIZE));

        if (handles->in_use == (handles->size - 1))
        {
            uint8_t type = handles->type;
            HandleData* first = gc_handles_free[type];
            handles->next_free = first;
            gc_handles_free[type] = handles;
        }
    }

    static void
    occupy_slot(HandleData* handles, uint32_t slot)
    {
        handles->in_use++;
        handles->bitmap[slot / BITMAP_SIZE] |= 1 << (slot % BITMAP_SIZE);

        if (handles->in_use == handles->size)
        {
            uint8_t type = handles->type;
            IL2CPP_ASSERT(handles == gc_handles_free[type]);
            gc_handles_free[type] = gc_handles_free[type]->next_free;
        }
    }

    static int
    find_first_unset(uint32_t bitmap)
    {
        int i;
        for (i = 0; i < 32; ++i)
        {
            if (!(bitmap & (1 << i)))
                return i;
        }
        return -1;
    }

    static HandleData*
    handle_data_alloc_entries(int type)
    {
        IL2CPP_ASSERT(sizeof(HandleData) < HANDLE_DATA_ALIGNMENT);
        IL2CPP_ASSERT(HANDLE_COUNT % BITMAP_SIZE == 0);
        HandleData* handles = (HandleData*)utils::Memory::AlignedMalloc(sizeof(HandleData), HANDLE_DATA_ALIGNMENT);
        memset(handles, 0, sizeof(HandleData));
        handles->type = type;
        handles->size = HANDLE_COUNT;
        if (!HandleTypeIsWeak((GCHandleType)handles->type))
        {
            GarbageCollector::RegisterRoot((char*)&handles->entries[0], HANDLE_COUNT * sizeof(void*));
        }
        handles->bitmap = (uint32_t*)utils::Memory::Calloc(sizeof(char), handles->size / CHAR_BIT);

        return handles;
    }

    static int32_t
    handle_data_next_unset(HandleData* handles)
    {
        uint32_t slot;
        for (slot = handles->slot_hint; slot < handles->size / BITMAP_SIZE; ++slot)
        {
            if (handles->bitmap[slot] == 0xffffffff)
                continue;
            handles->slot_hint = slot;
            return find_first_unset(handles->bitmap[slot]);
        }
        return -1;
    }

    static int32_t
    handle_data_first_unset(HandleData* handles)
    {
        uint32_t slot;
        for (slot = 0; slot < handles->slot_hint; ++slot)
        {
            if (handles->bitmap[slot] == 0xffffffff)
                continue;
            handles->slot_hint = slot;
            return find_first_unset(handles->bitmap[slot]);
        }
        return -1;
    }

    static int32_t
    handle_data_find_slot(HandleData* handles)
    {
        int32_t slot = 0;
        int32_t i = handle_data_next_unset(handles);
        if (i == -1 && handles->slot_hint != 0)
            i = handle_data_first_unset(handles);

        IL2CPP_ASSERT(i != -1);

        slot = handles->slot_hint * BITMAP_SIZE + i;
        return slot;
    }

    static Il2CppGCHandle
    handle_tag_weak(Il2CppGCHandle handle)
    {
        return (Il2CppGCHandle)((uintptr_t)handle | (uintptr_t)1);
    }

    static Il2CppGCHandle
    handle_untag_weak(Il2CppGCHandle handle)
    {
        return (Il2CppGCHandle)((uintptr_t)handle & ~(uintptr_t)1);
    }

    static uintptr_t AlignDownTo(uintptr_t size, uintptr_t align)
    {
        return size & ~(align - 1);
    }

    static HandleData*
    get_handle_data_from_handle(Il2CppGCHandle handle)
    {
        HandleData* handles = (HandleData*)AlignDownTo((uintptr_t)handle, HANDLE_DATA_ALIGNMENT);
        return handles;
    }

    static HandleData*
    handle_lookup(Il2CppGCHandle handle, uint32_t* slot)
    {
        HandleData* handles = get_handle_data_from_handle(handle);
        if (slot)
            *slot = (uint32_t)(ptrdiff_t)((void**)handle_untag_weak(handle) - &handles->entries[0]);
        return handles;
    }

    static baselib::ReentrantLock g_HandlesMutex;

#define lock_handles(handles) g_HandlesMutex.Acquire ()
#define unlock_handles(handles) g_HandlesMutex.Release ()

    static Il2CppGCHandle
    alloc_handle(GCHandleType type, Il2CppObject *obj, bool track)
    {
        int32_t slot = 0;
        Il2CppGCHandle res = 0;
        HandleData* handles = gc_handles[type];
        lock_handles(handles);
        handles = gc_handles_free[type];
        if (!handles)
        {
            handles = handle_data_alloc_entries(type);
            handles->next = gc_handles[type];
            gc_handles[type] = handles;

            handles->next_free = gc_handles_free[type];
            gc_handles_free[type] = handles;
        }
        slot = handle_data_find_slot(handles);
        occupy_slot(handles, slot);

        handles->entries[slot] = NULL;
        if (handles->type <= HANDLE_WEAK_TRACK)
        {
            if (obj)
                GarbageCollector::AddWeakLink(&(handles->entries[slot]), obj, track);
        }
        else
        {
            handles->entries[slot] = obj;
            GarbageCollector::SetWriteBarrier(handles->entries + slot);
        }

        //mono_perfcounters->gc_num_handles++;
        unlock_handles(handles);

        res = (Il2CppGCHandle) & handles->entries[slot];
        if (HandleTypeIsWeak((GCHandleType)handles->type))
        {
            /*
             * Use lowest bit as an optimization to indicate weak GC handle.
             * This allows client code to simply dereference strong GCHandle
             * when the bit is not set.
            */
            res = handle_tag_weak(res);
        }
        return res;
    }

    Il2CppGCHandle GCHandle::New(Il2CppObject *obj, bool pinned)
    {
        return alloc_handle(pinned ? HANDLE_PINNED : HANDLE_NORMAL, obj, false);
    }

    utils::Expected<Il2CppGCHandle> GCHandle::NewWeakref(Il2CppObject *obj, bool track_resurrection)
    {
        Il2CppGCHandle handle = alloc_handle(track_resurrection ? HANDLE_WEAK_TRACK : HANDLE_WEAK, obj, track_resurrection);

#ifndef HAVE_SGEN_GC
        if (track_resurrection)
            return utils::Il2CppError(utils::NotSupported, "IL2CPP does not support resurrection for weak references. Pass the trackResurrection with a value of false.");
#endif

        return (Il2CppGCHandle)handle;
    }

    GCHandleType GCHandle::GetHandleType(Il2CppGCHandle gchandle)
    {
        HandleData* handles = handle_lookup(gchandle, NULL);
        return (GCHandleType)handles->type;
    }

    Il2CppObject* GCHandle::GetTarget(Il2CppGCHandle gchandle)
    {
        uint32_t slot = 0;
        HandleData* handles = handle_lookup(gchandle, &slot);
        Il2CppObject *obj = NULL;

        if (handles->type >= HANDLE_TYPE_MAX)
            return NULL;

        lock_handles(handles);
        if (slot < handles->size && slot_occupied(handles, slot))
        {
            if (handles->type <= HANDLE_WEAK_TRACK)
            {
                obj = GarbageCollector::GetWeakLink(&handles->entries[slot]);
            }
            else
            {
                obj = (Il2CppObject*)handles->entries[slot];
            }
        }
        else
        {
            /* print a warning? */
        }
        unlock_handles(handles);
        /*g_print ("get target of entry %d of type %d: %p\n", slot, handles->type, obj);*/
        return obj;
    }

    static void
    il2cpp_gchandle_set_target(Il2CppGCHandle gchandle, Il2CppObject *obj)
    {
        uint32_t slot = 0;
        HandleData* handles = handle_lookup(gchandle, &slot);
        Il2CppObject *old_obj = NULL;


        IL2CPP_ASSERT(handles->type < HANDLE_TYPE_MAX);
        lock_handles(handles);
        if (slot < handles->size && slot_occupied(handles, slot))
        {
            if (handles->type <= HANDLE_WEAK_TRACK)
            {
                old_obj = (Il2CppObject*)handles->entries[slot];
                if (handles->entries[slot])
                    GarbageCollector::RemoveWeakLink(&handles->entries[slot]);
                if (obj)
                    GarbageCollector::AddWeakLink(&handles->entries[slot], obj, handles->type == HANDLE_WEAK_TRACK);
            }
            else
            {
                handles->entries[slot] = obj;
            }
        }
        else
        {
            /* print a warning? */
        }
        unlock_handles(handles);
    }

    void GCHandle::Free(Il2CppGCHandle gchandle)
    {
        if (!gchandle)
            return;

        uint32_t slot = 0;
        HandleData* handles = handle_lookup(gchandle, &slot);
        if (handles->type >= HANDLE_TYPE_MAX)
            return;

        lock_handles(handles);
        if (slot < handles->size && slot_occupied(handles, slot))
        {
            if (HandleTypeIsWeak((GCHandleType)handles->type))
            {
                if (handles->entries[slot])
                    GarbageCollector::RemoveWeakLink(&handles->entries[slot] /*, handles->type == HANDLE_WEAK_TRACK*/);
            }
            else
            {
                handles->entries[slot] = NULL;
            }
            vacate_slot(handles, slot);
        }
        else
        {
            /* print a warning? */
        }

        unlock_handles(handles);
    }

    utils::Expected<Il2CppGCHandle> GCHandle::GetTargetHandle(Il2CppObject * obj, Il2CppGCHandle handle, int32_t type)
    {
        if (type == -1)
        {
            il2cpp_gchandle_set_target(handle, obj);
            /* the handle doesn't change */
            return handle;
        }
        switch (type)
        {
            case HANDLE_WEAK:
                return NewWeakref(obj, false);
            case HANDLE_WEAK_TRACK:
                return NewWeakref(obj, true);
            case HANDLE_NORMAL:
                return New(obj, false);
            case HANDLE_PINNED:
                return New(obj, true);
            default:
                IL2CPP_ASSERT(0);
        }
        return 0;
    }

    void GCHandle::WalkStrongGCHandleTargets(WalkGCHandleTargetsCallback callback, void* context)
    {
        lock_handles(handles);
        const GCHandleType types[] = { HANDLE_NORMAL, HANDLE_PINNED };

        for (int gcHandleTypeIndex = 0; gcHandleTypeIndex < 2; gcHandleTypeIndex++)
        {
            const HandleData* handles = gc_handles[types[gcHandleTypeIndex]];

            while (handles != NULL)
            {
                for (uint32_t i = 0; i < handles->size; i++)
                {
                    if (handles->entries[i] != NULL)
                        callback(static_cast<Il2CppObject*>(handles->entries[i]), context);
                }

                handles = handles->next;
            }
        }
        unlock_handles(handles);
    }

    void GCHandle::AcquireMetadataLocks()
    {
        lock_handles(handles);
    }

    void GCHandle::ReleaseMetadataLocks()
    {
        unlock_handles(handles);
    }
} /* gc */
} /* il2cpp */
