//
// TranspositionTable.cpp - Implementation of game state evaluation cache
//

#include "TranspositionTable.hpp"

#include <cstring>
#include <iomanip>
#include <iostream>

namespace shardok {

// Global instance
TranspositionTable g_transpositionTable;

TranspositionTable::TranspositionTable() : table(TABLE_SIZE) {
    // Initialize all entries to zero
    clear();
}

auto TranspositionTable::hashGameState(const GameStateW& state) const -> uint64_t {
    // The FlatBuffer is contiguous in memory and units are sorted by ID,
    // so we can just hash the raw bytes for order-independent hashing
    // Use ComputeFNV1aHash to avoid creating a string copy
    return state.ComputeFNV1aHash();
}

std::optional<ScoreValue>
TranspositionTable::probe(const GameStateW& state, int depth, PlayerId player) {
    stats.probes++;

    uint64_t hash = hashGameState(state);
    size_t index = hash & INDEX_MASK;

    const auto& entry = table[index];

    // Check if this entry matches our position using FULL hash
    uint64_t stored_hash = entry.hash_full.load(std::memory_order_relaxed);
    uint8_t stored_depth = entry.depth.load(std::memory_order_relaxed);
    uint8_t stored_player = entry.player_id.load(std::memory_order_relaxed);
    uint16_t stored_age = entry.age.load(std::memory_order_relaxed);

    if (stored_hash == hash && stored_depth >= depth && stored_player == player &&
        stored_age == current_age.load(std::memory_order_relaxed)) {
        stats.hits++;
        float score = entry.score.load(std::memory_order_relaxed);
        return static_cast<ScoreValue>(score);
    }

    // Track collisions (different position mapped to same index)
    // Note: We use depth==0 to indicate empty entries, not hash==0
    if (stored_depth != 0 && stored_hash != hash) { stats.collisions++; }

    return std::nullopt;
}

void TranspositionTable::store(
        const GameStateW& state,
        int depth,
        PlayerId player,
        ScoreValue score) {
    stats.stores++;

    uint64_t hash = hashGameState(state);
    size_t index = hash & INDEX_MASK;

    auto& entry = table[index];

    // Simple replacement strategy: always replace if:
    // 1. Entry is from an older search (different age)
    // 2. New search is deeper
    // 3. Entry is empty (depth == 0)

    uint16_t stored_age = entry.age.load(std::memory_order_relaxed);
    uint8_t stored_depth = entry.depth.load(std::memory_order_relaxed);

    bool should_replace = (stored_depth == 0) ||          // Empty entry (depth 0 means unused)
                          (stored_age != current_age) ||  // Old entry
                          (depth >= stored_depth);        // Deeper or equal search

    if (should_replace) {
        // Store all fields with relaxed ordering (TT races are benign)
        entry.hash_full.store(hash, std::memory_order_relaxed);
        entry.score.store(static_cast<float>(score), std::memory_order_relaxed);
        entry.depth.store(static_cast<uint8_t>(depth), std::memory_order_relaxed);
        entry.player_id.store(static_cast<uint8_t>(player), std::memory_order_relaxed);
        entry.age.store(current_age, std::memory_order_relaxed);
    }
}

void TranspositionTable::clear() {
    // Reset all entries
    for (auto& entry : table) {
        entry.hash_full.store(0, std::memory_order_relaxed);
        entry.score.store(0.0f, std::memory_order_relaxed);
        entry.depth.store(0, std::memory_order_relaxed);
        entry.player_id.store(0, std::memory_order_relaxed);
        entry.age.store(0, std::memory_order_relaxed);
    }

    stats.reset();
    current_age = 0;
}

void TranspositionTable::printStats() const { printStats(std::cout); }

void TranspositionTable::printStats(std::ostream& output) const {
    const auto flags = output.flags();
    const auto precision = output.precision();

    output << "TranspositionTable Stats:\n"
           << "  Probes: " << stats.probes.load() << '\n'
           << "  Hits: " << stats.hits.load() << " (" << std::fixed << std::setprecision(1)
           << stats.hitRate() << "%)\n"
           << "  Stores: " << stats.stores.load() << '\n'
           << "  Collisions: " << stats.collisions.load() << '\n'
           << "  Table size: " << TABLE_SIZE << " entries ("
           << (TABLE_SIZE * sizeof(TTEntry)) / (1024.0 * 1024.0) << " MB)\n";

    output.flags(flags);
    output.precision(precision);
}

}  // namespace shardok
