#pragma once

#include <client.h>
#include <wasm-rt.h>

#include "pointer.h"

namespace fugg {
    namespace wasm {
        using pointer::BasePointer;

        class Memory {
            wasm_rt_memory_t** memory;

        public:
            constexpr explicit Memory(wasm_rt_memory_t** memory) : memory(memory) { }

            uintptr_t address_of(uintptr_t offset) const {
                if(offset != NULL) 
                    return reinterpret_cast<uintptr_t>(&(*memory)->data[offset]);
                else
                    return NULL;       
            }

            uintptr_t data() const {
                return reinterpret_cast<uintptr_t>((*memory)->data);
            }

            uintptr_t size() const {
                return (*memory)->size;
            }
        };

        static constexpr auto kRawMemory = &Z_clientZ_memory;
        static constexpr auto kMemory = Memory { kRawMemory };
    }

    template<typename T>
    T& module_ref(uintptr_t address) {
        return *reinterpret_cast<T*>(wasm::kMemory.address_of(address));
    }
    
    // A pointer that relative to the module's memory.
    template<typename T>
    using ModulePointer = pointer::OffsetPointer<T, uintptr_t, &Z_clientZ_memory>; 
    // A pointer allocated INSIDE the module's memory, but passed as global.
    template<typename T>
    using RuntimePointer = pointer::RelativePointer<T, uintptr_t, &Z_clientZ_memory>; 
    // A pointer that is globally accessible in the linear memory.
    template<typename T>
    using Pointer = pointer::Pointer<T, uintptr_t>;

    static_assert(
        sizeof(ModulePointer<unsigned char>) == sizeof(void*), 
        "ModulePointer needs a size of sizeof(void*)"
    );

    static_assert(
        sizeof(RuntimePointer<unsigned char>) == sizeof(unsigned char*), 
        "RuntimePointer needs a size of sizeof(unsigned char*)"
    );

    // Modifies the array to let the pointers be accessible from outside the WASM runtime
    template<typename T>
    static T* ptr_array(uintptr_t address, size_t length) {
        auto src = reinterpret_cast<uintptr_t*>(
            wasm::kMemory.address_of(address)
        );

        for(unsigned int i = 0; i < length; i++) {            
            src[i] = wasm::kMemory.address_of(src[i]);
        }

        return reinterpret_cast<T*>(src);
    }
}
