///|
/// The squares a knight attacks from every square.
let knight_attack_table : FixedArray[UInt64] = build_step_attacks([
  (1, 2),
  (2, 1),
  (2, -1),
  (1, -2),
  (-1, -2),
  (-2, -1),
  (-2, 1),
  (-1, 2),
])

///|
/// The squares a king attacks from every square.
let king_attack_table : FixedArray[UInt64] = build_step_attacks([
  (0, 1),
  (1, 1),
  (1, 0),
  (1, -1),
  (0, -1),
  (-1, -1),
  (-1, 0),
  (-1, 1),
])

///|
fn build_step_attacks(offsets : Array[(Int32, Int32)]) -> FixedArray[UInt64] {
  let table = FixedArray::make(64, 0UL)
  for square in Square::all() {
    let mut bits = 0UL
    for offset in offsets {
      match square.offset(offset.0, offset.1) {
        Some(target) => bits = bits | (1UL << target.index())
        None => ()
      }
    }
    table[square.index()] = bits
  }
  table
}

///|
/// The squares a knight on `square` attacks.
pub fn Bitboard::knight_attacks(square : Square) -> Bitboard {
  Bitboard::from_u64(knight_attack_table[square.index()])
}

///|
/// The squares a king on `square` attacks.
pub fn Bitboard::king_attacks(square : Square) -> Bitboard {
  Bitboard::from_u64(king_attack_table[square.index()])
}

///|
/// The squares a pawn of `color` attacks from `square`.
pub fn Bitboard::pawn_attacks(color : Color, square : Square) -> Bitboard {
  let rank_delta = if color == White { 1 } else { -1 }
  let mut bits = 0UL
  match square.offset(-1, rank_delta) {
    Some(target) => bits = bits | (1UL << target.index())
    None => ()
  }
  match square.offset(1, rank_delta) {
    Some(target) => bits = bits | (1UL << target.index())
    None => ()
  }
  Bitboard::from_u64(bits)
}

///|
/// Walks from `from` in one direction until the board ends or a piece is
/// reached. The blocking square is included.
fn ray_attacks(
  from : Square,
  occupancy : UInt64,
  file_delta : Int32,
  rank_delta : Int32,
) -> UInt64 {
  let mut bits = 0UL
  let mut file = from.file() + file_delta
  let mut rank = from.rank() + rank_delta
  while file >= 0 && file < 8 && rank >= 0 && rank < 8 {
    let index = rank * 8 + file
    bits = bits | (1UL << index)
    if ((occupancy >> index) & 1UL) == 1UL {
      break
    }
    file = file + file_delta
    rank = rank + rank_delta
  }
  bits
}

///|
/// The squares a rook on `square` attacks.
///
/// Each ray stops at the first occupied square and includes it, so a blocking
/// piece is part of the set. Move generation removes own pieces from the result.
pub fn Bitboard::rook_attacks(
  square : Square,
  occupancy : Bitboard,
) -> Bitboard {
  let occ = occupancy.to_u64()
  Bitboard::from_u64(
    ray_attacks(square, occ, 0, 1) |
    ray_attacks(square, occ, 0, -1) |
    ray_attacks(square, occ, 1, 0) |
    ray_attacks(square, occ, -1, 0),
  )
}

///|
/// The squares a bishop on `square` attacks.
///
/// As with rooks, each ray includes the square that stops it.
pub fn Bitboard::bishop_attacks(
  square : Square,
  occupancy : Bitboard,
) -> Bitboard {
  let occ = occupancy.to_u64()
  Bitboard::from_u64(
    ray_attacks(square, occ, 1, 1) |
    ray_attacks(square, occ, 1, -1) |
    ray_attacks(square, occ, -1, 1) |
    ray_attacks(square, occ, -1, -1),
  )
}

///|
/// The squares a queen on `square` attacks.
///
/// As with rooks and bishops, each ray includes the square that stops it.
pub fn Bitboard::queen_attacks(
  square : Square,
  occupancy : Bitboard,
) -> Bitboard {
  Bitboard::rook_attacks(square, occupancy).union(
    Bitboard::bishop_attacks(square, occupancy),
  )
}

///|
/// The squares a piece of one kind and colour attacks from `square`.
///
/// Pawn attacks ignore the en passant target; use the position level helpers
/// for a complete move list.
pub fn Bitboard::attacks_of(
  kind : PieceKind,
  color : Color,
  square : Square,
  occupancy : Bitboard,
) -> Bitboard {
  match kind {
    Pawn => Bitboard::pawn_attacks(color, square)
    Knight => Bitboard::knight_attacks(square)
    Bishop => Bitboard::bishop_attacks(square, occupancy)
    Rook => Bitboard::rook_attacks(square, occupancy)
    Queen => Bitboard::queen_attacks(square, occupancy)
    King => Bitboard::king_attacks(square)
  }
}