//
// jja: swiss army knife for chess file formats
// src/hash.rs: Utilities for Zobrist Hashing
//
// Copyright (c) 2023 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0-or-later

use std::{
    collections::{HashMap, HashSet},
    hash::{BuildHasher, Hasher},
};

use shakmaty::{
    zobrist::{Zobrist128, Zobrist16, Zobrist32, Zobrist64, Zobrist8, ZobristHash},
    Chess, EnPassantMode,
};

use crate::{
    abk::SBookMoveEntry, brainlearn::ExperienceEntry, obk::ObkMoveEntry, polyglot::BookEntry, tr,
};

/// Calculate the Zobrist hash of the given chess position.
/// The calculated hash is Polyglot compatible.
///
/// position: A reference to the Chess position.
///
/// Returns a u64 value representing the Zobrist hash.
pub fn zobrist_hash(position: &Chess) -> u64 {
    position
        .zobrist_hash::<Zobrist64>(EnPassantMode::PseudoLegal)
        .0
}

/// Calculate the Zobrist8 hash of the given chess position.
///
/// position: A reference to the Chess position.
///
/// Returns a u8 value representing the Zobrist hash.
pub fn zobrist8_hash(position: &Chess) -> u8 {
    position
        .zobrist_hash::<Zobrist8>(EnPassantMode::PseudoLegal)
        .0
}

/// Calculate the Zobrist16 hash of the given chess position.
///
/// position: A reference to the Chess position.
///
/// Returns a u16 value representing the Zobrist hash.
pub fn zobrist16_hash(position: &Chess) -> u16 {
    position
        .zobrist_hash::<Zobrist16>(EnPassantMode::PseudoLegal)
        .0
}

/// Calculate the Zobrist32 hash of the given chess position.
///
/// position: A reference to the Chess position.
///
/// Returns a u32 value representing the Zobrist hash.
pub fn zobrist32_hash(position: &Chess) -> u32 {
    position
        .zobrist_hash::<Zobrist32>(EnPassantMode::PseudoLegal)
        .0
}

/// Calculate the Zobrist128 hash of the given chess position.
///
/// position: A reference to the Chess position.
///
/// Returns a u128 value representing the Zobrist hash.
pub fn zobrist128_hash(position: &Chess) -> u128 {
    position
        .zobrist_hash::<Zobrist128>(EnPassantMode::PseudoLegal)
        .0
}

/// Avoid duplicate hashing while using `HashMap` with Zobrist hashes.
pub struct ZobristHasher {
    value: u64,
}

impl Hasher for ZobristHasher {
    fn write(&mut self, _bytes: &[u8]) {
        unreachable!("{}", tr!("ZobristHasher should only be used for u64 keys"));
    }

    fn write_u64(&mut self, i: u64) {
        self.value = i;
    }

    fn finish(&self) -> u64 {
        self.value
    }
}

/// A builder for creating instances of `ZobristHasher`.
#[derive(Clone)]
pub struct ZobristHasherBuilder;

impl Default for ZobristHasherBuilder {
    fn default() -> Self {
        Self
    }
}

impl BuildHasher for ZobristHasherBuilder {
    type Hasher = ZobristHasher;

    fn build_hasher(&self) -> Self::Hasher {
        ZobristHasher { value: 0 }
    }
}

/// A `HashSet` with Zobrist hashers.
pub type ZobristHashSet = HashSet<u64, ZobristHasherBuilder>;

/// A `HashMap` with Zobrist hashers for EPD data.
pub type EpdHashMap = HashMap<u64, String, ZobristHasherBuilder>;

/// A `HashMap` with Zobrist hashers for `BookEntry` data.
pub type BookEntryHashMap = HashMap<u64, Vec<BookEntry>, ZobristHasherBuilder>;

/// A `HashMap` with Zobrist hashers for `ExperienceEntry` data.
pub type ExperienceEntryHashMap = HashMap<u64, Vec<ExperienceEntry>, ZobristHasherBuilder>;

/// A `HashMap` with Zobrist hashers for `ObkMoveEntry` data.
pub type ObkMoveEntryHashMap = HashMap<u64, Vec<ObkMoveEntry>, ZobristHasherBuilder>;

/// A `HashMap` with Zobrist hashers for `SBookMoveEntry` data.
pub type SBookMoveEntryHashMap = HashMap<u64, Vec<SBookMoveEntry>, ZobristHasherBuilder>;
