//
// Created by Dan Crosby on 2025-01-21.
//

#include "GameStateW.hpp"

#include "src/main/cpp/net/eagle0/common/ContainerUtils.hpp"

namespace shardok {

auto GameStateW::GetOccupant(const net::eagle0::shardok::storage::fb::Coords& coords) const
        -> const Unit* {
    const auto* state = Get();
    if (!state || !state->hex_map()) { return nullptr; }

    const int16_t rowCount = state->hex_map()->row_count();
    const int16_t columnCount = state->hex_map()->column_count();

    // Check bounds
    if (coords.row() < 0 || coords.row() >= rowCount || coords.column() < 0 ||
        coords.column() >= columnCount) {
        return nullptr;
    }

    // Fast path: use bitfield cache if available
    if (state->occupied_tiles() && !state->occupied_tiles()->empty()) {
        const size_t tileIndex = coords.row() * columnCount + coords.column();
        const size_t expectedBitfieldSize = (rowCount * columnCount + 7) / 8;  // Ceiling division

        if (state->occupied_tiles()->size() == expectedBitfieldSize) {
            const size_t byteIndex = tileIndex / 8;
            const size_t bitOffset = tileIndex % 8;
            const uint8_t byte = state->occupied_tiles()->Get(byteIndex);
            const bool isOccupied = (byte & (1 << bitOffset)) != 0;

            if (!isOccupied) {
                return nullptr;  // Fast path: definitely no unit here (90% of cases)
            }
        }
    }

    // Slow path: O(n) search through units
    // Used when bitfield not available OR when bitfield indicates occupation
    if (!state->units()) { return nullptr; }

    for (int i = 0; i < state->units()->size(); ++i) {
        const auto* unit = state->units()->Get(i);
        if (unit && unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
            unit->location().row() == coords.row() &&
            unit->location().column() == coords.column()) {
            return unit;
        }
    }

    return nullptr;
}

auto GameStateW::GetKnownEnemyOccupant(
        PlayerId playerId,
        const std::vector<PlayerId>& allyPids,
        const net::eagle0::shardok::storage::fb::Coords& coords) const -> const Unit* {
    const auto* occupant = GetOccupant(coords);
    if (occupant) {
        if (!occupant->hidden() && occupant->player_id() != playerId &&
            !common::Contains(allyPids, occupant->player_id())) {
            return occupant;
        }
    }
    return nullptr;
}

void GameStateW::UpdateOccupiedTile(
        const net::eagle0::shardok::storage::fb::Coords& oldCoords,
        const net::eagle0::shardok::storage::fb::Coords& newCoords) {
    const auto* state = Get();
    auto* mutableOccupiedTiles = (*this)->mutable_occupied_tiles();
    if (!state || !state->hex_map() || !mutableOccupiedTiles) { return; }

    const int16_t rowCount = state->hex_map()->row_count();
    const int16_t columnCount = state->hex_map()->column_count();

    // Clear old position in bitfield
    if (oldCoords.row() >= 0 && oldCoords.row() < rowCount && oldCoords.column() >= 0 &&
        oldCoords.column() < columnCount) {
        const size_t tileIndex = oldCoords.row() * columnCount + oldCoords.column();
        const size_t byteIndex = tileIndex / 8;
        const size_t bitOffset = tileIndex % 8;
        if (byteIndex < mutableOccupiedTiles->size()) {
            uint8_t byte = mutableOccupiedTiles->Get(byteIndex);
            byte &= ~(1 << bitOffset);  // Clear the bit
            mutableOccupiedTiles->Mutate(byteIndex, byte);
        }
    }

    // Set new position in bitfield
    if (newCoords.row() >= 0 && newCoords.row() < rowCount && newCoords.column() >= 0 &&
        newCoords.column() < columnCount) {
        const size_t tileIndex = newCoords.row() * columnCount + newCoords.column();
        const size_t byteIndex = tileIndex / 8;
        const size_t bitOffset = tileIndex % 8;
        if (byteIndex < mutableOccupiedTiles->size()) {
            uint8_t byte = mutableOccupiedTiles->Get(byteIndex);
            byte |= (1 << bitOffset);  // Set the bit
            mutableOccupiedTiles->Mutate(byteIndex, byte);
        }
    }
}

auto GameStateW::GetOccupiedTilesBitfield() const -> const flatbuffers::Vector<uint8_t>* {
    const auto* state = Get();
    if (!state || !state->hex_map()) { return nullptr; }

    if (!state->occupied_tiles() || state->occupied_tiles()->empty()) { return nullptr; }

    // Verify the bitfield size matches expected map size
    const int16_t rowCount = state->hex_map()->row_count();
    const int16_t columnCount = state->hex_map()->column_count();
    const size_t expectedBitfieldSize = (rowCount * columnCount + 7) / 8;

    if (state->occupied_tiles()->size() != expectedBitfieldSize) { return nullptr; }

    return state->occupied_tiles();
}

}  // namespace shardok