#pragma once

#include "fugg.h"

#include <cstddef>
#include <iterator>
#include <bitset>
#include <cmath>

#include "../../core.h"
#include "../../item/ItemProperty.h"
#include "../../math.h"

#include "../Object.h"
#include "../ObjectProperty.h"
#include "../ObjectType.h"

#include "../../skill/SkillID.h"

#include "../../item/InventoryItem.h"
#include "../../item/Part.h"
#include "JobProperty.h"
#include "ParamID.h"
#include "MoverProperty.h"

namespace flyff {
    namespace raw {
        extern "C" {
        // w2c_f410: CMover::GetHitPoint(PlayerObject* mover);
        extern u32 w2c_f411(u32);
        // w2c_f2454: CCooltimeMgr::CanUse(uint64_t* timers, ItemProperty* prop);
        extern u32 w2c_f2466(u32, u32);
        }
    };

    struct __attribute__((packed)) Mover {
        static constexpr uint32_t MAX_PARAM = 0x52;

        using Parameter = unsigned long;

        using ObjectStateFlags = Object::StateFlags;
        using ObjectState = Object::State;

        struct __attribute__((packed)) Vtable {
            union {
                DEFINE_MEMBER(52, const u32, get_level);
                DEFINE_MEMBER(56, const u32, get_gender);

                DEFINE_MEMBER(96, const u32, get_active_hand_item_prop);

                DEFINE_MEMBER(144, const u32, get_max_origin_hp);
                DEFINE_MEMBER(148, const u32, get_max_origin_mp);

                const MinimumSize<220> _size;
            };
        };

        struct __attribute__((packed)) Skill {
            union {
                DEFINE_MEMBER(0, const flyff::SkillID, id);
                DEFINE_MEMBER(4, const uintptr_t, level);
            };
        };

        struct __attribute__((packed)) Buff {
            enum class Type {
                Item = 0,
                Skill = 1,
            };

            struct __attribute__((packed)) Vtable {
                union {
                    DEFINE_MEMBER(28, const u32, get_type);
                    DEFINE_MEMBER(32, const u32, get_id);
                };
            };

            union {
                DEFINE_MEMBER(0, const Pointer<const Vtable>, vtable);

                DEFINE_MEMBER(4, const Pointer<const ObjectProperty>, property);
                DEFINE_MEMBER(8, const Parameter, param_id);

                DEFINE_MEMBER(16, const uint64_t, time_instantiated);
                DEFINE_MEMBER(24, const uint64_t, time_total);

                DEFINE_MEMBER(32, const int, level);

                DEFINE_MEMBER(36, const bool, is_removed);
                DEFINE_MEMBER(37, const bool, is_sfx);
            };

            Type get_type() const {
                using Fn = FunctionPointer<Type, u32>;
                const auto &fn = fugg::function_ref<Fn>(vtable->get_type);

                return fn(kMemory.to_inside(this));
            }

            uint32_t get_id() const {
                using Fn = FunctionPointer<u32, u32>;
                const auto &fn = fugg::function_ref<Fn>(vtable->get_id);

                return fn(kMemory.to_inside(this));
            }

            uint64_t get_time_remaining() const;
        };

        union {
            DEFINE_MEMBER(0x0, const Pointer<const Vtable>, vtable);

            DEFINE_MEMBER(0x18, Matrix4, world_matrix);

            DEFINE_MEMBER(152, const Vector3, position);
            // In degrees!
            DEFINE_MEMBER(164, const Vector3, rotation);

            DEFINE_MEMBER(188, const Pointer<const ObjectProperty>, property);
            DEFINE_MEMBER(192, uintptr_t, world);

            DEFINE_MEMBER(204, const ObjectType, type);

            DEFINE_MEMBER(212, const int, property_id);
            DEFINE_MEMBER(216, const bool, is_despawned);

            DEFINE_MEMBER(352, const MoverID, object_id);

            DEFINE_MEMBER(860, Vector<InventoryItem>, inventory);
            // If the index of the InventoryItem is below this, it's in the inventory. Otherwise it's equipped.
            DEFINE_MEMBER(872, const uint32_t, max_inventory_slots);
            DEFINE_MEMBER(880, const uint32_t, locked_inventory_slots);

            DEFINE_MEMBER(904, uint32_t, if_not_null_needsattackitem);

            DEFINE_MEMBER(920, Parameter, adjusted_parameters[MAX_PARAM]);
            DEFINE_MEMBER(1248, Parameter, changed_parameters[MAX_PARAM]);

