//
// jja: swiss army knife for chess file formats
// src/obk.rs: Chessmaster book constants and utilities
//
// Copyright (c) 2023 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0-or-later

use std::convert::From;

use shakmaty::{uci::Uci, Chess, Color, Position, Rank, Role, Square};

/// `ObkMoveEntry` represents a single move entry in an OBK opening book.
///
/// The struct contains the move's properties, such as whether it is the last move
/// in a variation or at a level, the move's from/to rank and file, and its weight.
#[derive(Copy, Clone, Debug)]
pub struct ObkMoveEntry {
    /// Zobrist Hash
    pub key: u64,
    /// Indicates if this is the last move in the variation.
    pub is_last_in_variation: bool,
    /// Indicates if this is the last move at this level.
    pub is_last_at_level: bool,
    /// The move's from rank (0..7 meaning 1..8).
    pub from_rank: u8,
    /// The move's from file (0..7 meaning A..H).
    pub from_file: u8,
    /// The move's weight (0..3 meaning 1, 25, 50, 100).
    pub weight: u8,
    /// The move's to rank (0..7 meaning 1..8).
    pub to_rank: u8,
    /// The move's to file (0..7 meaning A..H).
    pub to_file: u8,
}

/// `ObkNoteEntry` represents a single note entry in an OBK opening book.
///
/// The struct contains the move number associated with the note, the note length,
/// the note type, and the note text.
pub struct ObkNoteEntry {
    /// The move number associated with the note.
    pub move_number: u32,
    /// The length of the note text.
    pub note_length: u8,
    /// The type of the note (80: Variation name, 81: Move annotation, 82: EOC code).
    pub note_type: u8,
    /// The note text.
    pub note_text: String,
}

/* FIXME: This function does not honour promotion! */
/// Implementing the conversion of an `ObkMoveEntry` into a `shakmaty::Uci`.
impl From<ObkMoveEntry> for Uci {
    /// Converts an `ObkMoveEntry` into a `shakmaty::Uci`.
    ///
    /// This implementation assumes that the given `ObkMoveEntry` contains
    /// valid move information. It will create a `shakmaty::Uci` using the from/to
    /// rank and file information from the `ObkMoveEntry`.
    fn from(obk_move: ObkMoveEntry) -> Self {
        let from = Square::from_coords(
            shakmaty::File::new(u32::from(obk_move.from_file)),
            shakmaty::Rank::new(u32::from(obk_move.from_rank)),
        );
        let to = Square::from_coords(
            shakmaty::File::new(u32::from(obk_move.to_file)),
            shakmaty::Rank::new(u32::from(obk_move.to_rank)),
        );

        Uci::Normal {
            from,
            to,
            promotion: None,
        }
    }
}

/// Converts an `ObkMoveEntry` into a `shakmaty::Uci`.
///
/// This implementation assumes that the given `ObkMoveEntry` contains
/// valid move information. It will create a `shakmaty::Uci` using the from/to
/// rank and file information from the `ObkMoveEntry`.
pub fn uci_from_obk_move_and_position(obk_move: ObkMoveEntry, pos: &Chess) -> Uci {
    let from = Square::from_coords(
        shakmaty::File::new(u32::from(obk_move.from_file)),
        shakmaty::Rank::new(u32::from(obk_move.from_rank)),
    );
    let to = Square::from_coords(
        shakmaty::File::new(u32::from(obk_move.to_file)),
        shakmaty::Rank::new(u32::from(obk_move.to_rank)),
    );

    // Check if the move is a pawn promotion
    let piece_at_from = pos
        .board()
        .piece_at(from)
        .expect("No piece on 'from' square");
    let promotion = if piece_at_from.role == Role::Pawn
        && ((piece_at_from.color == Color::White && to.rank() == Rank::Eighth)
            || (piece_at_from.color == Color::Black && to.rank() == Rank::First))
    {
        Some(Role::Queen)
    } else {
        None
    };

    Uci::Normal {
        from,
        to,
        promotion,
    }
}

impl ObkMoveEntry {
    /// Creates an `ObkMoveEntry` from a two-byte representation and a Zobrist hash.
    ///
    /// # Arguments
    ///
    /// * `bytes` - A two-byte array representing an `ObkMoveEntry`.
    /// * `position_hash` - The Zobrist hash of the position.
    ///
    /// # Returns
    ///
    /// Returns an `ObkMoveEntry` created from the given bytes and position hash.
    pub fn from_bytes(bytes: [u8; 2], key: u64) -> Self {
        let byte1 = bytes[0];
        let byte2 = bytes[1];

        let from_file = byte1 & 0b111;
        let from_rank = (byte1 >> 3) & 0b111;
        let to_file = byte2 & 0b111;
        let to_rank = (byte2 >> 3) & 0b111;

        let weight = match (byte2 >> 6) & 0x03 {
            0 => 1,
            1 => 25,
            2 => 50,
            3 => 100,
            _ => 1, /* TODO: Make these user-configurable */
        };

        ObkMoveEntry {
            key,
            weight,
            from_rank,
            from_file,
            to_rank,
            to_file,
            is_last_in_variation: (byte1 & 0x80) != 0,
            is_last_at_level: (byte1 & 0x40) != 0,
        }
    }
}
