module Innate
  # Innate support simple routing using string, regex and lambda based routers.
  # Route are stored in a dictionary, which supports hash-like access but
  # preserves order, so routes are evaluated in the order they are added.
  #
  # This middleware should wrap Innate::DynaMap.
  #
  # Please note that Rack::File is put before Route and Rewrite, that means
  # that you cannot apply routes to static files unless you add your own route
  # middleware before.
  #
  # String routers are the simplest way to route in Innate. One path is
  # translated into another:
  #
  #   Innate::Route[ '/foo' ] = '/bar'
  #     '/foo'  =>  '/bar'
  #
  # Regex routers allow matching against paths using regex. Matches within
  # your regex using () are substituted in the new path using printf-like
  # syntax.
  #
  #   Innate::Route[ %r!^/(\d+)\.te?xt$! ] = "/text/%d"
  #     '/123.txt'  =>  '/text/123'
  #     '/789.text' =>  '/text/789'
  #
  # For more complex routing, lambda routers can be used. Lambda routers are
  # passed in the current path and request object, and must return either a new
  # path string, or nil.
  #
  #   Innate::Route[ 'name of route' ] = lambda{ |path, request|
  #     '/bar' if path == '/foo' and request[:bar] == '1'
  #   }
  #     '/foo'        =>  '/foo'
  #     '/foo?bar=1'  =>  '/bar'
  #
  # Lambda routers can also use this alternative syntax:
  #
  #   Innate::Route('name of route') do |path, request|
  #     '/bar' if path == '/foo' and request[:bar] == '1'
  #   end
  #
  # NOTE: Use self::ROUTES notation in singleton methods to force correct
  #       lookup.

  class Route
    ROUTES = []

    def self.[](key)
      found = self::ROUTES.assoc(key)
      found[1] if found
    end

    def self.[]=(key, value)
      self::ROUTES.delete_if{|route_key, route_value| route_key == key }
      self::ROUTES << [key, value]
    end

    def self.clear
      self::ROUTES.clear
    end

    def initialize(app = Innate::DynaMap)
      @app = app
    end

    def call(env)
      path = env['PATH_INFO']
      path << '/' if path.empty?

      if modified = resolve(path)
        Log.debug("%s routes %p to %p" % [self.class.name, path, modified])
        env['PATH_INFO'] = modified
      end

      @app.call(env)
    end

    def resolve(path)
      self.class::ROUTES.each do |key, value|
        if key.is_a?(Regexp)
          md = path.match(key)
          return value % md.to_a[1..-1] if md

        elsif value.respond_to?(:call)
          new_path = value.call(path, Current.request)
          return new_path if new_path

        elsif value.respond_to?(:to_str)
          return value.to_str if path == key

        else
          Log.error("Invalid route %p => %p" % [key, value])
        end
      end

      nil
    end
  end

  # Identical with Innate::Route, but is called before any Node::call is made
  class Rewrite < Route
    ROUTES = []
  end

  module SingletonMethods
    def Route(key, value = nil, &block)
      Route[key] = value || block
    end

    def Rewrite(key, value = nil, &block)
      Rewrite[key] = value || block
    end
  end
end
