#include "Library/Player/PlayerUtil.h"

#include "Library/Controller/PadRumbleDirector.h"
#include "Library/LiveActor/ActorClippingFunction.h"
#include "Library/LiveActor/ActorFlagFunction.h"
#include "Library/LiveActor/ActorModelFunction.h"
#include "Library/LiveActor/ActorMovementFunction.h"
#include "Library/LiveActor/ActorPoseUtil.h"
#include "Library/LiveActor/ActorSceneInfo.h"
#include "Library/LiveActor/ActorSensorUtil.h"
#include "Library/LiveActor/LiveActor.h"
#include "Library/Player/PlayerHolder.h"
#include "Project/Controller/PadRumbleKeeper.h"

namespace al {

s32 getPlayerNumMax(const LiveActor* actor) {
    return getPlayerNumMax(actor->getSceneInfo()->playerHolder);
}

s32 getPlayerNumMax(const PlayerHolder* holder) {
    return holder->getPlayerNum();
}

s32 getAlivePlayerNum(const LiveActor* actor) {
    return getAlivePlayerNum(actor->getSceneInfo()->playerHolder);
}

s32 getAlivePlayerNum(const PlayerHolder* holder) {
    s32 playerNum = getPlayerNumMax(holder);
    s32 alivePlayers = 0;

    for (s32 i = 0; i < playerNum; i++) {
        LiveActor* player = getPlayerActor(holder, i);
        if (isAlive(player))
            alivePlayers++;
    }

    return alivePlayers;
}

LiveActor* getPlayerActor(const LiveActor* actor, s32 index) {
    return getPlayerActor(actor->getSceneInfo()->playerHolder, index);
}

LiveActor* getPlayerActor(const PlayerHolder* holder, s32 index) {
    return tryGetPlayerActor(holder, index);
}

const sead::Vector3f& getPlayerPos(const LiveActor* actor, s32 index) {
    return getPlayerPos(actor->getSceneInfo()->playerHolder, index);
}

const sead::Vector3f& getPlayerPos(const PlayerHolder* holder, s32 index) {
    return getTrans(getPlayerActor(holder, index));
}

LiveActor* tryGetPlayerActor(const LiveActor* actor, s32 index) {
    return tryGetPlayerActor(actor->getSceneInfo()->playerHolder, index);
}

LiveActor* tryGetPlayerActor(const PlayerHolder* holder, s32 index) {
    return holder->tryGetPlayer(index);
}

bool isPlayerDead(const LiveActor* actor, s32 index) {
    return isPlayerDead(actor->getSceneInfo()->playerHolder, index);
}

bool isPlayerDead(const PlayerHolder* holder, s32 index) {
    return isDead(getPlayerActor(holder, index));
}

bool isPlayerAreaTarget(const LiveActor* actor, s32 index) {
    return isPlayerAreaTarget(actor->getSceneInfo()->playerHolder, index);
}

bool isPlayerAreaTarget(const PlayerHolder* holder, s32 index) {
    return isAreaTarget(getPlayerActor(holder, index));
}

LiveActor* tryFindAlivePlayerActorFirst(const LiveActor* actor) {
    return tryFindAlivePlayerActorFirst(actor->getSceneInfo()->playerHolder);
}

LiveActor* tryFindAlivePlayerActorFirst(const PlayerHolder* holder) {
    u32 playerNum = getPlayerNumMax(holder);

    for (u32 i = 0; i < playerNum; i++) {
        LiveActor* player = holder->tryGetPlayer(i);
        if (!isDead(player))
            return player;
    }

    return nullptr;
}

LiveActor* findAlivePlayerActorFirst(const LiveActor* actor) {
    return findAlivePlayerActorFirst(actor->getSceneInfo()->playerHolder);
}

LiveActor* findAlivePlayerActorFirst(const PlayerHolder* holder) {
    return tryFindAlivePlayerActorFirst(holder);
}

static inline PadRumbleKeeper* getPlayerPadRumbleKeeper(const PlayerHolder* holder, s32 index) {
    return holder->getPadRumbleKeeper(index);
}

PadRumbleKeeper* getPlayerPadRumbleKeeper(const LiveActor* actor, s32 index) {
    return getPlayerPadRumbleKeeper(actor->getSceneInfo()->playerHolder, index);
}

s32 getPlayerPort(const PlayerHolder* holder, s32 index) {
    return getPlayerPadRumbleKeeper(holder, index)->getPort();
}

s32 getPlayerPort(const LiveActor* actor, s32 index) {
    return getPlayerPort(actor->getSceneInfo()->playerHolder, index);
}

LiveActor* findAlivePlayerActorFromPort(const PlayerHolder* holder, s32 port) {
    return tryFindAlivePlayerActorFromPort(holder, port);
}

LiveActor* tryFindAlivePlayerActorFromPort(const PlayerHolder* holder, s32 port) {
    s32 playerNum = getPlayerNumMax(holder);

    for (s32 i = 0; i < playerNum; i++) {
        LiveActor* player = tryGetPlayerActor(holder, i);
        if (getPlayerPort(player, i) == port && !isPlayerDead(player, i))
            return player;
    }

    return nullptr;
}

LiveActor* findAlivePlayerActorFromPort(const LiveActor* actor, s32 port) {
    return tryFindAlivePlayerActorFromPort(actor, port);
}

LiveActor* tryFindAlivePlayerActorFromPort(const LiveActor* actor, s32 port) {
    return tryFindAlivePlayerActorFromPort(actor->getSceneInfo()->playerHolder, port);
}

s32 findNearestPlayerIdCondition(const LiveActor* actor, const sead::Vector3f& pos,
                                 bool (*condition)(const LiveActor*), f32 threshold) {
    PlayerHolder* holder = actor->getSceneInfo()->playerHolder;
    s32 playerNum = getPlayerNumMax(holder);

    f32 minDistance = sead::Mathf::maxNumber();
    s32 nearestPlayerId = -1;
    for (s32 i = 0; i < playerNum; i++) {
        LiveActor* player = holder->getPlayer(i);
        if (!player || condition(player))
            continue;
        const sead::Vector3f& playerPos = getTrans(player);
        f32 distance = (playerPos - pos).squaredLength();
        if (distance < minDistance) {
            minDistance = distance;
            nearestPlayerId = i;
        }
    }

    if (threshold * threshold < minDistance && threshold > 0.0f)
        return -1;

    return nearestPlayerId;
}

s32 findNearestPlayerId(const LiveActor* actor, f32 threshold) {
    return findNearestPlayerIdCondition(actor, getTrans(actor), &isDead, threshold);
}

LiveActor* findNearestPlayerActor(const LiveActor* actor) {
    return getPlayerActor(actor, findNearestPlayerId(actor, -1.0f));
}

LiveActor* tryFindNearestPlayerActor(const LiveActor* actor) {
    s32 nearestPlayerId = findNearestPlayerId(actor, -1.0f);
    if (nearestPlayerId < 0)
        return nullptr;
    return getPlayerActor(actor, nearestPlayerId);
}

const sead::Vector3f& findNearestPlayerPos(const LiveActor* actor) {
    return getPlayerPos(actor, findNearestPlayerId(actor, -1.0f));
}

bool tryFindNearestPlayerPos(sead::Vector3f* pos, const LiveActor* actor) {
    s32 nearestPlayerId = findNearestPlayerId(actor, -1.0f);
    if (nearestPlayerId < 0)
        return false;
    LiveActor* player = getPlayerActor(actor, nearestPlayerId);
    if (!player)
        return false;

    *pos = getTrans(player);
    return true;
}

// NON_MATCHING: regalloc (https://decomp.me/scratch/XLeWX)
bool tryFindNearestPlayerDisatanceFromTarget(f32* distance, const LiveActor* actor,
                                             const sead::Vector3f& target) {
    s32 nearestPlayerId = findNearestPlayerIdCondition(actor, target, &isDead, -1.0f);
    if (nearestPlayerId < 0)
        return false;

    LiveActor* player = getPlayerActor(actor, nearestPlayerId);
    if (!player)
        return false;

    *distance = (getTrans(player) - target).length();
    return true;
}

bool isNearPlayer(const LiveActor* actor, f32 threshold) {
    return findNearestPlayerId(actor, threshold) >= 0;
}

// BUG: This only checks the nearest player (overall), not all players
bool isNearPlayerH(const LiveActor* actor, f32 threshold) {
    s32 nearestPlayerId = findNearestPlayerId(actor, -1.0f);
    if (nearestPlayerId < 0)
        return false;

    sead::Vector3f playerPos = getPlayerPos(actor, nearestPlayerId);
    sead::Vector3f diff = playerPos - getTrans(actor);
    f32 hDiff = diff.dot(getGravity(actor));
    return (diff - getGravity(actor) * hDiff).length() <= threshold;
}

// BUG: This only checks the nearest player (overall), not all players
bool isNearPlayerHCondition(const LiveActor* actor, f32 threshold,
                            bool (*condition)(const LiveActor*)) {
    s32 nearestPlayerId = findNearestPlayerIdCondition(actor, getTrans(actor), condition, -1.0f);
    if (nearestPlayerId < 0)
        return false;

    return calcDistanceH(getPlayerActor(actor, nearestPlayerId), actor) <= threshold;
}

const sead::Vector3f& getFarPlayerPosMaxX(const LiveActor* actor) {
    PlayerHolder* holder = actor->getSceneInfo()->playerHolder;
    getTrans(actor);

    const LiveActor* farPlayer = nullptr;
    f32 maxX = -1.0f;
    for (s32 i = 0; i < getPlayerNumMax(holder); i++) {
        LiveActor* player = holder->getPlayer(i);
        if (!farPlayer || maxX < getTrans(player).x) {
            maxX = getTrans(player).x;
            farPlayer = player;
        }
    }

    return getTrans(farPlayer);
}

const sead::Vector3f& getFarPlayerPosMinX(const LiveActor* actor) {
    PlayerHolder* holder = actor->getSceneInfo()->playerHolder;
    getTrans(actor);

    const LiveActor* farPlayer = nullptr;
    f32 minX = -1.0f;
    for (s32 i = 0; i < getPlayerNumMax(holder); i++) {
        LiveActor* player = holder->getPlayer(i);
        if (!farPlayer || getTrans(player).x < minX) {
            minX = getTrans(player).x;
            farPlayer = player;
        }
    }

    return getTrans(farPlayer);
}

u32 calcPlayerListOrderByDistance(const LiveActor* actor, const LiveActor** actorList, u32 size) {
    u32 playerNum = getPlayerNumMax(actor);
    const sead::Vector3f& pos = getTrans(actor);

    f32 distances[64];
    for (s32 i = 0; i != (s32)playerNum; i++) {
        LiveActor* player = getPlayerActor(actor, i);
        f32 distance = sead::Mathf::maxNumber();
        if (!isDead(player)) {
            sead::Vector3f playerPos = getTrans(player);
            distance = (playerPos - pos).squaredLength();
        }
        distances[i] = distance;
    }

    for (s32 i = 0; (u32)i < size; i++) {
        f32 min_distance = sead::Mathf::maxNumber();
        s32 min_index = -1;

        for (u32 j = 0; j < playerNum; j++) {
            if (distances[j] <= min_distance) {
                min_distance = distances[j];
                min_index = j;
            }
        }

        if (min_index == -1)
            return i;

        actorList[i] = getPlayerActor(actor, min_index);
        distances[min_index] = sead::Mathf::maxNumber();
    }

    return size;
}

// TODO: flag-loop issue, this shouldn't be written like this
static s32 blackBox(s32 value) {
    __asm__("" : "+r"(value));
    return value;
}

u32 calcAlivePlayerActor(const LiveActor* actor, const LiveActor** actorList, u32 size) {
    u32 playerNum = getPlayerNumMax(actor);
    s32 flag = 2;
    u32 result;
    u32 resultNum = 0;

    if (playerNum != 0) {
        for (u32 i = 0; i < playerNum; i++) {
            LiveActor* player = getPlayerActor(actor, i);
            if (!isDead(player)) {
                actorList[resultNum] = player;
                resultNum++;
                if (resultNum >= size) {
                    result = size;
                    flag = 1;
                } else {
                    flag = 0;
                }
            } else {
                flag = 4;
            }

            if ((blackBox(flag | 4) & 7) != 4)
                goto end;
        }
        flag = 2;
    }
end:
    if (flag != 2)
        resultNum = result;
    return resultNum;
}

LiveActor* tryFindNearestPlayerActorCondition(const LiveActor* actor,
                                              bool (*condition)(const LiveActor*)) {
    s32 nearestPlayerId = findNearestPlayerIdCondition(actor, getTrans(actor), condition, -1.0f);
    if (nearestPlayerId < 0)
        return nullptr;
    return getPlayerActor(actor, nearestPlayerId);
}

bool tryFindNearestPlayerPosCondition(sead::Vector3f* pos, const LiveActor* actor,
                                      bool (*condition)(const LiveActor*)) {
    LiveActor* nearestPlayer = tryFindNearestPlayerActorCondition(actor, condition);
    if (!nearestPlayer)
        return false;
    *pos = getTrans(nearestPlayer);
    return true;
}

bool isResetablePlayerPos(const LiveActor* actor, const sead::Vector3f& pos, f32 clippingRadius,
                          f32 threshold) {
    if (isJudgedToClipFrustum(actor, pos, clippingRadius, 300.0f) &&
        !isNearPlayer(actor, threshold))
        return true;
    return false;
}

bool isResetablePlayerPos(const LiveActor* actor, f32 threshold) {
    return isResetablePlayerPos(actor, getTrans(actor), getClippingRadius(actor), threshold);
}

void faceToPlayer(LiveActor* actor) {
    sead::Vector3f dir;
    calcDirToActorH(&dir, actor, getPlayerActor(actor, 0));
    faceToDirection(actor, dir);
}

}  // namespace al

