#include "Neuz.h"

#include <client.h>

// static_assert(sizeof(TestMover) == 4096, "Mover needs to be 4096 bytes");
// static_assert(offsetof(TestMover, name) == 0x7f4, "Mover.name needs to be at
// 2020");

namespace flyff {
    namespace raw {
        static constexpr uintptr_t WORLD = 0x00035eb8;
        static constexpr uintptr_t PLAYER = WORLD + 4;

        static constexpr uintptr_t PLAYER_OBJECT_ID = 0x00034ef8;
// The messages that are limited to 5 that are show temporarily.
        static constexpr uintptr_t MESSAGES_UI = 0x00035ec8;
        static constexpr uintptr_t ANNOUNCEMENTS_UI = 0x00034c94;
        static constexpr uintptr_t MOVER_MAP = 0x000332c8;

        static constexpr uintptr_t CURRENT_TIME = 0x00032880;
        static constexpr uintptr_t START_TIME = 0x000328c0;
        static constexpr uintptr_t MAYBE_LOGIN_TIME = START_TIME + 8;

        extern "C" {
// get_mover
        extern u32 w2c_f208(u64);
// get_text
        extern void w2c_f167(u32, u32);
// show_message
        extern void w2c_f341(u32, u32, u32);
// show_announcement
        extern void w2c_f607(u32, u32, u32);
// can_attack_target
        extern u32 w2c_f515(u32, u32);
// CProject::GetSkillProp()
        extern u32 w2c_f440(u32, u32);
        };

        static const auto &get_mover = w2c_f208;
        static const auto &get_text = w2c_f167;
        static const auto &show_message = w2c_f341;
        static const auto &show_announcement = w2c_f607;
        static const auto &can_attack_target = w2c_f515;
        static const auto &get_skill_prop = w2c_f440;
    };  // namespace raw

// Emscripten runtime:
// 1. Pre-run
// 2. Init runtime
// 3. Pre-main
// 4. Call main
// 5. Post-run

    Neuz::Neuz()
            : movers(make_ref<MoverMap>(raw::MOVER_MAP)),
              player(make_ref<Pointer<Mover>>(raw::PLAYER)),
              messages_ui(make_ref<Pointer<uintptr_t>>(raw::MESSAGES_UI)),
              announcements_ui(make_ref<Pointer<uintptr_t>>(raw::ANNOUNCEMENTS_UI)),
              player_object_id(make_ref<MoverID>(raw::PLAYER_OBJECT_ID)),
              current_time(make_ref<uint64_t>(raw::CURRENT_TIME)),
              start_time(make_ref<uint64_t>(raw::START_TIME)),
              maybe_login_time(make_ref<uint64_t>(raw::MAYBE_LOGIN_TIME)) {}

    Neuz &Neuz::instance() {
        static bool initialised = false;
        if (!initialised) {
            // Initialise the WebAssembly runtime.
            wasm_rt_init();
            // Initialise the client.
            flyff::raw::init();
            // 1. Pre-run: There are no pre-runs in this binary.
            // 2. Init runtime: `___wasm_call_ctors` is added to on init runtime.
            (*flyff::raw::_wasm_call_ctors)();
            // Only init the module once.
            initialised = true;
        }
        // Constructor will be called.
        static Neuz instance;
        // Whole module is initialised now.
        return instance;
    }

    int Neuz::main() const {
        // 3. Pre-main: There are no pre-mains in this binary.
        // 4. Call main: `_main` is main in the binary.
        // TODO: Fix arguments.
        return (*flyff::raw::main)(0, 0);
    }

    Pointer<Mover> Neuz::get_mover(flyff::MoverID id) const {
        const auto &node = Pointer<MoverMap::Node>(raw::get_mover(id));

        return node ? node->mover : Pointer<Mover>(nullptr);
    }

    flyff::String Neuz::get_text(const std::string &id) const {
        auto result = std::make_unique<fugg::detail::RawBasicString>();
        auto id_ = std::make_unique<fugg::String>(id);

        raw::get_text(
                fugg::RuntimePointer<fugg::detail::RawBasicString>(result.get())
                        .as_raw(),
                fugg::RuntimePointer<fugg::String>(id_.get()).as_raw());

        return *reinterpret_cast<flyff::String *>(result.get());
    }

    void Neuz::show_message(const std::string &message,
                            const flyff::Color &color) const {
        if (!messages_ui) return;

        // Copy the message into a new auto ptr
        auto message_ = std::make_unique<fugg::String>(message);
        auto color_ = std::make_unique<flyff::Color>(color);

        raw::show_message(
                static_cast<u32>(messages_ui),
                fugg::RuntimePointer<fugg::String>(message_.get()).as_raw(),
                fugg::RuntimePointer<flyff::Color>(color_.get()).as_raw());
    }

    void Neuz::show_announcement(const std::string &message,
                                 const flyff::Color &color) const {
        if (!announcements_ui) return;

        // Copy the message into a new auto ptr
        auto message_ = std::make_unique<fugg::String>(message);
        auto color_ = std::make_unique<flyff::Color>(color);

        raw::show_announcement(
                static_cast<u32>(announcements_ui),
                fugg::RuntimePointer<fugg::String>(message_.get()).as_raw(),
                fugg::RuntimePointer<flyff::Color>(color_.get()).as_raw());
    }

    bool Neuz::is_valid_attack(const Pointer<Mover> &attacker,
                               const Pointer<Mover> &defender) const {
        auto result = raw::can_attack_target(static_cast<u32>(attacker),
                                             static_cast<u32>(defender));

        return result != 0;
    }


    Pointer<SkillProperty> Neuz::get_skill_property(const SkillID &id) {
        return Pointer<SkillProperty>(raw::get_skill_prop(static_cast<uint32_t>(id), 1));
    }
}  // namespace flyff