#
# Finds a route definition that matches a path
#
# ===== Arguments
# * path: actual path to match
# * routes: array of 'routes'
#
# ===== Returns
# Array of two elements:
#
# * index 0: first matching route
# * index 1..n: values for the matched route's variables (in the order they were specified in the route)
#
# ===== Examples
#
#   URLMatcher.match('/foo', ['/', '/foo', '/bar/baz'])   #=> ['/foo']
#   URLMatcher.match('/80/07/01', ['/:year/:month/:day']) #=> ['/80/07/01', '80', '07', '01']
#

require 'pathname'

module Katello
  module Util
    module URLMatcher
      def self.match(path, routes)
        path     = Path.new(path)
        patterns = routes.map { |route| Pattern.new(Array(route).first) }

        patterns.each do |pattern|
          return [pattern.to_s] + pattern.vars if pattern == path
        end

        [nil]
      end

      class Path
        attr_accessor :parts, :ext

        def initialize(path)
          self.parts, self.ext = split_path(path)
        end

        def to_s
          '/' + self.parts.join('/') + self.ext
        end

        private

        def split_path(path)
          path  = path.to_s
          ext   = Pathname(path).extname
          path  = path.sub(/#{ext}$/, '')
          parts = path.split('/').reject { |part| part.empty? }
          [parts, ext]
        end
      end

      class Pattern < Path
        def variables
          return [] unless @match

          a = []
          self.parts.each_with_index do |part, i|
            a << @match.parts[i] if part[0] == ':'
          end
          a << @match.ext[1..] if self.ext[1] == ':'
          a
        end
        alias_method :vars, :variables

        def ==(other)
          is_match = size_match?(other) && ext_match?(other) && static_match?(other)
          @match = other if is_match
          is_match
        end

        private

        def size_match?(path)
          self.parts.size == path.parts.size
        end

        def ext_match?(path)
          (self.ext == path.ext) || (self.ext[1] == ':' && !path.ext.empty?)
        end

        def static_match?(path)
          self.parts.each_with_index do |part, i|
            return false unless part[0] == ':' || path.parts[i] == part
          end
          true
        end
      end
    end
  end
end
