require 'active_support'
require 'active_support/concern'

require 'fortitude/tags/partial_tag_placeholder'

module Fortitude
  class Widget
    module Rendering
      extend ActiveSupport::Concern

      # PUBLIC API
      def render(*args, &block)
        call_through = lambda do
          @_fortitude_rendering_context.record_render(args) do
            tag_rawtext(invoke_helper(:render, *args, &block))
          end
        end

        if self.class._fortitude_record_emitting_tag? && args[0].kind_of?(Hash) && args[0].has_key?(:partial)
          @_fortitude_rendering_context.emitting_tag!(self, Fortitude::Tags::PartialTagPlaceholder.instance, nil, nil, &call_through)
        else
          call_through.call
        end
      end

      def render_to(rendering_context, &block_for_content_method)
        @_fortitude_rendering_context = rendering_context
        @_fortitude_output_buffer_holder = rendering_context.output_buffer_holder
        @_fortitude_block_for_content_method = block_for_content_method

        rendering_context.record_widget(self) do
          begin
            run_content(&_fortitude_run_content_block)
          ensure
            @_fortitude_rendering_context = nil
            @_fortitude_block_for_content_method = nil
          end
        end
      end

      # PUBLIC API
      def to_html(rendering_context = nil)
        rendering_context ||= ::Fortitude::RenderingContext.default_rendering_context
        render_to(rendering_context)
        rendering_context.output_buffer_holder.output_buffer
      end

      # PUBLIC API
      def rendering_context
        @_fortitude_rendering_context
      end

      # PUBLIC API
      def widget(w, hash = nil, &block)
        if w.kind_of?(Class) && ((w < ::Fortitude::Widget) || ::Fortitude::Erector.is_erector_widget_class?(w))
          hash ||= { }
          w = w.new(hash)
        end

        if w.kind_of?(::Fortitude::Widget)
          begin
            @_fortitude_in_block_for_sub_widget = w if block
            w.render_to(@_fortitude_rendering_context, &block)
          ensure
            @_fortitude_in_block_for_sub_widget = nil
          end
        elsif ::Fortitude::Erector.is_erector_widget?(w)
          w.send(:_emit,
            :parent => rendering_context.helpers_object,
            :helpers => rendering_context.helpers_object,
            :output => rendering_context.output_buffer_holder.output_buffer)
        else
          raise "You tried to render a widget, but this is not valid: #{w.inspect}(#{hash.inspect})"
        end
      end

      # PUBLIC API
      def output_buffer
        @_fortitude_output_buffer_holder.output_buffer
      end

      # PUBLIC API
      def initialize(assigns = { }, &block)
        assign_locals_from(assigns)
        @_fortitude_constructor_block = block
      end

      # INTERNAL USE ONLY
      def _fortitude_new_buffer
        _fortitude_class_for_new_buffer.new
      end
      private :_fortitude_new_buffer

      POTENTIAL_NEW_BUFFER_CLASSES = %w{ActionView::OutputBuffer ActiveSupport::SafeBuffer String}

      # INTERNAL USE ONLY
      def _fortitude_class_for_new_buffer
        @_fortitude_class_for_new_buffer ||= begin
          out = nil
          POTENTIAL_NEW_BUFFER_CLASSES.each do |class_name|
            klass = eval(class_name) rescue nil
            if klass
              out = klass
              break
            end
          end
          raise "Huh? NONE of the following classes appear to be defined?!? #{POTENTIAL_NEW_BUFFER_CLASSES.inspect}" unless out
          out
        end
      end
      private :_fortitude_class_for_new_buffer

      # INTERNAL USE ONLY
      def _fortitude_run_content_block
        if @_fortitude_block_for_content_method
          @_fortitude_block_for_content_method
        elsif @_fortitude_constructor_block
          lambda { |*args| @_fortitude_constructor_block.call(self, *args) }
        elsif @_fortitude_rendering_context.effective_yield_block
          @_fortitude_rendering_context.effective_yield_block
        else
          nil
        end
      end

      # PUBLIC API
      def yield_from_widget(*args)
        block = _fortitude_run_content_block
        if block
          block.call(*args)
        else
          raise Fortitude::Errors::NoBlockToYieldTo.new(self)
        end
      end

      # PUBLIC API (Erector compatibility)
      def call_block
        yield_from_widget
      end
    end
  end
end