#pragma once

#include <client.h>

#include <cstddef>
#include <iostream>
#include <string>
#include <vector>

template<typename ReturnType, typename... Args>
using FunctionPointer = ReturnType(*)(Args...);

template<const size_t kSize>
class MinimumSize {
    unsigned char _padding[kSize];
};

#define STR_MERGE_IMPL(a, b) a##b
#define STR_MERGE(a, b) STR_MERGE_IMPL(a, b)
#define MAKE_PAD(size) STR_MERGE(_pad, __COUNTER__)[size]
#define DEFINE_MEMBER(offset, type, name) \
    struct {                              \
        unsigned char MAKE_PAD(offset);   \
        type name;                        \
    }

namespace flyff {

    class Memory {
        wasm_rt_memory_t **memory;

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

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

        uintptr_t to_inside(const uintptr_t offset) const {
            return offset != NULL ? offset - start() : NULL;
        }

        uintptr_t to_inside(const void *offset) const {
            return to_inside(reinterpret_cast<uintptr_t>(offset));
        }

        uintptr_t to_outside(const uintptr_t offset) {
            return offset != NULL ? reinterpret_cast<uintptr_t>(&(*memory)->data[offset]) : NULL;
        }

        template<typename T>
        T *to_outside(const T *offset) {
            return reinterpret_cast<T *>(to_outside(reinterpret_cast<uintptr_t>(offset)));
        }
    };

    static auto kMemory = Memory(&Z_clientZ_memory);

    class Table {
    };

    static const auto &kTable = &Z_clientZ_table;


    template<typename T>
    T &make_ref(uintptr_t address) {
        return *reinterpret_cast<T *>(kMemory.to_outside(address));
    }

    template<typename T>
    class __attribute__((packed)) Pointer;

    template<typename T>
    class __attribute__((packed)) Pointer {
        // Always located INSIDE the flyff module.
        uintptr_t offset;

        T *as_outside() const {
            return reinterpret_cast<T *>(kMemory.to_outside(offset));
        }

        Pointer() : offset(0) {}

    public:
        explicit Pointer(void const *ptr) : offset(
                ptr == nullptr ? 0 : kMemory.to_inside(reinterpret_cast<uintptr_t>(ptr))) {}

        explicit Pointer(uintptr_t ptr) : offset(ptr) {}

        Pointer(const Pointer<T> &other) : offset(other.offset) {}

        Pointer<T> &operator=(Pointer<T> other) {
            offset = other.offset;
            return *this;
        }

        T *operator->() const {
            return as_outside();
        }

        explicit operator T *() const {
            return as_outside();
        }

        explicit operator u32() const {
            return offset;
        }

        explicit operator bool() const {
            return offset != 0 && as_outside() != nullptr;
        }

        bool operator==(Pointer<T> const &rhs) const { return offset == rhs.offset; }

        friend std::ostream &operator<<(std::ostream &os, const Pointer<T> &ptr) {
            os << reinterpret_cast<void *>(ptr.offset);
            return os;
        }
    };

    static_assert(sizeof(Pointer<void>) == sizeof(u32), "Pointers needs to be the size of u32");

// Allocates an unmanaged pointer inside the flyff module.
// template <class T, class... Args>
// Pointer<T> make_pointer(Args&&... args) {
//     return Pointer<T>(new T(std::forward<Args>(args)...));
// }

    class __attribute__((packed)) String {
        union {
            DEFINE_MEMBER(0x0, char*, text);
            DEFINE_MEMBER(0x4, size_t, length);
            DEFINE_MEMBER(0x8, void*, capacity);

            DEFINE_MEMBER(0x0, char, data[1]);
            DEFINE_MEMBER(0xb, char, short_length);

            const MinimumSize<12> _size;
        };

    public:
        // This has to be const until strings can allocate...
        operator std::string() const {
            auto copy = *this;

            auto is_short = -1 < copy.short_length;
            if (!is_short) {
                copy.text = kMemory.to_outside(text);
                // TODO: Probably offset capacity too.
            }

            return *reinterpret_cast<std::string *>(&copy);
        }

        friend std::ostream &operator<<(std::ostream &os, const String &str) {
            const std::string conv = str;
            os << conv;
            return os;
        }
    };

    using MoverID = uint64_t;
    using Timestamp = uint64_t;

    static_assert(sizeof(String) == sizeof(std::string),
                  "String needs to be the same size as std::string");

    template<typename T>
    class __attribute__((packed)) Vector {
        // const Pointer<T>& begin_;
        // const Pointer<T>& end_;
        // const Pointer<T>& capacity_;
        Pointer<T> begin_;
        Pointer<T> end_;
        Pointer<T> capacity_;

        Vector() : begin_(nullptr), end_(nullptr), capacity_(nullptr) {}

    public:
        typedef T *iterator;
        typedef const T *const_iterator;

        // This has to be const until strings can allocate...
        T *begin() const {
            return static_cast<T *>(begin_);
        }

        T *end() const {
            return static_cast<T *>(end_);
        }

        size_t size() const {
            return static_cast<size_t>(end_ - begin_);
        }
    };

    static_assert(sizeof(Vector<char>) == sizeof(std::vector<char>),
                  "Vector needs to be the same size as std::vector");


    class HashMap {

    };

};  // namespace flyff