module Lrama
  class Lexer
    class Location
      attr_reader :grammar_file, :first_line, :first_column, :last_line, :last_column

      def initialize(grammar_file:, first_line:, first_column:, last_line:, last_column:)
        @grammar_file = grammar_file
        @first_line = first_line
        @first_column = first_column
        @last_line = last_line
        @last_column = last_column
      end

      def ==(other)
        self.class == other.class &&
        self.grammar_file == other.grammar_file &&
        self.first_line == other.first_line &&
        self.first_column == other.first_column &&
        self.last_line == other.last_line &&
        self.last_column == other.last_column
      end

      def partial_location(left, right)
        offset = -first_column
        new_first_line = -1
        new_first_column = -1
        new_last_line = -1
        new_last_column = -1

        _text.each.with_index do |line, index|
          new_offset = offset + line.length + 1

          if offset <= left && left <= new_offset
            new_first_line = first_line + index
            new_first_column = left - offset
          end

          if offset <= right && right <= new_offset
            new_last_line = first_line + index
            new_last_column = right - offset
          end

          offset = new_offset
        end

        Location.new(
          grammar_file: grammar_file,
          first_line: new_first_line, first_column: new_first_column,
          last_line: new_last_line, last_column: new_last_column
        )
      end

      def to_s
        "#{path} (#{first_line},#{first_column})-(#{last_line},#{last_column})"
      end

      def generate_error_message(error_message)
        <<~ERROR.chomp
          #{path}:#{first_line}:#{first_column}: #{error_message}
          #{line_with_carets}
        ERROR
      end

      def line_with_carets
        <<~TEXT
          #{text}
          #{carets}
        TEXT
      end

      private

      def path
        grammar_file.path
      end

      def blanks
        (text[0...first_column] or raise "#{first_column} is invalid").gsub(/[^\t]/, ' ')
      end

      def carets
        blanks + '^' * (last_column - first_column)
      end

      def text
        @text ||= _text.join("\n")
      end

      def _text
        @_text ||=begin
          range = (first_line - 1)...last_line
          grammar_file.lines[range] or raise "#{range} is invalid"
        end
      end
    end
  end
end
