#pragma once

#include <basis/seadTypes.h>
#include <prim/seadEnum.h>
#include <prim/seadSafeString.h>
#include "KingSystem/Utils/BitField.h"

namespace ksys::phys {

enum class ContactLayerType {
    Entity,
    Sensor,
    Invalid,
};

constexpr int NumContactLayerTypes = 2;

SEAD_ENUM(ContactLayer,
EntityObject,\
EntitySmallObject,\
EntityGroundObject,\
EntityPlayer,\
EntityNPC,\
EntityRagdoll,\
EntityWater,\
EntityAirWall,\
EntityGround,\
EntityGroundSmooth,\
EntityGroundRough,\
EntityRope,\
EntityTree,\
EntityNPC_NoHitPlayer,\
EntityHitOnlyWater,\
EntityWallForClimb,\
EntityHitOnlyGround,\
EntityQueryCustomReceiver,\
EntityForbidden18,\
EntityNoHit,\
EntityMeshVisualizer,\
EntityForbidden21,\
EntityForbidden22,\
EntityForbidden23,\
EntityForbidden24,\
EntityForbidden25,\
EntityForbidden26,\
EntityForbidden27,\
EntityForbidden28,\
EntityForbidden29,\
EntityForbidden30,\
EntityEnd,\
SensorObject,\
SensorSmallObject,\
SensorPlayer,\
SensorEnemy,\
SensorNPC,\
SensorHorse,\
SensorRope,\
SensorAttackPlayer,\
SensorAttackEnemy,\
SensorChemical,\
SensorTerror,\
SensorHitOnlyInDoor,\
SensorInDoor,\
SensorReserve13,\
SensorReserve14,\
SensorChemicalElement,\
SensorAttackCommon,\
SensorQueryOnly,\
SensorTree,\
SensorCamera,\
SensorMeshVisualizer,\
SensorNoHit,\
SensorReserve20,\
SensorCustomReceiver,\
SensorEnd)

constexpr int MaxNumLayersPerType = 32;

constexpr auto FirstEntity = ContactLayer::EntityObject;
constexpr auto LastEntity = ContactLayer::EntityMeshVisualizer;

constexpr auto FirstSensor = ContactLayer::SensorObject;
constexpr auto LastSensor = ContactLayer::SensorCustomReceiver;

// If the values of the two following constants are changed, most of the BitFields defined
// in this file will need to be updated.
constexpr int NumRegularEntityLayers = ContactLayer::EntityHitOnlyGround - FirstEntity + 1;
constexpr int NumRegularSensorLayers = ContactLayer::SensorMeshVisualizer - FirstSensor + 1;
static_assert(NumRegularEntityLayers == 17);
static_assert(NumRegularSensorLayers == 21);

constexpr bool isEntityGroundLayer(ContactLayer::ValueType layer) {
    return layer == ContactLayer::EntityGround || layer == ContactLayer::EntityGroundSmooth ||
           layer == ContactLayer::EntityGroundRough;
}

SEAD_ENUM(Material,
Undefined,\
Soil,\
Stone,\
Sand,\
Metal,\
WireNet,\
Grass,\
Wood,\
Water,\
Snow,\
Ice,\
Lava,\
Bog,\
HeavySand,\
Cloth,\
Glass,\
Bone,\
Rope,\
CharControl,\
Ragdoll,\
Surfing,\
GuardianFoot,\
HeavySnow,\
Unused0,\
LaunchPad,\
Conveyer,\
Rail,\
Grudge,\
Meat,\
Vegetable,\
Bomb,\
MagicBall,\
Barrier,\
AirWall,\
Misc,\
GrudgeSlow
)

constexpr bool isInvalidMaterial(Material::ValueType mat) {
    return mat >= Material::size();
}

SEAD_ENUM(GroundHit,
Player,\
Animal,\
NPC,\
Camera,\
AttackHitPlayer,\
AttackHitEnemy,\
Arrow,\
Bomb,\
Magnet,\
CameraBody,\
IK,\
Grudge,\
MovingTrolley,\
LineOfSight,\
Giant,\
HitAll,\
Ignore
)

SEAD_ENUM(FloorCode,
None,\
Return,\
FlowStraight,\
FlowLeft,\
FlowRight,\
Slip,\
NarrowPlace,\
TopBroadleafTree,\
TopConiferousTree,\
Fall,\
Attach,\
NoImpulseUpperMove,\
NoPreventFall
)

SEAD_ENUM(WallCode,
None,\
NoClimb,\
Hang,\
LadderUp,\
Ladder,\
Slip,\
LadderSide,\
NoSlipRain,\
NoDashUpAndNoClimb,\
IceMakerBlock
)

enum class MotionType {
    Dynamic = 0,
    Fixed = 1,
    Keyframed = 2,
    Unknown = 3,
    Invalid = -1,
};

enum class GroundCollisionMode {
    /// Ground collision is not handled in any special way.
    Normal = 0,
    /// Any collision with a non-ground layer is ignored.
    IgnoreNonGround = 1,
    /// Any collision with a ground layer is ignored.
    IgnoreGround = 2,
};

enum class WaterCollisionMode {
    /// Water collision is not handled in any special way.
    Normal = 0,
    /// Any collision with a water layer is ignored.
    IgnoreWater = 1,
};

/// Collision filter info / collision mask that is used for entity rigid bodies.
/// https://docs.google.com/spreadsheets/d/e/2PACX-1vQyEL5_Wee3MI23c-nHa4dMPJDVen9TMMcrOUX7Wka9NAH1AW9bkkq7ZJHawJkSzGOqgHUYc-83t4Or/pubhtml
union EntityCollisionMask {
    union RegularMask {
        ContactLayer getLayer() const { return int(layer); }
        ContactLayer getLayerSensor() const { return int(layer + FirstSensor); }
        GroundHit getGroundHit() const { return int(ground_hit); }

