# respond_to (The MIT License)

# Permission is hereby granted, free of charge, to any person obtaining a copy of this software
# and associated documentation files (the 'Software'), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge, publish, distribute,
# sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE

require 'rack/accept'

module Rack

  module RespondTo

    # This method is triggered after this helper is registred
    # within Sinatra.
    # We need to overide the default render method to supply correct path to the
    # template, since Sinatra is by default looking in the current __FILE__ path
    def self.registered(app)
      app.use Rack::Accept
      app.use Rack::MediaType
      app.helpers Rack::RespondTo::Helpers
      app.class_eval do
        alias :render_without_format :render
        def render(*args, &block)
          begin
            assumed_layout = args[1] == :layout
            args[1] = "#{args[1]}.#{@media_type}".to_sym if args[1].is_a?(::Symbol)
            render_without_format(*args, &block)
          rescue Errno::ENOENT => e
            raise "ERROR: Missing template: #{args[1]}.#{args[0]}" unless assumed_layout
            raise e
          end
        end
        private :render
      end
    end

    module Helpers

      # This code was inherited from respond_to plugin
      # http://github.com/cehoffman/sinatra-respond_to
      #
      # This method is used to overide the default content_type returned from
      # rack-accept middleware.
      def self.included(klass)
        klass.class_eval do
          alias_method :original_content_type, :content_type

          def content_type(*args)
            original_content_type(*args)
            request.env['rack-accept.formats'] = { args.first.to_sym => 1 }
            response['Content-Type']
          end

        end
      end

      def accepting_formats
        request.env['rack-accept.formats']
      end

      def static_file?(path)
        public_dir = File.expand_path(settings.public)
        path = File.expand_path(File.join(public_dir, unescape(path)))
        path[0, public_dir.length] == public_dir && File.file?(path)
      end

      def respond_to(&block)
        wants = {}
        def wants.method_missing(type, *args, &handler)
          self[type] = handler
        end
        yield wants

        if request.env["SCRIPT_NAME"].include?("cimi") || Deltacloud.default_frontend.name == :cimi
          #when cimi and neither json or xml defined... default to _something_  -  json?
          if ([:json, :xml] & accepting_formats.keys).empty?
            request.env['rack-accept.formats'] = {:json=>0}
          end
          @media_type = (accepting_formats.has_key?(:xml) ? [:xml, accepting_formats[:xml]] : nil)
        end if Deltacloud.respond_to? :default_frontend

        @media_type ||= accepting_formats.to_a.sort { |a,b| a[1]<=>b[1] }.reverse.select do |format, priority|
          wants.keys.include?(format) == true
        end.first
        if @media_type and @media_type.kind_of? Symbol
          @media_type = [ @media_type ]
        end
        if @media_type and @media_type[0]
          @media_type = @media_type[0]
          if  Rack::MediaType::ACCEPTED_MEDIA_TYPES[@media_type]
            headers 'Content-Type' => Rack::MediaType::ACCEPTED_MEDIA_TYPES[@media_type][:return]
          else
            headers 'Content-Type' => 'application/xml'
          end
          wants[@media_type.to_sym].call if wants[@media_type.to_sym]
        else
          headers 'Content-Type' => nil
          status 406
        end
      end
    end
  end

  class MediaType < Sinatra::Base

    include Rack::RespondTo::Helpers

    # Define supported media types here
    # The :return key stands for content-type which will be returned
    # The :match key stands for the matching Accept header
    ACCEPTED_MEDIA_TYPES = {
      :xml => { :return => 'application/xml', :match => ['application/xml', 'text/xml'] },
      :json => { :return => 'application/json', :match => ['application/json'] },
      :html => { :return => 'text/html', :match => ['application/xhtml+xml', 'text/html', '*/*'] },
      :png => { :return => 'image/png', :match => ['image/png'] },
      :gv => { :return => 'application/ghostscript', :match => ['application/ghostscript'] }
    } unless defined?(ACCEPTED_MEDIA_TYPES)

    def call(env)
      accept, index = env['rack-accept.request'], {}

      # Skip everything when 'format' parameter is set in URL
      if env['rack.request.query_hash'] and env['rack.request.query_hash']["format"]
         media_type = case env['rack.request.query_hash']["format"]
            when 'html' then :html
            when 'xml' then :xml
            when 'json' then :json
            when 'gv' then :gv
            when 'png' then :png
          end
        index[media_type] = 1 if media_type
      else
        # Sort all requested media types in Accept using their 'q' values
        sorted_media_types = accept.media_type.qvalues.to_a.sort{ |a,b| a[1]<=>b[1] }.collect { |t| t.first }
        # If Accept header is missing or is empty, fallback to XML format
        sorted_media_types << 'application/xml' if sorted_media_types.empty?
        # Choose the right format with the media type according to the priority
        ACCEPTED_MEDIA_TYPES.each do |format, definition|
          definition[:match].each do |mt|
            break if index[format] = sorted_media_types.index(mt)
          end
        end
        # Reject formats with no/nil priority
        index.reject! { |format, priority| not priority }
      end

      #puts sorted_media_types.inspect
      #puts index.inspect

      # If after all we don't have any matching format assume that client has
      # requested unknown/wrong media type and throw an 406 error with no body
      if index.keys.empty?
        status, headers, response = 406, {}, ""
      else
        env['rack-accept.formats'] = index
        status, headers, response = @app.call(env)
      end
      [status, headers, response]
    end

  end

end

