module Hobo::Dryml

  class TemplateHandler < ActionView::TemplateHandler
    
    def compile(*args)
      # Ignore - we handle compilation ourselves
    end
    
    # Pre Rails 2.2
    def render(template)
      renderer = Hobo::Dryml.page_renderer_for_template(@view, template.locals.keys, template)
      this = @view.instance_variable_set("@this", @view.controller.send(:dryml_context) || template.locals[:this])
      s = renderer.render_page(this, template.locals)
      # Important to strip whitespace, or the browser hangs around for ages (FF2)
      s.strip
    end
    
    def render_for_rails22(template, view, local_assigns)
      renderer = Hobo::Dryml.page_renderer_for_template(view, local_assigns.keys, template)
      this = view.controller.send(:dryml_context) || local_assigns[:this]
      @view._?.instance_variable_set("@this", this)
      s = renderer.render_page(this, local_assigns)

      # Important to strip whitespace, or the browser hangs around for ages (FF2)
      s.strip
    end

  end

end

module ActionController

  class Base

    def dryml_context
      @this
    end

    def dryml_fallback_tag(tag_name)
      @dryml_fallback_tag = tag_name
    end


    def call_dryml_tag(tag, options={})
      @template.send(:_evaluate_assigns_and_ivars)

      # TODO: Figure out what this bit is all about :-)
      if options[:with]
        @this = options[:with] unless options[:field]
      else
        options[:with] = dryml_context
      end

      Hobo::Dryml.render_tag(@template, tag, options)
    end


    # TODO: This is namespace polution, should be called render_dryml_tag
    def render_tag(tag, attributes={}, options={})
      text = call_dryml_tag(tag, attributes)
      text && render({:text => text, :layout => false }.merge(options))
    end
    
    # DRYML fallback tags -- monkey patch this method to attempt to render a tag if there's no template
    def render_for_file_with_dryml(template, status = nil, layout = nil, locals = {})
      # in rails 2.2, "template" is actually "template_path"
      
      # if we're passed a MissingTemplateWrapper, see if there's a
      # dryml tag that will render the page
      if template.respond_to? :original_template_path
        # this is the Rails 2.3 path
        tag_name = @dryml_fallback_tag || "#{File.basename(template.original_template_path).dasherize}-page"
        
        text = call_dryml_tag(tag_name)
        if text
          return render_for_text(text, status)
        else
          template.raise_wrapped_exception
        end
      else
        begin
          result = render_for_file_without_dryml(template, status, layout, locals)
        rescue ActionView::MissingTemplate => ex
          # this is the Rails 2.2 path
          tag_name = @dryml_fallback_tag || "#{File.basename(template).dasherize}-page"
          
          text = call_dryml_tag(tag_name)
          if text
            return render_for_text(text, status)
          else
            raise ex
          end
        end
      end
    end
    alias_method_chain :render_for_file, :dryml
      
  end
end

class ActionView::Template
  
  def render_with_dryml(view, local_assigns = {})
    if handler == Hobo::Dryml::TemplateHandler
      render_dryml(view, local_assigns)
    else
      render_without_dryml(view, local_assigns)
    end
  end
  alias_method_chain :render, :dryml
  
  # We've had to copy a bunch of logic from Renderable#render, because we need to prevent Rails
  # from trying to compile our template. DRYML templates are each compiled as a class, not just a method,
  # so the support for compiling templates that Rails provides is innadequate.
  def render_dryml(view, local_assigns = {})
    if view.instance_variable_defined?(:@_render_stack)
      # Rails 2.2
      stack = view.instance_variable_get(:@_render_stack)
      stack.push(self)
 
      # This is only used for TestResponse to set rendered_template
      unless is_a?(ActionView::InlineTemplate) || view.instance_variable_get(:@_first_render)
        view.instance_variable_set(:@_first_render, self)
      end

      view.send(:_evaluate_assigns_and_ivars)
      view.send(:_set_controller_content_type, mime_type) if respond_to?(:mime_type)
 
      result = Hobo::Dryml::TemplateHandler.new.render_for_rails22(self, view, local_assigns)
 
      stack.pop
      result
    else
      # Rails 2.3      
      compile(local_assigns)

      view.with_template self do
        view.send(:_evaluate_assigns_and_ivars)
        view.send(:_set_controller_content_type, mime_type) if respond_to?(:mime_type)
        
        Hobo::Dryml::TemplateHandler.new.render_for_rails22(self, view, local_assigns)      
      end
    end
  end
  
end

# this is only used in Rails 2.3
class MissingTemplateWrapper
  attr_reader :original_template_path
  
  def initialize(exception, path)
    @exception = exception
    @original_template_path = path
  end

  def method_missing(*args)
    raise @exception
  end

  def render
    raise @exception
  end
end
  
    
module ActionView
  class PathSet < Array
    # this is only used by Rails 2.3
    def find_template_with_dryml(original_template_path, format = nil, html_fallback = true)
      begin
        find_template_without_dryml(original_template_path, format, html_fallback)
      rescue ActionView::MissingTemplate => ex
        # instead of throwing the exception right away, hand back a
        # time bomb instead.  It'll blow if mishandled...
        return MissingTemplateWrapper.new(ex, original_template_path)
      end
    end

    if method_defined? "find_template"
      # only rails 2.3 has this function
      alias_method_chain :find_template, :dryml
    end
  end
end