# frozen_string_literal: true

# The Qi class provides a consistent representation of a game state
# and supports changes in the game state through the commit method.
# It is designed to be used in board games such as chess, makruk, shogi, xiangqi.
class Qi
  # @!attribute [r] captures_hash
  #   @return [Hash<Object, Integer>] a hash of captured pieces
  #   @example
  #     {"r"=>2, "b"=>1, "g"=>4, "s"=>1, "n"=>4, "p"=>17, "S"=>1}

  # @!attribute [r] squares_hash
  #   @return [Hash<Object, Object>] A hash where the keys represent square
  #     identifiers and the values represent the piece that will occupy each square.
  #     Both the keys and values can be any type of Ruby object, such as integers, strings, symbols, etc.
  #   @example
  #     {A3: "s", E4: "k", B5: "s", C22: "+P", D43: "+B"}

  # @!attribute [r] state
  #   @return [Hash<Symbol, Object>] a hash of game states
  #   @example
  #     {:in_check=>true}

  # @!attribute [r] turns
  #   @return [Array<Object>] a rotation of turns
  #   @example
  #     ["Sente", "Gote"]

  attr_reader :captures_hash, :squares_hash, :state, :turns

  # @param captures_hash [Hash<Object, Integer>] a hash of captured pieces
  # @param squares_hash [Hash<Object, Object>] A hash where the keys represent square
  #   identifiers and the values represent the piece that will occupy each square.
  #   Both the keys and values can be any type of Ruby object, such as integers, strings, symbols, etc.
  # @param turns [Array<Object>] a rotation of turns
  # @param state [Hash<Symbol, Object>] a hash of game states
  #
  # @example
  #   captures = Hash.new(0)
  #   north_captures = %w[r r b g g g g s n n n n p p p p p p p p p p p p p p p p p]
  #   south_captures = %w[S]
  #   (north_captures + south_captures).each { |piece| captures[piece] += 1 }
  #   squares = { 3 => "s", 4 => "k", 5 => "s", 22 => "+P", 43 => "+B" }
  #   Qi.new(captures, squares, [0, 1])
  def initialize(captures_hash, squares_hash, turns, **state)
    @captures_hash = ::Hash.new(0).merge(captures_hash.select { |_, v| v > 0 })
    @squares_hash = squares_hash.compact
    @turns = turns
    @state = state.transform_keys(&:to_sym)

    freeze
  end

  # Return an array of captures containing piece names.
  #
  # @return [Array<Object>] an array of captures
  # @example
  #   ["S", "b", "g", "g", "g", "g", "n", "n", "n", "n", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "r", "r", "s"]
  def captures_array
    captures_hash.flat_map { |piece, count| ::Array.new(count, piece) }.sort
  end

  # Commit a change to the game state and return a new Qi object.
  #
  # @param add_captures_array [Array<Object>] an array of pieces to be added to captures
  # @param del_captures_array [Array<Object>] an array of pieces to be deleted from captures
  # @param edit_squares_hash [Hash<Object, Object>] A hash where the keys represent square
  #   identifiers and the values represent the piece that will occupy each square.
  #   Both the keys and values can be any type of Ruby object, such as integers, strings, symbols, etc.
  # @param state [Hash<Symbol, Object>] a hash of new game states
  # @return [Qi] a new Qi object representing the updated game state
  # @example
  #   qi0.commit([], [], { D43: nil, B13: "+B" }, in_check: true)
  def commit(add_captures_array, del_captures_array, edit_squares_hash, **state)
    self.class.new(
      edit_captures_hash(add_captures_array.compact, del_captures_array.compact, **captures_hash),
      squares_hash.merge(edit_squares_hash),
      turns.rotate,
      **state
    )
  end

  # Check if the current Qi object is equal to another Qi object.
  #
  # @param other [Qi] another Qi object
  # @return [Boolean] returns true if the captures_hash, squares_hash, turn, and state of both Qi objects are equal, false otherwise
  def eql?(other)
    return false unless other.captures_hash == captures_hash
    return false unless other.squares_hash == squares_hash
    return false unless other.turn == turn
    return false unless other.state == state

    true
  end
  alias == eql?

  # Get the current turn.
  #
  # @return [Object] the current turn
  def turn
    turns.fetch(0)
  end

  private

  # Edits the captures hash and returns it.
  #
  # @param add_captures_array [Array<Object>] an array of pieces to be added to captures
  # @param del_captures_array [Array<Object>] an array of pieces to be deleted from captures
  # @param hash [Hash<Object, Integer>] the current captures hash
  # @return [Hash<Object, Integer>] the updated captures hash
  def edit_captures_hash(add_captures_array, del_captures_array, **hash)
    add_captures_array.each { |piece_name| hash[piece_name] += 1 }
    del_captures_array.each { |piece_name| hash[piece_name] -= 1 }

    hash
  end
end