module RailsConnector

  # @api public
  module DefaultCmsRoutingHelper
    # Returns the path for +target+ using the +CmsController+ routes.
    # +target+ can be an +Obj+ or a +Link+ or a +LinkList+.
    # If +target+ is a +Linklist+, it must be non-empty. The first +Link+ from the +LinkList+ will be used.
    # +options+ are optional and include url settings such as path parameters or protocol.
    # @return [String]
    # @api public
    def cms_path(target, options = {})
      cms_path_or_url(target, "path", options)
    end

    # Returns the absolute URL for target using the +CmsController+ routes.
    # +target+ can be an +Obj+ or a +Link+ or a +LinkList+.
    # If +target+ is a +Linklist+, it must be non-empty. The first +Link+ from the +LinkList+ will be used.
    # +options+ are optional and include url settings such as path parameters or protocol.
    # @return [String]
    # @api public
    def cms_url(target, options = {})
      cms_path_or_url(target, "url", options)
    end

    LINK_TO_UNREACHABLE = "#__target_object_not_reachable"
    LINK_TO_EMPTY_LINKLIST = "#__empty_linklist"

    def cms_path_or_url(target, path_or_url, options = {})
      if target.is_a?(Link)
        cms_path_or_url_for_links(target, path_or_url, options)
      elsif target.is_a?(Obj)
        cms_path_or_url_for_objs(target, path_or_url, options)
      elsif target.respond_to?(:first)
        if target.first.is_a?(Link)
          cms_path_or_url_for_links(target.first, path_or_url, options)
        else
          return LINK_TO_EMPTY_LINKLIST
        end
      else
        raise "cms_path or cms_url was called with an instance of #{target.class}. "+
          "It must only be called with an Obj or a Link or a non-empty LinkList."
      end
    end

    def cms_path_or_url_for_links(link, path_or_url, options = {})
      return LINK_TO_UNREACHABLE if link.internal? && link.destination_object.nil?
      url = basic_url_or_path_for_link(link, path_or_url, options)
      url = append_search(url, link) if options.empty?

      append_fragment(url, link)
    end

    def cms_path_or_url_for_objs(obj, path_or_url, options = {})
      permalink = obj.permalink
      if permalink && !Configuration.editor_interface_enabled?
        __send__("cms_permalink_#{path_or_url}", options.merge(:permalink => permalink))
      elsif obj.homepage? && !Configuration.editor_interface_enabled?
        __send__("root_#{path_or_url}", options)
      else
        if obj.body_data_url
          obj.body_data_url
        elsif obj.binary? && !obj.file_extension.blank?
          __send__("cms_id_#{path_or_url}",
            options.merge(:id => obj.id, :slug => obj.slug.presence, :format => obj.file_extension))
        else
          routing_options = options.merge(:id => obj.id, :slug => obj.slug.presence)
          __send__("cms_id_#{path_or_url}", routing_options)
        end
      end
    end

    private

    def basic_url_or_path_for_link(link, path_or_url, options = {})
      if link.internal?
        __send__("cms_#{path_or_url}", link.destination_object, options)
      else
        if link.external_prefix?
          url = remove_external_prefix(link.url)
        else
          url = link.url
        end

        url = merge_options(url, options) if options.any?
        url
      end
    end

    def remove_external_prefix(link)
      link.gsub(/external:/, "").strip
    end

    def append_search(url, link)
      url += "?#{link.search}" unless link.search.blank?
      url
    end

    def append_fragment(url, link)
      url += "##{link.fragment}" unless link.fragment.blank?
      url
    end

    def merge_options(url, options)
      parsed_url = URI.parse(url)

      search = Rack::Utils.parse_query(parsed_url.query)
      merged_search = search.merge(options.stringify_keys)
      parsed_url.query = merged_search.to_query

      parsed_url.to_s
    end

  end
end