module RDF
  ##
  # An RDF parser.
  #
  # @example Iterating over known RDF reader classes
  #   RDF::Reader.each { |klass| puts klass.name }
  #
  # @example Obtaining an RDF reader class
  #   RDF::Reader.for(:ntriples)     #=> RDF::NTriples::Reader
  #   RDF::Reader.for("spec/data/test.nt")
  #   RDF::Reader.for(:file_name      => "spec/data/test.nt")
  #   RDF::Reader.for(:file_extension => "nt")
  #   RDF::Reader.for(:content_type   => "text/plain")
  #
  # @example Instantiating an RDF reader class
  #   RDF::Reader.for(:ntriples).new($stdin) { |reader| ... }
  #
  # @example Parsing RDF statements from a file
  #   RDF::Reader.open("spec/data/test.nt") do |reader|
  #     reader.each_statement do |statement|
  #       puts statement.inspect
  #     end
  #   end
  #
  # @example Parsing RDF statements from a string
  #   data = StringIO.new(File.read("spec/data/test.nt"))
  #   RDF::Reader.for(:ntriples).new(data) do |reader|
  #     reader.each_statement do |statement|
  #       puts statement.inspect
  #     end
  #   end
  #
  # @abstract
  # @see RDF::Format
  # @see RDF::Writer
  class Reader
    extend  ::Enumerable
    include ::Enumerable

    ##
    # Enumerates known RDF reader classes.
    #
    # @yield  [klass]
    # @yieldparam [Class]
    # @return [Enumerator]
    def self.each(&block)
      @@subclasses.each(&block)
    end

    ##
    # Finds an RDF reader class based on the given criteria.
    #
    # @overload for(format)
    #   Finds an RDF reader class based on a symbolic name.
    #
    #   @param  [Symbol] format
    #   @return [Class]
    #
    # @overload for(filename)
    #   Finds an RDF reader class based on a file name.
    #
    #   @param  [String] filename
    #   @return [Class]
    #
    # @overload for(options = {})
    #   Finds an RDF reader class based on various options.
    #
    #   @param  [Hash{Symbol => Object}] options
    #   @option options [String, #to_s]   :file_name      (nil)
    #   @option options [Symbol, #to_sym] :file_extension (nil)
    #   @option options [String, #to_s]   :content_type   (nil)
    #   @return [Class]
    #
    # @return [Class]
    def self.for(options = {})
      if format = Format.for(options)
        format.reader
      end
    end

    ##
    # Retrieves the RDF serialization format class for this writer class.
    #
    # @return [Class]
    def self.format(klass = nil)
      if klass.nil?
        Format.each do |format|
          if format.reader == self
            return format
          end
        end
        nil # not found
      end
    end

    class << self
      alias_method :format_class, :format
    end

    ##
    # @param  [String] filename
    # @option options [Symbol] :format (:ntriples)
    # @yield  [reader]
    # @yieldparam [Reader]
    # @raise  [FormatError] if no reader available for the specified format
    def self.open(filename, options = {}, &block)
      Kernel.open(filename, 'rb') do |file|
        if reader = self.for(options[:format] || filename)
          reader.new(file, options, &block)
        else
          raise FormatError.new("unknown RDF format: #{options[:format] || filename}")
        end
      end
    end

    ##
    # @param  [IO, File, String] input
    # @yield  [reader]
    # @yieldparam [Reader] reader
    def initialize(input = $stdin, options = {}, &block)
      @options = options
      @nodes   = {}
      @input   = case input
        when String then StringIO.new(input)
        else input
      end
      block.call(self) if block_given?
    end

    ##
    # @yield  [statement]
    # @yieldparam [Statement]
    # @return [Reader]
    def each(&block)
      each_statement(&block)
    end

    ##
    # @yield  [statement]
    # @yieldparam [Statement]
    # @return [Enumerator]
    def each_statement(&block)
      begin
        loop { block.call(read_statement) }
      rescue EOFError => e
      end
    end

    ##
    # @yield  [triple]
    # @yieldparam [Array(Value)]
    # @return [Enumerator]
    def each_triple(&block)
      begin
        loop { block.call(*read_triple) }
      rescue EOFError => e
      end
    end

    protected

      ##
      # @raise [NotImplementedError] unless implemented in subclass
      def read_statement
        Statement.new(*read_triple)
      end

      ##
      # @raise [NotImplementedError] unless implemented in subclass
      def read_triple
        raise NotImplementedError
      end

      ##
      # @raise [ReaderError]
      def fail_subject
        raise RDF::ReaderError, "expected subject in #{@input.inspect} line #{lineno}"
      end

      ##
      # @raise [ReaderError]
      def fail_predicate
        raise RDF::ReaderError, "expected predicate in #{@input.inspect} line #{lineno}"
      end

      ##
      # @raise [ReaderError]
      def fail_object
        raise RDF::ReaderError, "expected object in #{@input.inspect} line #{lineno}"
      end

    private

      @@subclasses = [] # @private

      def self.inherited(child) #:nodoc:
        @@subclasses << child
        super
      end

      def lineno
        @input.lineno
      end

      def readline
        @line = @input.readline.chomp
      end

      def strip!
        @line.strip!
      end

      def blank?
        @line.nil? || @line.empty?
      end

      def match(pattern)
        if @line =~ pattern
          result, @line = $1, $'.lstrip
          result || true
        end
      end

  end

  class ReaderError < IOError; end
end