        u32 raw;
        util::BitField<0, 5, u32> layer;

        /// Only valid for ragdoll masks.
        util::BitField<5, 5, u32> ragdoll_bone_index;
        /// Only valid for ragdoll masks.
        util::BitField<10, 5, u32> ragdoll_parent_bone_index;

        /// Only valid for CustomReceiver masks.
        /// Layers to collide with for EntityQueryCustomReceiver entities.
        util::BitField<5, NumRegularEntityLayers, u32> query_custom_receiver_layer_mask;

        /// Only valid for non-CustomReceiver masks.
        util::BitField<16, 10, u32> group_handler_index;
        util::BitField<26, 4, u32> ground_hit;
    };

    union GroundHitMask {
        ContactLayer getLayer() const { return int(layer); }

        void addGroundHit(GroundHit hit) {
            raw |= (1 << hit) << decltype(ground_hit_types)::StartBit();
        }

        u32 raw;
        util::BitField<0, 1, u32> unk;
        util::BitField<8, 16, u32> ground_hit_types;
        util::BitField<23, 1, u32> unk23;
        util::BitField<25, 5, u32> layer;
    };

    constexpr explicit EntityCollisionMask(u32 raw_ = 0) : raw(raw_) {}
    constexpr EntityCollisionMask(const EntityCollisionMask&) = default;
    constexpr EntityCollisionMask& operator=(const EntityCollisionMask& m) {
        raw = m.raw;
        return *this;
    }

    bool operator==(EntityCollisionMask rhs) const { return raw == rhs.raw; }
    bool operator!=(EntityCollisionMask rhs) const { return raw != rhs.raw; }

    static EntityCollisionMask make(ContactLayer layer, GroundHit ground_hit) {
        EntityCollisionMask mask;
        mask.regular.layer.Init(layer);
        mask.regular.ground_hit.Init(ground_hit);
        return mask;
    }

    ContactLayer getLayer() const {
        return is_ground_hit_mask ? ground_hit.getLayer() : regular.getLayer();
    }

    ContactLayer getLayerSensor() const {
        return is_ground_hit_mask ? ContactLayer(ContactLayer::SensorCustomReceiver) :
                                    regular.getLayerSensor();
    }

    GroundHit getGroundHit() const {
        return is_ground_hit_mask ? GroundHit::HitAll : regular.getGroundHit();
    }

    u32 raw;
    RegularMask regular;
    GroundHitMask ground_hit;
    /// Only valid in non-ragdoll, non-CustomReceiver mode.
    util::BitField<5, 2, GroundCollisionMode, u32> ground_col_mode;
    /// Only valid in non-ragdoll, non-CustomReceiver mode.
    util::BitField<7, 1, WaterCollisionMode, u32> water_col_mode;
    /// If this flag is set, then this entity will always collide with ground or water,
    /// regardless of the configured GroundCollisionMode or WaterCollisionMode modes.
    util::BitField<30, 1, bool, u32> is_ragdoll;
    util::BitField<31, 1, bool, u32> is_ground_hit_mask;
};
static_assert(sizeof(EntityCollisionMask) == sizeof(u32));

/// Collision mask that is used for raycast-based queries against entities.
union EntityQueryCollisionMask {
    constexpr explicit EntityQueryCollisionMask(u32 raw_ = 0) : raw(raw_) {}
    constexpr EntityQueryCollisionMask(const EntityQueryCollisionMask&) = default;
    constexpr EntityQueryCollisionMask& operator=(const EntityQueryCollisionMask& m) {
        raw = m.raw;
        return *this;
    }
    constexpr bool operator==(EntityQueryCollisionMask rhs) const { return raw == rhs.raw; }
    constexpr bool operator!=(EntityQueryCollisionMask rhs) const { return raw != rhs.raw; }