namespace alPlayerFunction {

void registerPlayer(al::LiveActor* actor, al::PadRumbleKeeper* padRumbleKeeper) {
    return actor->getSceneInfo()->playerHolder->registerPlayer(actor, padRumbleKeeper);
}

bool isFullPlayerHolder(al::LiveActor* actor) {
    return actor->getSceneInfo()->playerHolder->isFull();
}

s32 findPlayerHolderIndex(const al::LiveActor* actor) {
    al::PlayerHolder* playerHolder = actor->getSceneInfo()->playerHolder;
    s32 playerNum = playerHolder->getPlayerNum();
    for (s32 i = 0; i < playerNum; i++)
        if (playerHolder->getPlayer(i) == actor)
            return i;
    return 0;
}

s32 findPlayerHolderIndex(const al::HitSensor* sensor) {
    return findPlayerHolderIndex(al::getSensorHost(sensor));
}

bool isPlayerActor(const al::LiveActor* actor) {
    al::PlayerHolder* playerHolder = actor->getSceneInfo()->playerHolder;
    s32 playerNum = playerHolder->getPlayerNum();
    for (s32 i = 0; i < playerNum; i++)
        if (playerHolder->getPlayer(i) == actor)
            return true;
    return false;
}

bool isPlayerActor(const al::HitSensor* sensor) {
    return isPlayerActor(al::getSensorHost(sensor));
}

}  // namespace alPlayerFunction
