module Padrino
  module PathRouter
    #
    # High performance engine for finding all routes which are matched with pattern
    #
    class Compiler
      # All regexps generated by recursive compiler
      attr_reader :regexps

      ##
      # Constructs an instance of Padrino::PathRouter::Compiler
      #
      def initialize(routes)
        @routes = routes
      end
  
      ##
      # Compiles all routes into regexps.
      #
      def compile!
        return if compiled?
        @regexps = @routes.map.with_index do |route, index|
          route.index = index
          /(?<_#{index}>#{route.matcher.to_regexp})/
        end
        @regexps = recursive_compile(@regexps)
        @compiled = true
      end

      ##
      # Returns true if all routes has been compiled.
      #
      def compiled?
        !!@compiled
      end

      ##
      # Finds routes by using request or env.
      #
      def find_by(request_or_env)
        request = request_or_env.is_a?(Hash) ? Sinatra::Request.new(request_or_env) : request_or_env
        pattern = encode_default_external(request.path_info)
        verb    = request.request_method
        rotation { |offset| match?(offset, pattern) }.select { |route| route.verb == verb }
      end

      ##
      # Calls routes by using request.
      #
      def call_by_request(request)
        rotation do |offset|
          pattern  = encode_default_external(request.path_info)
          if route = match?(offset, pattern)
            params = route.params_for(pattern, request.params)
            yield(route, params) if route.verb == request.request_method
            route
          end
        end
      end

      ##
      # Finds routes by using PATH_INFO.
      #
      def find_by_pattern(pattern)
        pattern = pattern.encode(Encoding.default_external)
        rotation { |offset| match?(offset, pattern) }
      end
  
      private

      ##
      # Returns a instance of PathRouter::Route if path is matched with current regexp
      #
      def match?(offset, path)
        current_regexp = @regexps[offset]
        return unless current_regexp === path || (path.end_with?("/") && current_regexp === path[0..-2])
        @routes[offset..-1].detect{ |route| Regexp.last_match["_#{route.index}"] }
      end

      ##
      # Runs through all regexps to find routes.
      #
      def rotation(offset = 0)
        compile! unless compiled?
        loop.with_object([]) do |_, candidacies|
          return candidacies unless route = yield(offset)
          candidacies << route
          offset = route.index.next
        end
      end

      ##
      # Compiles routes into regexp recursively.
      #
      def recursive_compile(regexps, paths = [])
        return paths if regexps.length.zero?
        paths << Regexp.union(regexps)
        regexps.shift
        recursive_compile(regexps, paths)
      end

      ##
      # Encode string with Encoding.default_external
      #
      def encode_default_external(string)
        string.encode(Encoding.default_external)
      end
    end
  end
end
