module Fortitude
  module Support
    class StaticizedMethod
      def initialize(widget_class, method_name, options = { })
        @widget_class = widget_class
        @method_name = method_name

        @output_by_locale = { }
        @has_yield = false

        @options = options
        @options.assert_valid_keys(:locale_support)

        set_constant!
      end

      def run!(widget)
        locale = locale_support? ? widget.widget_locale : nil
        output = (@output_by_locale[locale] ||= generate_content!(widget))

        if output.kind_of?(Array)
          widget.rawtext(output[0])
          yield
          widget.rawtext(output[1])
        else
          widget.rawtext(output)
        end
      end

      def create_method!
        begin
          widget_class.instance_method(method_name)
        rescue NameError => ne
          raise NameError, %{You declared the method #{method_name.inspect} in class
#{widget_class.name.inspect} to be a Fortitude static content method,
but there is no method declared on this class with that name.

(It's possible you simply tried to declare the method static before
it was defined on that class -- the call to 'static' must come
_after_ the definition of the method in the class's source code.)}
        end

        unless widget_class.instance_methods.map(&:to_s).include?(dynamic_method_name.to_s)
          widget_class.send(:alias_method, dynamic_method_name, method_name)
        end

        widget_class.class_eval <<-EOS
    def #{method_name}
      #{constant_name}.run!(self) { yield }
    end
  EOS
      end

      private
      attr_reader :widget_class, :method_name, :output_by_locale, :has_yield, :options

      def locale_support?
        ! (options.has_key?(:locale_support) && (! options[:locale_support]))
      end

      def generate_content!(widget)
        yielded = false
        pre_yield = nil

        result = widget.capture do
          widget.with_staticness_enforced(method_name) do
            widget.send(dynamic_method_name) do
              raise "This method yields more than once; you can't make it static" if yielded
              pre_yield = widget.output_buffer.dup
              yielded = true
              widget.output_buffer.replace('')
            end
          end
        end

        @has_yield = yielded

        if yielded
          [ pre_yield, result ]
        else
          result
        end
      end

      def set_constant!
        widget_class.send(:remove_const, constant_name) if widget_class.const_defined?(constant_name)
        widget_class.const_set(constant_name, self)
      end

      def constant_name
        "FORTITUDE_STATICIZED_METHOD_#{method_name.to_s.upcase}"
      end

      def dynamic_method_name
        "_#{method_name}_dynamic".to_sym
      end
    end
  end
end
