module Chess
  # Rappresents a game in PGN (Portable Game Notation) format.
  class Pgn
    # Array that include PGN standard tags.
    TAGS = %w[event site date round white black result].freeze

    # The name of the tournament or match event.
    # @return [String]
    attr_accessor :event
    # The location of the event. This is in City, Region COUNTRY format, where
    # COUNTRY is the three-letter International Olympic Committee code for the
    # country.
    # @return [String]
    attr_accessor :site
    # The starting date of the game, in YYYY.MM.DD form. ?? is used for unknown values.
    # @return [String]
    attr_reader :date
    # The playing round ordinal of the game within the event.
    # @return [String]
    attr_accessor :round
    # The player of the white pieces, in Lastname, Firstname format.
    # @return [String]
    attr_accessor :white
    # The player of the black pieces, same format as White.
    # @return [String]
    attr_accessor :black
    # The result of the game. This can only have four possible values: 1-0
    # (White won), 0-1 (Black won), 1/2-1/2 (Draw), or * (other, e.g., the game
    # is ongoing).
    # @return [String]
    attr_accessor :result
    # The array with all moves done in short algebraic chess notation.
    # @return [Array<String>]
    attr_accessor :moves

    # @param [String] filename The path of the PGN file.
    # @param [Boolean] check_moves If true check if the moves are legal.
    # @raise [InvalidPgnFormatError]
    # @raise [IllegalMoveError]
    def initialize(filename = nil, check_moves: false)
      self.load(filename, check_moves: check_moves) if filename
      @date = '??'
    end

    # Load a PGN from file.
    # @param [String] filename The path of the PGN file.
    # @param [Boolean] check_moves If true check if the moves are legal.
    # @return [Pgn] Returns `self`.
    # @raise [InvalidPgnFormatError]
    # @raise [IllegalMoveError]
    def load(filename, check_moves: false)
      str = File.read(filename)
      load_from_string(str, check_moves: check_moves)
    end

    # Load a PGN from string.
    # @param [String] str The PGN string to load.
    # @param [Boolean] check_moves If true check if the moves are legal.
    # @return [Pgn] Returns `self`.
    # @raise [InvalidPgnFormatError]
    # @raise [IllegalMoveError]
    def load_from_string(str, check_moves: false)
      str.gsub!(/\{.*?\}/, '') # remove comments
      TAGS.each do |t|
        instance_variable_set(:"@#{t}", str.match(/^\[#{t.capitalize} ".*"\]\s?$/).to_s.strip[t.size + 3..-3])
      end
      @result = '1/2-1/2' if @result == '1/2'
      game_index = str.index(/^1\./)
      raise Chess::InvalidPgnFormatError.new if game_index.nil?

      game = str[game_index..-1].strip
      @moves = game.tr("\n", ' ').split(/\d+\./).collect(&:strip)[1..-1].map(&:split).flatten
      @moves.delete_at(@moves.size - 1) if @moves.last.match?(/(0-1)|(1-0)|(1\/2)|(1\/2-1\/2)|(\*)/)
      @moves.each do |m|
        raise Chess::InvalidPgnFormatError.new if m !~ MOVE_REGEXP && m !~ SHORT_CASTLING_REGEXP && m !~ LONG_CASTLING_REGEXP
      end
      Chess::Game.new(@moves) if check_moves
      return self
    end

    # PGN to string.
    def to_s
      s = ''
      TAGS.each do |t|
        tag = instance_variable_defined?(:"@#{t}") ? instance_variable_get(:"@#{t}") : ''
        s << "[#{t.capitalize} \"#{tag}\"]\n"
      end
      s << "\n"
      m = ''
      @moves.each_with_index do |move, i|
        m << "#{(i / 2) + 1}. " if i.even?
        m << "#{move} "
      end
      m << @result unless @result.nil?
      s << m.gsub(/(.{1,78})(?: +|$)\n?|(.{78})/, "\\1\\2\n")
    end

    # Write PGN to file.
    # @param [String] filename The path of the PGN file.
    def write(filename)
      File.write(filename, self.to_s)
    end

    # # @!visibility private
    # alias old_date= date=

    # Set the date tag.
    def date=(value)
      @date =
        if value.is_a?(Time)
          value.strftime('%Y.%m.%d')
        else
          value
        end
    end
  end
end
