require 'oai'
require 'libis/tools/extend/hash'
require 'libis/services/service_error'

module Libis
  module Services
    class Oai
      include OAI::XPath

      class Query
        attr_accessor :from, :until, :set, :metadata_prefix

        def intialize(metadata_prefix = 'oai_dc')
          @from = @until = @set = nil
          @metadata_prefix = metadata_prefix
        end

        def to_hash
          { from: @from, until: @until, metadata_prefix: @metadata_prefix, set: @set }.cleanup
        end

      end

      def initialize(url, options = {})
        options[:debug] = true
        @oai_client = OAI::Client.new(url, options)
      end

      def identify
        do_oai_request(:identify)
      end

      def sets(token = nil)
        options = { resumption_token: token }
        do_oai_request(:list_sets, options)
      end

      def metdata_formats(identifier = nil)
        do_oai_request(:list_metadata_formats, {identifier: identifier})
      end

      def identifiers(token = nil, query = Query.new)
        options = token ? {resumption_token: token} : query.to_hash
        do_oai_request(:list_identifiers, options)
      end

      def records(token = nil, query = Query.new)
        options = token ? {resumption_token: token} : query.to_hash
        do_oai_request(:list_records, options)
      end

      def record(identifier, metadata_prefix)
        do_oai_request(:get_record, identifier: identifier, metadata_prefix: metadata_prefix)
      end

      private

      def do_oai_request(method, options = {})
        response = @oai_client.send(method, options.cleanup)
        object_to_hash(response)
      rescue OAI::Exception => e
        raise Libis::Services::ServiceError, "OAI Error: #{e.code} - #{e.message}"
      end

      def object_to_hash(obj)
        case obj
          when Array
            obj.map { |x| object_to_hash(x) }
          when Hash
            obj.each_with_object({}) do |k,v,h|
              h[k] = object_to_hash(v)
            end
          when REXML::Element
            obj.to_s
          when OAI::Response, OAI::Header, OAI::Record, OAI::MetadataFormat, OAI::Set
            result = obj.instance_variables.map do |x|
              x[1..-1].to_sym
            end.select do |x|
              ![:_source, :doc, :resumption_block].include? x
            end.each_with_object({}) do |x, h|
              h[x] = object_to_hash obj.send(x)
            end
            if obj.methods.include?(:entries)
              result[:entries] = obj.entries.map do |entry|
                object_to_hash(entry)
              end
            end
            result
          else
            obj
        end
      end

    end
  end
end