            DEFINE_MEMBER(1576, Vector<Pointer<Buff>>, buffs);

            DEFINE_MEMBER(1616, const Vector<flyff::MoverID>, enemies);
            DEFINE_MEMBER(1628, Vector3, move_target);

            DEFINE_MEMBER(1652, Vector3, delta_velocity);

            DEFINE_MEMBER(1688, unsigned long long, last_combat);

            DEFINE_MEMBER(1728, MoverID, move_toward_target);
            DEFINE_MEMBER(1736, MoverID, last_hit_mover);
            DEFINE_MEMBER(1744, MoverID, selected_target);
            // The id of the target that it hit last.
            DEFINE_MEMBER(1752, MoverID, attack_target);
            DEFINE_MEMBER(1760, MoverID, follow_target);
            // The id, only non-zero when the attack animation plays, of the target it is attacking.
            DEFINE_MEMBER(1768, MoverID, current_attack_target);
            DEFINE_MEMBER(1776, MoverID, platform_standing_on);

            DEFINE_MEMBER(1792, int, current_animation);

            DEFINE_MEMBER(1816, int, adj_param);
            DEFINE_MEMBER(1820, uint32_t, server_tick);

            DEFINE_MEMBER(1840, int, hitpoints);

            DEFINE_MEMBER(1848, int, is_grounded);
            DEFINE_MEMBER(1888, const float, speed_factor);

            DEFINE_MEMBER(1804, uint32_t, object_state);
            DEFINE_MEMBER(1808, uint32_t, object_state_flags);

            DEFINE_MEMBER(2020, const String, name);

            DEFINE_MEMBER(2432, const flyff::Pointer<const JobProperty>, job_property);

            DEFINE_MEMBER(3224, const Timestamp, last_food_time);
            DEFINE_MEMBER(3248, const Timestamp, last_refresher_time);
            DEFINE_MEMBER(3256, const Timestamp, last_vitaldrink_time);

            DEFINE_MEMBER(3424, Vector<Skill>, skills);

            const MinimumSize<4096> size;
        };

        JobID get_job() const {
            return job_property ? job_property->id : static_cast<JobID>(-1);
        }

        uint32_t get_skill_level(const flyff::SkillID &skill_index) const {
            for (const auto &skill: skills) {
                if (skill_index == skill.id)
                    return skill.level;
            }

            return 0;
        }

        bool can_use_item(const Pointer<ItemProperty> &property) {
            return raw::w2c_f2466(kMemory.to_inside(&last_food_time), static_cast<u32>(property));
        }

        uint32_t get_inventory_slots() const {
            return max_inventory_slots - locked_inventory_slots;
        }

        bool is_in_combat(uint32_t millis = 10000) const;

        uint32_t get_param(ParamID index, uint32_t base) const {
            Parameter changeParam = get_change_param(index);
            if (changeParam != 0x7FFFFFFF)
                return changeParam;

            Parameter adjustParam = get_adjust_param(index);
            if (adjustParam)
                return base + adjustParam;
            else
                return base;
        }

        Parameter get_adjust_param(ParamID index) const {
            auto id = static_cast<uint32_t>(index);
            // Find these values in CMover::Init
            return adjusted_parameters[id] ^ adj_param ^ 0x0554e725 + id * 0x41c64e6d;
        }

        Parameter get_change_param(ParamID index) const {
            auto id = static_cast<uint32_t>(index);

            return changed_parameters[id] ^ adj_param ^ id * 0x41c64e6d + 0xb5a52d1a;
        }

        uint32_t get_hp() const {
            return raw::w2c_f411(kMemory.to_inside(this));
        }

        flyff::Pointer<flyff::ItemProperty> get_equipped_item(const flyff::Part &part) {
            using Fn = FunctionPointer<u32, u32, u32>;
            const auto &fn = fugg::function_ref<Fn>(vtable->get_active_hand_item_prop);

            const auto &ptr = fn(kMemory.to_inside(this), static_cast<uint32_t>(part));
            return flyff::Pointer<flyff::ItemProperty>(ptr);
        }

        float get_speed() const {
            auto *mover_property = reinterpret_cast<const flyff::MoverProperty *>(
                    static_cast<const flyff::ObjectProperty *>(property)
            );

            auto src_speed = mover_property->speed * speed_factor;
            auto adj_value = get_adjust_param(flyff::ParamID::Speed);
            if (adj_value != 0) {
                src_speed = src_speed + (src_speed * (static_cast<float>(adj_value) / 100.0f));
            }

            return src_speed <= 0. ? 0. : src_speed;
        }

