module Vidibus
  module Permalink
    class Dispatcher

      class PathError < StandardError; end

      # Initialize a new Dispatcher instance.
      # Provide an absolute +path+ to be dispatched.
      def initialize(path, options = {})
        self.path = path
        @scope = options[:scope]
      end

      # Returns the path to dispatch.
      def path
        @path
      end

      # Sets path to dispatch
      def path=(value)
        raise PathError.new("Path must be absolute.") unless value.match(/^\//)
        @path = value
      end

      # Returns parts of the path.
      # Ignores file extension and request params
      def parts
        @parts ||= path.gsub(/(\.[^\/]+)?(\?.*)?$/, "").scan(/[^?\/]+/)
      end

      # Returns permalink objects matching the parts.
      def objects
        @objects ||= resolve_path
      end

      # Returns true if all parts could be resolved.
      def found?
        @is_found ||= begin
          !objects.include?(nil)
        end
      end

      # Returns true if any part does not reflect
      # the current permalink of the underlying linkable.
      def redirect?
        @is_redirect ||= begin
          return unless found?
          redirectables.any?
        end
      end

      # Returns the path to redirect to, if any.
      def redirect_path
        @redirect_path ||= begin
          return unless redirect?
          "/" << current_parts.join("/")
        end
      end

      private

      def resolve_path
        results = ::Permalink.for_scope(@scope).any_in(value: parts)
        links = Array.new(parts.length)
        done = {}
        for result in results
          if i = parts.index(result.value)
            key = "#{result.linkable_type}##{result.linkable_id}"
            next if done[key]
            links[i] = result
            done[key] = true
          end
        end
        links
      end

      # Returns an array containing the current permalinks of all objects.
      def current_parts
        objects.map { |o| o.current.value }
      end

      def redirectables
        objects.select { |o| !o.current? }
      end
    end
  end
end