module ManageableContent
  module Controllers
    module Dsl
      extend ActiveSupport::Concern

      mattr_accessor :manageable_layout_content_keys
      self.manageable_layout_content_keys = {}

      included do
        class_attribute :manageable_content_custom_key_evaluator
        class_attribute :manageable_content_keys
        self.manageable_content_keys = {}

        helper_method :manageable_layout_content_for
        helper_method :manageable_content_for
      end

      module ClassMethods

        # Configures the manageable contents that will be shared between all Controllers that
        # use a given layout.
        # For example, if all Controllers using the application layout will share a 'footer_message'
        # and a 'footer_copyright' contents, the following should be set:
        #
        #   manageable_layout_content_for :footer_message, :footer_copyright
        #
        # By default, this will set contents for a layout named the same as the Controller in which
        # the manageable_layout_content_for method was called. For example, if it was called in
        # the ApplicationController, it will set the layout vars for the 'application' layout.
        # If you need to use multiple layouts with ApplicationController, you can specify
        # for which layout are the contents being set with this:
        #
        #   manageable_layout_content_for :footer_message, :footer_copyright, :layout => 'application'
        #
        # You can also set a content type for the given keys. By default they are :text content types.
        # Available types are :text for long contents and :string for short contents.
        #
        #   manageable_layout_content_for :footer_copyright, :type => :string
        #
        def manageable_layout_content_for(*keys)
          options = keys.last.is_a?(Hash) ? keys.pop : {}
          layout  = options[:layout] || self.controller_path
          type    = options[:type]   || :text

          if keys.present?
            Dsl.manageable_layout_content_keys[layout] =
              ((Dsl.manageable_layout_content_keys[layout] || {}).merge(keys_for_type(type, keys)))
          end
        end

        # Configures the manageable contents for a Controller.
        # For example, if the Controller will have a 'body' and a 'side' contents,
        # the following should be set:
        #
        #   manageable_content_for :body, :side
        #
        # This will also inherit manageable contents from superclasses. For example,
        # if all your Controllers will have a 'title' content, you can add the following
        # to ApplicationController, and all Controllers which inherit from it will have it too:
        #
        #   manageable_content_for :title
        #
        # You can also set a content type for the given keys. By default they are :text content types.
        # Available types are :text for long contents and :string for short contents.
        #
        #   manageable_content_for :title, :type => :string
        #
        def manageable_content_for(*keys)
          options = keys.last.is_a?(Hash) ? keys.pop : {}
          type    = options[:type] || :text

          if keys.present?
            self.manageable_content_keys = (self.manageable_content_keys.merge(keys_for_type(type, keys)))
          end
        end

        # Defines a custom page key that should be looked at for valid pages for the current controller.
        # This can be useful when dealing with custom pages.
        # Expects a block that should return a page key.
        # E.g.:
        #
        #   manageable_content_page_key do |params|
        #     params[:page]
        #   end
        def manageable_content_custom_key(&block)
          raise ArgumentError, "Block required" unless block_given?
          self.manageable_content_custom_key_evaluator = block
        end

        private

        def keys_for_type(type, keys)
          keys.inject({}) do |config, key|
            config[key] = type
            config
          end
        end
      end

      # Retrieves the content for the current layout with a given key.
      def manageable_layout_content_for(key)
        manageable_content_for_page :layout, key
      end

      # Retrieves the content for the current page with a given key.
      def manageable_content_for(key)
        manageable_content_for_page :controller, key
      end

      private

      def manageable_content_for_page(type, key)
        layout = if _layout.instance_of? String
          # Rails <= 3.1
          _layout
        else
          # Rails >= 3.2 returns something like "layout/application", so we get everything but the
          # "layout" part
          _layout.virtual_path.split('/')[(1..-1)].join('/')
        end

        keys = [layout]
        keys << if self.class.manageable_content_custom_key_evaluator
          instance_eval(&self.class.manageable_content_custom_key_evaluator)
        else
          controller_path
        end

        @pages ||= ManageableContent::Manager.page(keys)

        subject = type == :layout ? @pages.try(:slice, 0) : @pages.try(:slice, 1)
        subject.try(:content, key).try(:html_safe)
      end
    end
  end
end