// logic for finding local i think
#include "expresso_recovered.h"

#include <cstring>

namespace expresso {

const char* const kPawnRoleNames[5] = {
    "Counselor",
    "jason",
    "Slasher",
    "Hunter",
    "SCKiller",
};

const char* const kKillerRoleNames[5] = {
    "jason",
    "Slasher",
    "SCKiller",
    "Savini",
    "Uber",
};

bool NameContains(const char* haystack, const char* needle) {
    if (haystack == nullptr || needle == nullptr) return false;
    return std::strstr(haystack, needle) != nullptr;
}

namespace {

bool LooksLikeGameObject(const IMemory& mem, uint64_t object) {
    if (!IsCanonicalUserPointer(object)) return false;
    const uint64_t cls = SafeReadPtr(mem, object + uobject::kClassPrivate);
    if (!IsCanonicalUserPointer(cls)) return false;
    const int32_t idx = SafeReadInt32(mem, cls + uobject::kNameIndex);
    return static_cast<uint32_t>(idx) > limits::kMinClassNameIdx;  // 0x15B7DC
}

bool HasPlayerRoleName(const IMemory& mem, uint64_t game_base, uint64_t object) {
    char name[0xB0];
    if (!GetObjectClassName(mem, game_base, object, name, sizeof(name)))
        return false;
    for (size_t i = 0; i < kPawnRoleCount; ++i) {
        if (NameContains(name, kPawnRoleNames[i]))
            return true;
    }
    return false;
}

} // namespace

uint64_t GetLocalPawn(const IMemory& mem, uint64_t game_base,
                      LocalPlayerCache& cache, uint64_t now_ms) {
    const uint64_t pc = GetLocalPlayerController(mem, game_base, cache, now_ms);
    if (pc == 0) return 0; // 0x15B77C

    // slot A: PlayerController + 0x3E0 (0x15B785)
    const uint64_t a = SafeReadPtr(mem, pc + pc_slot::kA);
    if (LooksLikeGameObject(mem, a)) return a; // 0x15B7E1

    // slot B: PlayerController + 0x6A0 (0x15B7E7)
    const uint64_t b = SafeReadPtr(mem, pc + pc_slot::kB);
    if (LooksLikeGameObject(mem, b)) return b;                  // 0x15B82F

    // slot C: *(*(PlayerController + 0x3D0) + 0x20) (0x15B835/0x15B85A)
    const uint64_t outer = SafeReadPtr(mem, pc + pc_slot::kC);
    if (!IsCanonicalUserPointer(outer)) return 0;
    const uint64_t c = SafeReadPtr(mem, outer + pc_slot::kCInner);
    if (!IsCanonicalUserPointer(c)) return 0;

    return HasPlayerRoleName(mem, game_base, c) ? c : 0; // 0x15B96A/0x15B945
}

bool IsLocalPawnKiller(const IMemory& mem, uint64_t game_base,
                       LocalPlayerCache& cache, uint64_t now_ms) {
    const uint64_t pawn = GetLocalPawn(mem, game_base, cache, now_ms);
    if (!IsCanonicalUserPointer(pawn)) return false; // 0x15BFB8

    char name[0xB0];
    if (!GetObjectClassName(mem, game_base, pawn, name, sizeof(name)))
        name[0] = '\0'; // 0x15C019

    for (size_t i = 0; i < kKillerRoleCount; ++i) {
        if (NameContains(name, kKillerRoleNames[i]))
            return true; // 0x15C0A1
    }
    return false; // 0x15C09D
}

AutoBlockResult AutoBlockTick(IMemory& mem, uint64_t game_base,
                              LocalPlayerCache& cache, uint64_t now_ms,
                              bool feature_enabled, bool menu_open_mirror,
                              const AutoBlockInput& input,
                              AutoBlockState& state,
                              ProcessEventFn invoke, void* user) {
    if (!feature_enabled) // 0x160122
        return AutoBlockResult::kDisabled;

    const uint64_t pawn = GetLocalPawn(mem, game_base, cache, now_ms); // 0x16012D
    if (!IsCanonicalUserPointer(pawn)) // 0x16013C
        return AutoBlockResult::kNoPawn;

    // Antagonists do not block, so the feature is suppressed for them
    if (IsLocalPawnKiller(mem, game_base, cache, now_ms)) // 0x160151
        return AutoBlockResult::kWrongRole;

    // Three bytes that must all read false
    if (SafeReadBool(mem, pawn + pawn::kGateE54)  || // 0x16015E
        SafeReadBool(mem, pawn + pawn::kGate1604) || // 0x160172
        SafeReadBool(mem, pawn + pawn::kGate1060)) { // 0x160186
        return AutoBlockResult::kPawnBusy;
    }

    if (menu_open_mirror) // 0x16019A
        return AutoBlockResult::kMenuOpen;

    // Held interact ('E'/'F') or attack (LMB/RMB) suppresses the block
    if (input.interact_down || input.attack_down) // 0x1601F5/0x1601FE
        return AutoBlockResult::kInputHeld;

    // local flag true first, then tell the server. The local write is
    // what makes the client's own prediction agree with the RPC it is about to
    // send ,a server that trusts the RPC therefore sees a consistent client
    SafeSetTrue(mem, pawn + pawn::kFlag1010); // 0x160206

    // Frame-count rate limit rather than a wall-clock one: 30 ticks of the
    // render loop, so the effective rate scales with frame rate
    if (state.cooldown_frames > 0) { // 0x160218
        --state.cooldown_frames; // 0x16021E
        return AutoBlockResult::kCoolingDown;
    }

    const uint64_t fn =
        FindFunctionByName(mem, game_base, pawn, "SERVER_OnBlock"); // 0x16025B
    if (fn == 0) // 0x160260
        return AutoBlockResult::kNoFunction;

    struct { bool bBlocking; } params{true}; // 0x16026B
    const ServerRpcCall call{"SERVER_OnBlock",
                             func_flags::kNative | func_flags::kPrivate,
                             &params, sizeof(params)}; // 0x160265 r9d=0x40400
    CallUFunction(mem, pawn, fn, &params, call.or_flags, invoke, user); // 0x16027B

    state.cooldown_frames = limits::kAutoBlockCooldownFrames; // 0x160280
    return AutoBlockResult::kFired;
}

bool ForcePawnFloatField(IMemory& mem, uint64_t pawn) {
    if (!IsCanonicalUserPointer(pawn)) return false;
    if (!SafeReadBool(mem, pawn + pawn::kGateFA0)) return false;
    return SafeWriteFloat(mem, pawn + pawn::kFloatF24, 1.0f);
}

} // namespace expresso
