require 'sproutcore/jsdoc'

module SproutCore
  module Merb
   
    # A subclass of this controller handles all incoming requests for the location it is
    # mounted at.  For index.html requests, it will rebuild the html file everytime it is
    # requested if you are in development mode.  For all other requests, it will build the
    # resource one time and then return the file if it already exists.
    class BundleController < ::Merb::Controller
      
      def self.library_for_class(klass)
        (@registered_libraries ||= {})[klass]
      end
      
      def self.register_library_for_class(library, klass)
        (@registered_libraries ||= {})[klass] = library
      end
        
      # Entry point for all requests routed through the SproutCore controller.  Example
      # the request path to determine which bundle should handle the request.
      def main

        # Before we do anything, set the build_mode for the bundles.  This shouldn't change
        # during execution, but if we set this during the router call, the Merb.environment
        # is sometimes not ready yet.
        if ::Merb.environment.to_sym == :production
          Bundle.build_mode = :production
        else
          ::SproutCore.logger.level = Logger::DEBUG
        end
        
        # Make sure we can service this with a bundle
        raise(NotFound, "No SproutCore Bundle registered at this location.") if current_bundle.nil?

        # Check for a few special urls that need to be rewritten
        url = request.path
        puts "BEFORE URL: #{url} current_bundle: #{current_bundle.bundle_name}"
        if request.method == :get 
          url = rewrite_bundle_if(url, /^#{current_bundle.index_root}\/-tests/, :sc_test_runner)
          url = rewrite_bundle_if(url, /^#{current_bundle.index_root}\/-docs/, :sc_docs)
        end

        # If we are in development mode, reload bundle first
        current_bundle.reload! if current_bundle.build_mode == :development

        puts "AFTER URL: #{url} current_bundle: #{current_bundle.bundle_name}"

        # Get the normalized URL for the requested resource
        url = current_bundle.normalize_url(url)

        
        # Check for a few special urls for built-in services and route them off
        case url
        when "#{current_bundle.url_root}/-tests/index.js"
          ret = handle_test(url)
        when "#{current_bundle.index_root}/-docs"
          ret = (request.method == :post) ? handle_doc(url) : handle_resource(url)

        when "#{current_bundle.url_root}/-docs"
          ret = (request.method == :post) ? handle_doc(url) : handle_resource(url)

        else
          ret = handle_resource(url)
        end
        
        # Done!
        return ret
      end
      
      # Invoked whenever you request a regular resource
      def handle_resource(url)
        
        # Get the entry for the resource.  
        entry = current_bundle.entry_for_url(url, :hidden => :include)
        raise(NotFound, "No matching entry in #{current_bundle.bundle_name}") if entry.nil?

        
        build_path = entry.build_path

        # Found an entry, build the resource.  If the resource has already been built, this
        # will not do much.  If this the resource is an index.html file, force the build.
        is_index = /\/index\.html$/ =~ url
        puts "building: #{url} - is_index: #{is_index}"
        
        current_bundle.build_entry(entry, :force => is_index, :hidden => :include)

        # Move to final build path if necessary
        if (build_path != entry.build_path) && File.exists?(entry.build_path)
          FileUtils.mv(entry.build_path, build_path)
        end

        # And return the file.  Set the content type using a mime-map borroed from Rack.
        headers['Content-Type'] = entry.content_type
        headers['Content-Length'] = File.size(build_path).to_s
        ret = File.open(build_path)
        
        
        # In development mode only, immediately delete built composite resources.  We want 
        # each request to come directly to us.
        if (current_bundle.build_mode == :development) && (!entry.cacheable?)
          FileUtils.rm(build_path)
        end
        
        return ret
      end

      # Returns JSON containing all of the tests
      def handle_test(url)
        test_entries = current_bundle.entries_for(:test, :hidden => :include)
        content_type = :json
        ret = test_entries.map do |entry| 
          { :name => entry.filename.gsub(/^tests\//,''), :url => "#{entry.url}?#{entry.timestamp}" }
        end
        return ret.to_json
      end

      # If you POST to this URL, regenerates the docs.
      def handle_doc(url)
        JSDoc.generate :bundle => current_bundle
        return "OK"
      end
      
      ######################################################################
      ## Support Methods
      ##
      
      # Returns the library for this class
      def library
        ::SproutCore::Merb::BundleController.library_for_class(self.class)
      end
      
      
      # Returns the bundle for this request
      def current_bundle
        return @current_bundle unless @current_bundle.nil?

        # Tear down the URL, looking for the first bundle 
        bundle_map = library.bundles_grouped_by_url
        url = request.path.split('/')
        ret = nil
        while url.size > 0 && ret.nil?
          ret = bundle_map[url.join('/')]
          url.pop
        end
        
        # Try root path if nothing found
        ret = bundle_map['/'] if ret.nil?
        
        # Return
        return (@current_bundle = ret)
      end

      # This method is used to redirect certain urls to an alternate bundle.  If the 
      # match phrase matches the url, then both the url we use to fetch resources and the
      # current_bundle will be swapped out.
      #
      # ===== Params
      # url<String>:: The url to check
      # match<Regex>:: The pattern to match and optionally later replace
      # new_bundle_name<Symbol>:: The name of the new bundle to swap in if matched
      #
      # ===== Returns
      # The rewritten url.  May also change the value of current_bundle
      #
      def rewrite_bundle_if(url, match, new_bundle_name)
        return url unless match =~ url
        new_bundle = library.bundle_for(new_bundle_name)
        if new_bundle
          url = url.gsub(match, new_bundle.index_root)
          @current_bundle = new_bundle
        end
        return url
      end
      
    end
    
  end
end