require "rack/jquery_ui/version"
require "rack/jquery/helpers"

# @see http://rack.github.io/
module Rack

  # jQuery CDN script tags and fallback in one neat package.
  class JQueryUI
    include JQuery::Helpers

    # The usual file name.
    JQUERY_UI_FILE_NAME = "jquery-ui.min.js"


    # Namespaced for convenience, to help with checking
    # which CDN supports what.
    module CDNs

      # Script tags for the Media Temple CDN
      MEDIA_TEMPLE = "http://code.jquery.com/ui/#{JQUERY_UI_VERSION}/jquery-ui.js"
  
      # Script tags for the Google CDN
      GOOGLE = "//ajax.googleapis.com/ajax/libs/jqueryui/#{JQUERY_UI_VERSION}/jquery-ui.min.js"
  
      # Script tags for the Microsoft CDN
      MICROSOFT = "//ajax.aspnetcdn.com/ajax/jquery.ui/#{JQUERY_UI_VERSION}/jquery-ui.min.js"
  
  
      # Script tags for the Cloudflare CDN
      CLOUDFLARE = "//cdnjs.cloudflare.com/ajax/libs/jqueryui/#{JQUERY_UI_VERSION}/jquery-ui.min.js"
    end

    # This javascript checks if the jQuery-UI object has loaded. If not, that most likely means the CDN is unreachable, so it uses the local minified jQuery.
    FALLBACK = <<STR
<script type="text/javascript">
  !window.jQuery.ui && document.write(unescape("%3Cscript src='/js/jquery-ui/#{JQUERY_UI_VERSION}/#{JQUERY_UI_FILE_NAME}' type='text/javascript'%3E%3C/script%3E"))
</script>
STR


    # @param [Symbol] organisation Choose which CDN to use, either :google, :microsoft or :media_temple
    # @return [String] The HTML script tags to get the CDN.
    def self.cdn( organisation=:google, options={} )
      raise = (opt = options[:raise]).nil? ?
                raise? :
                opt
      script = if organisation === :media_temple         
          CDNs::MEDIA_TEMPLE
        elsif organisation === :microsoft
          CDNs::MICROSOFT
        elsif organisation === :cloudflare
          CDNs::CLOUDFLARE
        else
          CDNs::GOOGLE
      end
      "<script src='#{script}'></script>\n#{FALLBACK}"
    end


    # Default options hash for the middleware.
    DEFAULT_OPTIONS = {
      :http_path  => "/js/jquery-ui/#{JQUERY_UI_VERSION}",
      :raise      =>  false
    }


    # Whether to raise an exception if the chosen CDN
    # does not support the jQuery UI version this library is using
    # @param [TrueClass] bool
    def self.raise=( bool )
      @raise = bool
    end


    # @see raise=
    # @return [TrueClass]
    def self.raise?
      @raise = false if @raise.nil?
      @raise
    end


    # @param [#call] app
    # @param [Hash] options
    # @option options [String] :http_path If you wish the jQuery fallback route to be "/js/jquery-ui/1.10.1/jquery-ui.min.js" (or whichever version this is at) then do nothing, that's the default. If you want the path to be "/assets/javascripts/jquery-ui/1.10.1/jquery-ui.min.js" then pass in `:http_path => "/assets/javascripts/#{Rack::JQueryUI::JQUERY_UI_VERSION}".
    # @note ***Don't leave out the version number!***. The scripts provided by jQuery don't contain the version in the filename like the jQuery scripts do, which means that organising them and sending the right headers back is bound to go wrong unless you put the version number somewhere in the route. You have been warned!
    # @option options [TrueClass] :raise If one of the CDNs does not support then raise an error if it is chosen. Defaults to false.
    # @example
    #   # The default:
    #   use Rack::JQueryUI
    #   # With a different route to the fallback:
    #   use Rack::JQueryUI, :http_path => "/assets/js/#{Rack::JQueryUI::JQUERY_UI_VERSION}"
    def initialize( app, options={} )
      @app, @options  = app, DEFAULT_OPTIONS.merge(options)
      @http_path_to_jquery = ::File.join @options[:http_path], JQUERY_UI_FILE_NAME
      self.class.raise = @options[:raise]
    end


    # @param [Hash] env Rack request environment hash.
    def call( env )
      dup._call env
    end


    # For thread safety
    # @param (see #call)
    def _call( env )
      request = Rack::Request.new(env.dup)
      if request.path_info == @http_path_to_jquery
        response = Rack::Response.new
        # for caching
        response.headers.merge! caching_headers( "#{JQUERY_UI_FILE_NAME}-#{JQUERY_UI_VERSION}", JQUERY_UI_VERSION_DATE)

        # There's no need to test if the IF_MODIFIED_SINCE against the release date because the header will only be passed if the file was previously accessed by the requester, and the file is never updated. If it is updated then it is accessed by a different path.
        if request.env['HTTP_IF_MODIFIED_SINCE']
          response.status = 304
        else
          response.status = 200
          response.write ::File.read( ::File.expand_path "../../../vendor/assets/javascripts/jquery-ui/#{JQUERY_UI_VERSION}/#{JQUERY_UI_FILE_NAME}", __FILE__)
        end
        response.finish
      else
        @app.call(env)
      end
    end # call

  end
end