require 'rack'

module Goliath
  module Rack
    module Formatters
      # A XML formatter. Attempts to convert your data into
      # an XML document.
      #
      # @example
      #   use Goliath::Rack::Formatters::XML
      class XML
        include Goliath::Rack::AsyncMiddleware

        def initialize(app, opts = {})
          @app = app
          @opts = opts
          @opts[:root] ||= 'results'
          @opts[:item] ||= 'item'
        end

        def post_process(env, status, headers, body)
          if xml_response?(headers)
            body = [to_xml(body)]
          end
          [status, headers, body]
        end

        def xml_response?(headers)
          headers['Content-Type'] =~ %r{^application/xml}
        end

        def to_xml(content, fragment=false)
          xml_string = ''
          xml_string << xml_header(@opts[:root]) unless fragment

          xml_string << case(content.class.to_s)
          when "Hash"   then hash_to_xml(content)
          when "Array"  then array_to_xml(content, @opts[:item])
          when "String" then string_to_xml(content)
          else string_to_xml(content)
          end

          xml_string << xml_footer(@opts[:root]) unless fragment
          xml_string
        end

        def string_to_xml(content)
          ::Rack::Utils.escape_html(content.to_s)
        end

        def hash_to_xml(content)
          xml_string = ''
          if content.key?('meta')
            xml_string += xml_item('meta', content['meta'])
            content.delete('meta')
          end

          content.each_pair { |key, value| xml_string << xml_item(key, value) }
          xml_string
        end

        def array_to_xml(content, item='item')
          xml_string = ''
          content.each { |value| xml_string << xml_item(item, value) }
          xml_string
        end

        def xml_header(root)
          "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<#{root}>"
        end

        def xml_footer(root)
          "</#{root}>"
        end

        def xml_item(key, value)
          "<#{key}>#{to_xml(value, true)}</#{key}>\n"
        end
      end
    end
  end
end