    util::BitField<0, NumRegularEntityLayers, u32> layer_mask;
    util::BitField<17, 1, u32> unk;
    util::BitField<18, 10, u32> group_handler_index;
    util::BitField<28, 4, GroundHit::ValueType, u32> ground_hit_type;
    u32 raw;
};

union SensorCollisionMask {
    struct CustomReceiverTag {};

    union Data {
        ContactLayer getLayer() const { return int(layer) + FirstSensor; }

        /// The sensor's contact layer.
        /// @note Add FirstSensor to get the actual ContactLayer value.
        util::BitField<0, 5, u32> layer;
        /// Whether `ignored_layer` contains a valid layer.
        util::BitField<5, 1, bool, u32> has_ignored_layer;
        /// Contact layer to ignore during collision detection. Only valid if `has_ignored_layer`.
        /// @note Add FirstSensor to get the actual ContactLayer value.
        util::BitField<6, 5, u32> ignored_layer;
    };

    union CustomReceiverData {
        util::BitField<0, NumRegularSensorLayers, u32> layer;
        util::BitField<0, NumRegularSensorLayers, u32> layer_mask;
    };

    constexpr SensorCollisionMask() : raw(0) {}
    constexpr explicit SensorCollisionMask(CustomReceiverTag) : raw(0) {
        is_custom_receiver = true;
    }
    constexpr explicit SensorCollisionMask(u32 raw_) : raw(raw_) {}
    constexpr SensorCollisionMask(const SensorCollisionMask&) = default;
    constexpr SensorCollisionMask& operator=(const SensorCollisionMask& m) {
        raw = m.raw;
        return *this;
    }

    static SensorCollisionMask make(ContactLayer layer) {
        SensorCollisionMask mask;
        if (layer == ContactLayer::SensorCustomReceiver) {
            mask.is_custom_receiver = true;
            mask.custom_receiver_data.layer.Init(ContactLayer::SensorCustomReceiver - FirstSensor);
        } else {
            mask.is_custom_receiver = false;
            mask.data.layer.Init(layer - FirstSensor);
        }
        return mask;
    }

    ContactLayer getLayer() const {
        return is_custom_receiver ? ContactLayer::SensorCustomReceiver : data.getLayer();
    }

    u32 raw;
    Data data;
    CustomReceiverData custom_receiver_data;
    util::BitField<21, 10, u32> group_handler_index;
    util::BitField<31, 1, bool, u32> is_custom_receiver;
};

/// Collision mask that is used for raycast-based queries against sensors.
union SensorQueryCollisionMask {
    constexpr explicit SensorQueryCollisionMask(u32 raw_ = 0) : raw(raw_) {}
    constexpr SensorQueryCollisionMask(const SensorQueryCollisionMask&) = default;
    constexpr SensorQueryCollisionMask& operator=(const SensorQueryCollisionMask& m) {
        raw = m.raw;
        return *this;
    }
    constexpr bool operator==(SensorQueryCollisionMask rhs) const { return raw == rhs.raw; }
    constexpr bool operator!=(SensorQueryCollisionMask rhs) const { return raw != rhs.raw; }

    util::BitField<0, NumRegularSensorLayers, u32> layer_mask;
    util::BitField<22, 10, u32> group_handler_index;
    u32 raw;
};

ContactLayerType getContactLayerType(ContactLayer layer);
u32 makeContactLayerMask(ContactLayer layer);
u32 getContactLayerBase(ContactLayerType type);
int getContactLayerBaseRelativeValue(ContactLayer layer);
const char* contactLayerToText(ContactLayer layer);
ContactLayer contactLayerFromText(const sead::SafeString& text);

const char* materialToText(Material material);
Material materialFromText(const sead::SafeString& text);

const char* groundHitToText(GroundHit hit);
GroundHit groundHitFromText(const sead::SafeString& text);

const char* floorCodeToText(FloorCode code);
FloorCode floorCodeFromText(const sead::SafeString& text);

const char* wallCodeToText(WallCode code);
WallCode wallCodeFromText(const sead::SafeString& text);

MotionType motionTypeFromText(const sead::SafeString& text);

}  // namespace ksys::phys