        uint32_t get_max_hp() const {
            float factor = 1.0f;
            auto result = get_param(ParamID::MaxHitPoint, get_max_origin_hp());
            auto percent = get_param(ParamID::MaxHitPointScaling, 0);

            factor += (float) percent / (float) 100;
            result = static_cast<float>(result) * factor;

            return result;
        }

        uint32_t get_hp_percent(int percent = 100) const {
            auto max = get_max_hp();
            if (max == 0) return 0;

            return get_hp() * percent / max;
        }

        uint32_t get_max_origin_hp() const {
            using Fn = FunctionPointer<u32, u32, u32, u32>;
            const auto &fn = fugg::function_ref<Fn>(vtable->get_max_origin_hp);

            return fn(kMemory.to_inside(this), 0, 0);
        }


        // int get_max_hp() const {
        //     return raw::w2c_f410(kMemory.to_inside(this));
        // }

        uint32_t get_state() const {
            return object_state;
        }

        Object::MoveState get_move_state() const {
            // 0x000000FF
            return static_cast<Object::MoveState>((object_state >> 0) & 0xFF);
        }

        uint32_t get_turn_state() const {
            return (object_state & ObjectState::TurnAll);
        }

        uint32_t get_look_state() const {
            return (object_state & ObjectState::LookAll);
        }

        void set_jump_state(Object::JumpState state) {
            uint32_t new_state = (static_cast<uint32_t>(state) & 0xF) << 12;
            object_state = (object_state & ~(1UL << 12)) | new_state;
        }

        Object::JumpState get_jump_state() const {
            // 0x0000F000
            return static_cast<Object::JumpState>((object_state >> 12) & 0xF);
        }

        Object::AttackState get_attack_state() const {
            // 0x00FF0000
            return static_cast<Object::AttackState>((object_state >> 16) & 0xFF);
        }

        uint32_t get_damage_state() const {
            return (object_state & ObjectState::DmgAll);
        }

        uint32_t get_action_state() const {
            return (object_state & ObjectState::ActionAll);
        }

        bool is_fly() const {
            return (object_state_flags & ObjectStateFlags::Fly) != 0;
        }

        bool is_dead() const {
            return (object_state & ObjectState::DieAll) != 0;
        }

        bool is_sit() const {
            return (object_state_flags & ObjectStateFlags::Sit) != 0;
        }

        // Whether the movement state is running, not if the Mover
        // is actually moving. See `is_moving()`
        bool is_run() const {
            return (object_state_flags & ObjectStateFlags::Walk) == 0;
        }

        // Whether the movement state is walking, not if the Mover
        // is actually moving. See `is_moving()`
        bool is_walk() const {
            return (object_state_flags & ObjectStateFlags::Walk) != 0;
        }

        // Normal action - No other commands have any effect during this action.
        bool is_action() const {
            return (object_state & ObjectState::ActionAll) != 0;
        }

        bool is_moving() const {
            return (get_move_state() == Object::MoveState::MoveForward ||
                    get_move_state() == Object::MoveState::MoveBackward ||
                    get_move_state() == Object::MoveState::Left ||
                    get_move_state() == Object::MoveState::Right);
        }

        bool is_jumping() const {
            return get_jump_state() != Object::JumpState::None &&
                   get_jump_state() != Object::JumpState::Ready;
        }

        bool is_attacking() const {
            return get_attack_state() != Object::AttackState::None;
        }

        bool is_damaged() const {
            return (object_state & ObjectState::DmgAll) != 0;
        }

        bool is_turning() const {
            return (object_state & ObjectState::TurnAll) != 0;
        }

        u32 get_level() {
            const auto &fn = fugg::function_ref<FunctionPointer<u32, u32>>(vtable->get_level);

            return fn(kMemory.to_inside(this));
        }

        // int32_t get_gender() {
        //     // The function itself works but I fear it returns wrong results.
        //     // Female player = 0
        //     // Male player = 0
        //     // Npc = 2
        //     const auto& fn = fugg::function_ref<FunctionPointer<int32_t, Pointer<Mover>>>(vtable->get_gender);

        //     return fn(Pointer<Mover>(this));
        // }
    };

    struct MoverMap {
        struct __attribute__((packed)) Node {
            const Pointer<const Node> next;
            unsigned int list_index;
            unsigned long long hash;
            Pointer<Mover> mover;
        };

        const Pointer<const uintptr_t> buckets;
        size_t capacity;
        const Pointer<const Node> first;
    };

};  // namespace flyff