# Simple note, accept header parsing was looked at but deamed
# too much of an irregularity to deal with.  Problems with the header
# differences from IE, Firefox, Safari, and every other UA causes
# problems with the expected output.  The general expected behavior
# would be serve html when no extension provided, but most UAs say
# they will accept application/xml with out a quality indicator, meaning
# you'd get the xml block served insead.  Just plain retarded, use the
# extension and you'll never be suprised.

module Sinatra
  module RespondTo
    class UnhandledFormat < Sinatra::NotFound; end
    class MissingTemplate < Sinatra::NotFound
      def code; 500 end
    end

    TEXT_MIME_TYPES = [:txt, :html, :js, :json, :xml, :rss, :atom, :css, :asm, :c, :cc, :conf,
                       :csv, :cxx, :diff, :dtd, :f, :f77, :f90, :for, :gemspec, :h, :hh, :htm,
                       :log, :mathml, :mml, :p, :pas, :pl, :pm, :py, :rake, :rb, :rdf, :rtf, :ru,
                       :s, :sgm, :sgml, :sh, :svg, :svgz, :text, :wsdl, :xhtml, :xsl, :xslt, :yaml,
                       :yml, :ics]

    def self.registered(app)
      app.helpers RespondTo::Helpers

      app.set :default_charset, 'utf-8' unless app.respond_to?(:default_charset)
      app.set :default_content, :html unless app.respond_to?(:default_content)
      app.set :assume_xhr_is_js, true unless app.respond_to?(:assume_xhr_is_js)

      # We remove the trailing extension so routes
      # don't have to be of the style
      #
      #   get '/resouce.:format'
      #
      # They can instead be of the style
      #
      #   get '/resource'
      #
      # and the format will automatically be available in as <tt>format</tt>
      app.before do
        unless options.static? && options.public? && ["GET", "HEAD"].include?(request.request_method) && static_file?(unescape(request.path_info))
          request.path_info.gsub! %r{\.([^\./]+)$}, ''
          format $1 || options.default_content

          # For the oh so common case of actually wanting Javascript from an XmlHttpRequest
          format :js if request.xhr? && options.assume_xhr_is_js?
        end
      end

      # Replace all routes that have an ending extension with one that doesn't
      # Most generally a fix for the __sinatra__ routes in development
      # app.routes.each_pair do |verb, subroutes|
      #   subroutes.each do |subroute|
      #     subroute[0] = Regexp.new(subroute[0].source.gsub(/\\\.[^\.\/]+\$$/, '$'))
      #   end
      # end

     app.configure :development do
        # Very, very, very hackish but only for development at least
        # Modifies the regex matching /__sinatra__/:image.png to not have the extension
        ["GET", "HEAD"].each do |verb|
          app.routes[verb][1][0] = Regexp.new(app.routes[verb][1][0].source.gsub(/\\\.[^\.\/]+\$$/, '$'))
        end

        app.error UnhandledFormat do
          content_type :html, :charset => 'utf-8'

          (<<-HTML).gsub(/^ {10}/, '')
          <!DOCTYPE html>
          <html>
          <head>
            <style type="text/css">
            body { text-align:center;font-family:helvetica,arial;font-size:22px;
              color:#888;margin:20px}
            #c {margin:0 auto;width:500px;text-align:left}
            </style>
          </head>
          <body>
            <h2>Sinatra doesn't know this ditty.</h2>
            <img src='/__sinatra__/404.png'>
            <div id="c">
              Try this:
              <pre>#{request.request_method.downcase} '#{request.path_info}' do\n  respond_to do |wants|\n    wants.#{format} { "Hello World" }\n  end\nend</pre>
            </div>
          </body>
          </html>
          HTML
        end

        app.error MissingTemplate do
          content_type :html, :charset => 'utf-8'

          engine = request.env['sinatra.error'].message[/\.([^\.]+)$/, 1]
          path = request.path_info[/([^\/]+)$/, 1]
          path = "root" if path.nil? || path.empty?

          layout = case engine
                   when 'haml' then "!!!\n%html\n  %body= yield"
                   when 'erb' then "<html>\n  <body>\n    <%= yield %>\n  </body>\n</html>"
                   when 'builder' then "builder do |xml|\n  xml << yield\nend"
                   end

          layout = "<small>app.html.#{engine}</small>\n<pre>#{escape_html(layout)}</pre>" if layout

          (<<-HTML).gsub(/^ {10}/, '')
          <!DOCTYPE html>
          <html>
          <head>
            <style type="text/css">
            body { text-align:center;font-family:helvetica,arial;font-size:22px;
              color:#888;margin:20px}
            #c {margin:0 auto;width:500px;text-align:left;}
            small {float:right;clear:both;}
            pre {clear:both;}
            </style>
          </head>
          <body>
            <h2>Sinatra can't find #{request.env['sinatra.error'].message}</h2>
            <img src='/__sinatra__/500.png'>
            <div id="c">
              Try this:<br />
              #{layout if layout}
              <small>#{path}.html.#{engine}</small>
              <pre>Hello World!</pre>
              <small>application.rb</small>
              <pre>#{request.request_method.downcase} '#{request.path_info}' do\n  respond_to do |wants|\n    wants.#{engine == 'builder' ? 'xml' : 'html'} { #{engine} :#{path}#{",\n#{' '*32}layout => :app" if layout} }\n  end\nend</pre>
            </div>
          </body>
          </html>
          HTML
        end

      end

      app.class_eval do
        private
          def render_with_format(*args)
            args[1] = "#{args[1]}.#{format}".to_sym
            render_without_format *args
          rescue Errno::ENOENT
            raise MissingTemplate, "#{args[1]}.#{args[0]}"
          end
          alias_method :render_without_format, :render
          alias_method :render, :render_with_format

          def lookup_layout_with_format(*args)
            args[1] = "#{args[1]}.#{format}".to_sym if args
            lookup_layout_without_format *args
          end
          alias_method :lookup_layout_without_format, :lookup_layout
          alias_method :lookup_layout, :lookup_layout_with_format
      end
    end

    module Helpers
      def format(val=nil)
        request.env['sinatra.respond_to.format'] = val.to_sym unless val.nil?
        request.env['sinatra.respond_to.format']
      end

      def static_file?(path)
        return false unless path =~ /.*[^\/]$/
        public_dir = File.expand_path(options.public)
        path = File.expand_path(File.join(public_dir, unescape(request.path_info)))
        return false if path[0, public_dir.length] != public_dir
        return false unless File.file?(path)
        true
      end

      def respond_to(&block)
        wants = {}
        def wants.method_missing(type, *args, &block)
          Sinatra::Base.send(:fail, "Unknown media type for respond_to: #{type}\nTry registering the extension with a mime type") if Sinatra::Base.media_type(type).nil?
          self[type] = block
        end

        yield wants

        handler = wants[format]
        raise UnhandledFormat  if handler.nil?

        opts = [format]
        opts << {:charset => options.default_charset} if TEXT_MIME_TYPES.include? format && response['Content-Type'] !~ /charset=/

        content_type *opts

        handler.call
      end
    end
  end

  Sinatra::Application.register RespondTo
end