///|
/// A set of squares stored in a 64-bit word.
///
/// Bit `i` corresponds to `Square(i)`, so bit `0` is `a1` and bit `63` is
/// `h8`.
pub(all) struct Bitboard(UInt64) derive(Eq, @debug.Debug)

///|
pub extend Bitboard with Eq::{equal, not_equal}

///|
pub extend Bitboard with @debug.Debug::{to_repr}

///|
pub extend Bitboard with Show::{to_string, output}

///|
pub impl Show for Bitboard with fn output(self, logger) {
  logger.write_string(Bitboard::to_ascii(self))
}

///|
/// The empty square set.
pub fn Bitboard::empty() -> Bitboard {
  Bitboard(0UL)
}

///|
/// The square set containing every square.
pub fn Bitboard::full() -> Bitboard {
  Bitboard(0xFFFFFFFFFFFFFFFFUL)
}

///|
/// Wraps a raw word.
pub fn Bitboard::from_u64(value : UInt64) -> Bitboard {
  Bitboard(value)
}

///|
/// The raw word behind the square set.
pub fn Bitboard::to_u64(self : Bitboard) -> UInt64 {
  self.0
}

///|
/// A set containing exactly one square.
pub fn Bitboard::single(square : Square) -> Bitboard {
  Bitboard(1UL << square.index())
}

///|
/// Every square of one file, `0` for `a`.
pub fn Bitboard::file_mask(file : Int32) -> Bitboard {
  Bitboard(0x0101010101010101UL << file)
}

///|
/// Every square of one rank, `0` for rank `1`.
pub fn Bitboard::rank_mask(rank : Int32) -> Bitboard {
  Bitboard(0xFFUL << (rank * 8))
}

///|
/// Whether the square is a member of the set.
pub fn Bitboard::contains(self : Bitboard, square : Square) -> Bool {
  ((self.0 >> square.index()) & 1UL) == 1UL
}

///|
/// Returns a set with the square added.
pub fn Bitboard::set(self : Bitboard, square : Square) -> Bitboard {
  Bitboard(self.0 | (1UL << square.index()))
}

///|
/// Returns a set with the square removed.
pub fn Bitboard::remove(self : Bitboard, square : Square) -> Bitboard {
  Bitboard(self.0 & (1UL << square.index()).lnot())
}

///|
/// The union of two sets.
pub fn Bitboard::union(self : Bitboard, other : Bitboard) -> Bitboard {
  Bitboard(self.0 | other.0)
}

///|
/// The intersection of two sets.
pub fn Bitboard::intersect(self : Bitboard, other : Bitboard) -> Bitboard {
  Bitboard(self.0 & other.0)
}

///|
/// The squares of `self` that are not in `other`.
pub fn Bitboard::difference(self : Bitboard, other : Bitboard) -> Bitboard {
  Bitboard(self.0 & other.0.lnot())
}

///|
/// Whether the set holds no square at all.
pub fn Bitboard::is_empty(self : Bitboard) -> Bool {
  self.0 == 0UL
}

///|
/// The number of squares in the set.
pub fn Bitboard::count(self : Bitboard) -> Int32 {
  self.0.popcnt()
}

///|
/// The lowest square of the set in index order, or `None` when empty.
pub fn Bitboard::first(self : Bitboard) -> Square? {
  if self.0 == 0UL {
    None
  } else {
    Some(Square(self.0.ctz()))
  }
}

///|
/// The squares of the set in ascending index order.
pub fn Bitboard::squares(self : Bitboard) -> Array[Square] {
  let out : Array[Square] = []
  let mut bits = self.0
  while bits != 0UL {
    let index = bits.ctz()
    out.push(Square(index))
    bits = bits ^ (1UL << index)
  }
  out
}

///|
/// Renders the set as an eight by eight diagram, rank `8` first.
pub fn Bitboard::to_ascii(self : Bitboard) -> String {
  let buf = StringBuilder()
  for row = 0; row < 8; row = row + 1 {
    let rank = 7 - row
    buf.write_char(Square::rank_char(rank))
    buf.write_char(' ')
    for file = 0; file < 8; file = file + 1 {
      let square = Square::make(file, rank)
      if self.contains(square) {
        buf.write_char('X')
      } else {
        buf.write_char('.')
      }
      if file < 7 {
        buf.write_char(' ')
      }
    }
    buf.write_char('\n')
  }
  buf.write_string("  a b c d e f g h")
  buf.to_string()
}