// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.

package revel

import (
	"encoding/csv"
	"fmt"
	"io"
	"io/ioutil"
	"net/url"
	"path/filepath"
	"regexp"
	"strings"

	"os"
	"sync"

	"github.com/revel/pathtree"
	"github.com/revel/revel/logger"
	"errors"
)

const (
	httpStatusCode = "404"
)

type Route struct {
	ModuleSource        *Module         // Module name of route
	Method              string          // e.g. GET
	Path                string          // e.g. /app/:id
	Action              string          // e.g. "Application.ShowApp", "404"
	ControllerNamespace string          // e.g. "testmodule.",
	ControllerName      string          // e.g. "Application", ""
	MethodName          string          // e.g. "ShowApp", ""
	FixedParams         []string        // e.g. "arg1","arg2","arg3" (CSV formatting)
	TreePath            string          // e.g. "/GET/app/:id"
	TypeOfController    *ControllerType // The controller type (if route is not wild carded)

	routesPath string // e.g. /Users/robfig/gocode/src/myapp/conf/routes
	line       int    // e.g. 3
}

type RouteMatch struct {
	Action           string // e.g. 404
	ControllerName   string // e.g. Application
	MethodName       string // e.g. ShowApp
	FixedParams      []string
	Params           map[string][]string // e.g. {id: 123}
	TypeOfController *ControllerType     // The controller type
	ModuleSource     *Module             // The module
}

type ActionPathData struct {
	Key                 string            // The unique key
	ControllerNamespace string            // The controller namespace
	ControllerName      string            // The controller name
	MethodName          string            // The method name
	Action              string            // The action
	ModuleSource        *Module           // The module
	Route               *Route            // The route
	FixedParamsByName   map[string]string // The fixed parameters
	TypeOfController    *ControllerType   // The controller type
}

var (
	// Used to store decoded action path mappings
	actionPathCacheMap = map[string]*ActionPathData{}
	// Used to prevent concurrent writes to map
	actionPathCacheLock = sync.Mutex{}
	// The path returned if not found
	notFound = &RouteMatch{Action: "404"}
)

var routerLog = RevelLog.New("section", "router")

func init() {
	AddInitEventHandler(func(typeOf Event, value interface{}) (responseOf EventResponse) {
		// Add in an
		if typeOf == ROUTE_REFRESH_REQUESTED {
			// Clear the actionPathCacheMap cache
			actionPathCacheLock.Lock()
			defer actionPathCacheLock.Unlock()
			actionPathCacheMap = map[string]*ActionPathData{}
		}
		return
	})
}

// NewRoute prepares the route to be used in matching.
func NewRoute(moduleSource *Module, method, path, action, fixedArgs, routesPath string, line int) (r *Route) {
	// Handle fixed arguments
	argsReader := strings.NewReader(string(namespaceReplace([]byte(fixedArgs), moduleSource)))
	csvReader := csv.NewReader(argsReader)
	csvReader.TrimLeadingSpace = true
	fargs, err := csvReader.Read()
	if err != nil && err != io.EOF {
		routerLog.Error("NewRoute: Invalid fixed parameters for string ", "error", err, "fixedargs", fixedArgs)
	}

	r = &Route{
		ModuleSource: moduleSource,
		Method:       strings.ToUpper(method),
		Path:         path,
		Action:       string(namespaceReplace([]byte(action), moduleSource)),
		FixedParams:  fargs,
		TreePath:     treePath(strings.ToUpper(method), path),
		routesPath:   routesPath,
		line:         line,
	}

	// URL pattern
	if !strings.HasPrefix(r.Path, "/") {
		routerLog.Error("NewRoute: Absolute URL required.")
		return
	}

	// Ignore the not found status code
	if action != httpStatusCode {
		routerLog.Debugf("NewRoute: New splitActionPath path:%s action:%s", path, action)
		pathData, found := splitActionPath(&ActionPathData{ModuleSource: moduleSource, Route: r}, r.Action, false)
		if found {
			if pathData.TypeOfController != nil {
				// Assign controller type to avoid looking it up based on name
				r.TypeOfController = pathData.TypeOfController
				// Create the fixed parameters
				if l := len(pathData.Route.FixedParams); l > 0 && len(pathData.FixedParamsByName) == 0 {
					methodType := pathData.TypeOfController.Method(pathData.MethodName)
					if methodType != nil {
						pathData.FixedParamsByName = make(map[string]string, l)
						for i, argValue := range pathData.Route.FixedParams {
							Unbind(pathData.FixedParamsByName, methodType.Args[i].Name, argValue)
						}
					} else {
						routerLog.Panicf("NewRoute: Method %s not found for controller %s", pathData.MethodName, pathData.ControllerName)
					}
				}
			}
			r.ControllerNamespace = pathData.ControllerNamespace
			r.ControllerName = pathData.ControllerName
			r.ModuleSource = pathData.ModuleSource
			r.MethodName = pathData.MethodName

			// The same action path could be used for multiple routes (like the Static.Serve)
		} else {
			routerLog.Panicf("NewRoute: Failed to find controller for route path action %s \n%#v\n", path+"?"+r.Action, actionPathCacheMap)
		}
	}
	return
}

func (route *Route) ActionPath() string {
	return route.ModuleSource.Namespace() + route.ControllerName
}

func treePath(method, path string) string {
	if method == "*" {
		method = ":METHOD"
	}
	return "/" + method + path
}

type Router struct {
	Routes []*Route
	Tree   *pathtree.Node
	Module string // The module the route is associated with
	path   string // path to the routes file
}

func (router *Router) Route(req *Request) (routeMatch *RouteMatch) {
	// Override method if set in header
	if method := req.GetHttpHeader("X-HTTP-Method-Override"); method != "" && req.Method == "POST" {
		req.Method = method
	}

	leaf, expansions := router.Tree.Find(treePath(req.Method, req.GetPath()))
	if leaf == nil {
		return nil
	}

	// Create a map of the route parameters.
	var params url.Values
	if len(expansions) > 0 {
		params = make(url.Values)
		for i, v := range expansions {
			params[leaf.Wildcards[i]] = []string{v}
		}
	}
	var route *Route
	var controllerName, methodName string

	// The leaf value is now a list of possible routes to match, only a controller
	routeList := leaf.Value.([]*Route)
	var typeOfController *ControllerType

	//INFO.Printf("Found route for path %s %#v", req.URL.Path, len(routeList))
	for index := range routeList {
		route = routeList[index]
		methodName = route.MethodName

		// Special handling for explicit 404's.
		if route.Action == httpStatusCode {
			route = nil
			break
		}

		// If wildcard match on method name use the method name from the params
		if methodName[0] == ':' {
			if methodKey, found := params[methodName[1:]]; found && len(methodKey) > 0 {
				methodName = strings.ToLower(methodKey[0])
			} else {
				routerLog.Fatal("Route: Failure to find method name in parameters", "params", params, "methodName", methodName)
			}
		}

		// If the action is variablized, replace into it with the captured args.
		controllerName = route.ControllerName
		if controllerName[0] == ':' {
			controllerName = strings.ToLower(params[controllerName[1:]][0])
			if typeOfController = route.ModuleSource.ControllerByName(controllerName, methodName); typeOfController != nil {
				break
			}
		} else {
			typeOfController = route.TypeOfController
			break
		}
		route = nil
	}

	if route == nil {
		routeMatch = notFound
	} else {

		routeMatch = &RouteMatch{
			ControllerName:   route.ControllerNamespace + controllerName,
			MethodName:       methodName,
			Params:           params,
			FixedParams:      route.FixedParams,
			TypeOfController: typeOfController,
			ModuleSource:     route.ModuleSource,
		}
	}

	return
}

// Refresh re-reads the routes file and re-calculates the routing table.
// Returns an error if a specified action could not be found.
func (router *Router) Refresh() (err *Error) {
	RaiseEvent(ROUTE_REFRESH_REQUESTED, nil)
	router.Routes, err = parseRoutesFile(appModule, router.path, "", true)
	RaiseEvent(ROUTE_REFRESH_COMPLETED, nil)
	if err != nil {
		return
	}
	err = router.updateTree()
	return
}

func (router *Router) updateTree() *Error {
	router.Tree = pathtree.New()
	pathMap := map[string][]*Route{}

	allPathsOrdered := []string{}
	// It is possible for some route paths to overlap
	// based on wildcard matches,
	// TODO when pathtree is fixed (made to be smart enough to not require a predefined intake order) keeping the routes in order is not necessary
	for _, route := range router.Routes {
		if _, found := pathMap[route.TreePath]; !found {
			pathMap[route.TreePath] = append(pathMap[route.TreePath], route)
			allPathsOrdered = append(allPathsOrdered, route.TreePath)
		} else {
			pathMap[route.TreePath] = append(pathMap[route.TreePath], route)
		}
	}
	for _, path := range allPathsOrdered {
		routeList := pathMap[path]
		err := router.Tree.Add(path, routeList)

		// Allow GETs to respond to HEAD requests.
		if err == nil && routeList[0].Method == "GET" {
			err = router.Tree.Add(treePath("HEAD", routeList[0].Path), routeList)
		}

		// Error adding a route to the pathtree.
		if err != nil {
			return routeError(err, path, fmt.Sprintf("%#v", routeList), routeList[0].line)
		}
	}
	return nil
}

// Returns the controller namespace and name, action and module if found from the actionPath specified
func splitActionPath(actionPathData *ActionPathData, actionPath string, useCache bool) (pathData *ActionPathData, found bool) {
	actionPath = strings.ToLower(actionPath)
	if pathData, found = actionPathCacheMap[actionPath]; found && useCache {
		return
	}
	var (
		controllerNamespace, controllerName, methodName, action string
		foundModuleSource                                       *Module
		typeOfController                                        *ControllerType
		log                                                     = routerLog.New("actionPath", actionPath)
	)
	actionSplit := strings.Split(actionPath, ".")
	if actionPathData != nil {
		foundModuleSource = actionPathData.ModuleSource
	}
	if len(actionSplit) == 2 {
		controllerName, methodName = strings.ToLower(actionSplit[0]), strings.ToLower(actionSplit[1])
		if i := strings.Index(methodName, "("); i > 0 {
			methodName = methodName[:i]
		}
		log = log.New("controller", controllerName, "method", methodName)
		log.Debug("splitActionPath: Check for namespace")
		if i := strings.Index(controllerName, namespaceSeperator); i > -1 {
			controllerNamespace = controllerName[:i+1]
			if moduleSource, found := ModuleByName(controllerNamespace[:len(controllerNamespace)-1]); found {
				foundModuleSource = moduleSource
				controllerNamespace = moduleSource.Namespace()
				log = log.New("namespace",controllerNamespace)
				log.Debug("Found module namespace")
			} else {
				log.Warnf("splitActionPath: Unable to find module %s for action: %s", controllerNamespace[:len(controllerNamespace)-1], actionPath)
			}
			controllerName = controllerName[i+1:]
			log = log.New("controllerShortName",controllerName)
			// Check for the type of controller
			typeOfController = foundModuleSource.ControllerByName(controllerName, methodName)
			found = typeOfController != nil
			log.Debug("Found controller","found",found,"type",typeOfController)
		} else if controllerName[0] != ':' {
			// First attempt to find the controller in the module source
			if foundModuleSource != nil {
				typeOfController = foundModuleSource.ControllerByName(controllerName, methodName)
				if typeOfController != nil {
					controllerNamespace = typeOfController.Namespace
				}
			}
			log.Info("Found controller for path", "controllerType", typeOfController)

			if typeOfController == nil {
				// Check to see if we can determine the controller from only the controller name
				// an actionPath without a moduleSource will only come from
				// Scan through the controllers
				matchName := controllerName
				for key, controller := range controllers {
					// Strip away the namespace from the controller. to be match
					regularName := key
					if i := strings.Index(key, namespaceSeperator); i > -1 {
						regularName = regularName[i+1:]
					}
					if regularName == matchName {
						// Found controller match
						typeOfController = controller
						controllerNamespace = typeOfController.Namespace
						controllerName = typeOfController.ShortName()
						foundModuleSource = typeOfController.ModuleSource
						found = true
						break
					}
				}
			} else {
				found = true
			}
		} else {
			// If wildcard assign the route the controller namespace found
			controllerNamespace = actionPathData.ModuleSource.Name + namespaceSeperator
			foundModuleSource = actionPathData.ModuleSource
			found = true
		}
		action = actionSplit[1]
	} else {
		foundPaths := ""
		for path := range actionPathCacheMap {
			foundPaths += path + ","
		}
		log.Warnf("splitActionPath: Invalid action path %s found paths %s", actionPath, foundPaths)
		found = false
	}

	// Make sure no concurrent map writes occur
	if found {
		actionPathCacheLock.Lock()
		defer actionPathCacheLock.Unlock()
		if actionPathData != nil {
			actionPathData.ControllerNamespace = controllerNamespace
			actionPathData.ControllerName = controllerName
			actionPathData.MethodName = methodName
			actionPathData.Action = action
			actionPathData.ModuleSource = foundModuleSource
			actionPathData.TypeOfController = typeOfController
		} else {
			actionPathData = &ActionPathData{
				ControllerNamespace: controllerNamespace,
				ControllerName:      controllerName,
				MethodName:          methodName,
				Action:              action,
				ModuleSource:        foundModuleSource,
				TypeOfController:    typeOfController,
			}
		}
		actionPathData.TypeOfController = foundModuleSource.ControllerByName(controllerName, "")
		if actionPathData.TypeOfController == nil && actionPathData.ControllerName[0] != ':' {
			log.Warnf("splitActionPath: No controller found for %s %#v", foundModuleSource.Namespace()+controllerName, controllers)
		}

		pathData = actionPathData
		if pathData.Route != nil && len(pathData.Route.FixedParams) > 0 {
			// If there are fixed params on the route then add them to the path
			// This will give it a unique path and it should still be usable for a reverse lookup provided the name is matchable
			// for example
			// GET   /test/                     Application.Index("Test", "Test2")
			// {{url "Application.Index(test,test)" }}
			// should be parseable
			actionPath = actionPath + "(" + strings.ToLower(strings.Join(pathData.Route.FixedParams, ",")) + ")"
		}
		if actionPathData.Route != nil {
			log.Debugf("splitActionPath: Split Storing recognized action path %s for route  %#v ", actionPath, actionPathData.Route)
		}
		pathData.Key = actionPath
		actionPathCacheMap[actionPath] = pathData
		if !strings.Contains(actionPath, namespaceSeperator) && pathData.TypeOfController != nil {
			actionPathCacheMap[strings.ToLower(pathData.TypeOfController.Namespace)+actionPath] = pathData
			log.Debugf("splitActionPath: Split Storing recognized action path %s for route  %#v ", strings.ToLower(pathData.TypeOfController.Namespace)+actionPath, actionPathData.Route)
		}
	}
	return
}

// parseRoutesFile reads the given routes file and returns the contained routes.
func parseRoutesFile(moduleSource *Module, routesPath, joinedPath string, validate bool) ([]*Route, *Error) {
	contentBytes, err := ioutil.ReadFile(routesPath)
	if err != nil {
		return nil, &Error{
			Title:       "Failed to load routes file",
			Description: err.Error(),
		}
	}
	return parseRoutes(moduleSource, routesPath, joinedPath, string(contentBytes), validate)
}

// parseRoutes reads the content of a routes file into the routing table.
func parseRoutes(moduleSource *Module, routesPath, joinedPath, content string, validate bool) ([]*Route, *Error) {
	var routes []*Route

	// For each line..
	for n, line := range strings.Split(content, "\n") {
		line = strings.TrimSpace(line)
		if len(line) == 0 || line[0] == '#' {
			continue
		}

		const modulePrefix = "module:"

		// Handle included routes from modules.
		// e.g. "module:testrunner" imports all routes from that module.
		if strings.HasPrefix(line, modulePrefix) {
			moduleRoutes, err := getModuleRoutes(line[len(modulePrefix):], joinedPath, validate)
			if err != nil {
				return nil, routeError(err, routesPath, content, n)
			}
			routes = append(routes, moduleRoutes...)
			continue
		}

		// A single route
		method, path, action, fixedArgs, found := parseRouteLine(line)
		if !found {
			continue
		}

		// this will avoid accidental double forward slashes in a route.
		// this also avoids pathtree freaking out and causing a runtime panic
		// because of the double slashes
		if strings.HasSuffix(joinedPath, "/") && strings.HasPrefix(path, "/") {
			joinedPath = joinedPath[0 : len(joinedPath)-1]
		}
		path = strings.Join([]string{AppRoot, joinedPath, path}, "")

		// This will import the module routes under the path described in the
		// routes file (joinedPath param). e.g. "* /jobs module:jobs" -> all
		// routes' paths will have the path /jobs prepended to them.
		// See #282 for more info
		if method == "*" && strings.HasPrefix(action, modulePrefix) {
			moduleRoutes, err := getModuleRoutes(action[len(modulePrefix):], path, validate)
			if err != nil {
				return nil, routeError(err, routesPath, content, n)
			}
			routes = append(routes, moduleRoutes...)
			continue
		}

		route := NewRoute(moduleSource, method, path, action, fixedArgs, routesPath, n)
		routes = append(routes, route)

		if validate {
			if err := validateRoute(route); err != nil {
				return nil, routeError(err, routesPath, content, n)
			}
		}
	}

	return routes, nil
}

// validateRoute checks that every specified action exists.
func validateRoute(route *Route) error {
	// Skip 404s
	if route.Action == httpStatusCode {
		return nil
	}

	// Skip variable routes.
	if route.ControllerName[0] == ':' || route.MethodName[0] == ':' {
		return nil
	}

	// Precheck to see if controller exists
	if _, found := controllers[route.ControllerNamespace+route.ControllerName]; !found {
		// Scan through controllers to find module
		for _, c := range controllers {
			controllerName := strings.ToLower(c.Type.Name())
			if controllerName == route.ControllerName {
				route.ControllerNamespace = c.ModuleSource.Name + namespaceSeperator
				routerLog.Warn("validateRoute: Matched empty namespace route for %s to this namespace %s for the route %s", controllerName, c.ModuleSource.Name, route.Path)
			}
		}
	}

	// TODO need to check later
	// does it do only validation or validation and instantiate the controller.
	var c Controller
	return c.SetTypeAction(route.ControllerNamespace+route.ControllerName, route.MethodName, route.TypeOfController)
}

// routeError adds context to a simple error message.
func routeError(err error, routesPath, content string, n int) *Error {
	if revelError, ok := err.(*Error); ok {
		return revelError
	}
	// Load the route file content if necessary
	if content == "" {
		if contentBytes, er := ioutil.ReadFile(routesPath); er != nil {
			routerLog.Error("routeError: Failed to read route file ", "file", routesPath, "error", er)
		} else {
			content = string(contentBytes)
		}
	}
	return &Error{
		Title:       "Route validation error",
		Description: err.Error(),
		Path:        routesPath,
		Line:        n + 1,
		SourceLines: strings.Split(content, "\n"),
		Stack:       fmt.Sprintf("%s", logger.NewCallStack()),
	}
}

// getModuleRoutes loads the routes file for the given module and returns the
// list of routes.
func getModuleRoutes(moduleName, joinedPath string, validate bool) (routes []*Route, err *Error) {
	// Look up the module.  It may be not found due to the common case of e.g. the
	// testrunner module being active only in dev mode.
	module, found := ModuleByName(moduleName)
	if !found {
		routerLog.Debug("getModuleRoutes: Skipping routes for inactive module", "module", moduleName)
		return nil, nil
	}
	routePath := filepath.Join(module.Path, "conf", "routes")
	if _, e := os.Stat(routePath); e == nil {
		routes, err = parseRoutesFile(module, routePath, joinedPath, validate)
	}
	if err == nil {
		for _, route := range routes {
			route.ModuleSource = module
		}
	}

	return routes, err
}

// Groups:
// 1: method
// 4: path
// 5: action
// 6: fixedargs
var routePattern = regexp.MustCompile(
	"(?i)^(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD|WS|PROPFIND|MKCOL|COPY|MOVE|PROPPATCH|LOCK|UNLOCK|TRACE|PURGE|\\*)" +
		"[(]?([^)]*)(\\))?[ \t]+" +
		"(.*/[^ \t]*)[ \t]+([^ \t(]+)" +
		`\(?([^)]*)\)?[ \t]*$`)

func parseRouteLine(line string) (method, path, action, fixedArgs string, found bool) {
	matches := routePattern.FindStringSubmatch(line)
	if matches == nil {
		return
	}
	method, path, action, fixedArgs = matches[1], matches[4], matches[5], matches[6]
	found = true
	return
}

func NewRouter(routesPath string) *Router {
	return &Router{
		Tree: pathtree.New(),
		path: routesPath,
	}
}

type ActionDefinition struct {
	Host, Method, URL, Action string
	Star                      bool
	Args                      map[string]string
}

func (a *ActionDefinition) String() string {
	return a.URL
}

func (router *Router) Reverse(action string, argValues map[string]string) (ad *ActionDefinition) {
	ad, err := router.ReverseError(action,argValues,nil)
	if err!=nil {
		routerLog.Error("splitActionPath: Failed to find reverse route", "action", action, "arguments", argValues)
	}
	return ad
}
func (router *Router) ReverseError(action string, argValues map[string]string, req *Request) (ad *ActionDefinition, err error) {
	var log logger.MultiLogger
	if req!=nil {
		log = req.controller.Log.New("action", action)
	} else {
		log = routerLog.New("action", action)
	}
	pathData, found := splitActionPath(nil, action, true)
	if !found {
		log.Error("splitActionPath: Failed to find reverse route", "action", action, "arguments", argValues)
		return
	}

	log.Debug("Checking for route", "pathdataRoute", pathData.Route)
	if pathData.Route == nil {
		var possibleRoute *Route
		// If the route is nil then we need to go through the routes to find the first matching route
		// from this controllers namespace, this is likely a wildcard route match
		for _, route := range router.Routes {
			// Skip routes that are not wild card or empty
			if route.ControllerName == "" || route.MethodName == "" {
				continue
			}
			if route.ModuleSource == pathData.ModuleSource && route.ControllerName[0] == ':' {
				// Wildcard match in same module space
				pathData.Route = route
				break
			} else if route.ActionPath() == pathData.ModuleSource.Namespace()+pathData.ControllerName &&
				(route.Method[0] == ':' || route.Method == pathData.MethodName) {
				// Action path match
				pathData.Route = route
				break
			} else if route.ControllerName == pathData.ControllerName &&
				(route.Method[0] == ':' || route.Method == pathData.MethodName) {
				// Controller name match
				possibleRoute = route
			}
		}
		if pathData.Route == nil && possibleRoute != nil {
			pathData.Route = possibleRoute
			routerLog.Warnf("Reverse: For a url reverse a match was based on  %s matched path to route %#v ", action, possibleRoute)
		}
		if pathData.Route != nil {
			routerLog.Debugf("Reverse: Reverse Storing recognized action path %s for route %#v\n", action, pathData.Route)
		}
	}

	// Likely unknown route because of a wildcard, perform manual lookup
	if pathData.Route != nil {
		route := pathData.Route

		// If the controller or method are wildcards we need to populate the argValues
		controllerWildcard := route.ControllerName[0] == ':'
		methodWildcard := route.MethodName[0] == ':'

		// populate route arguments with the names
		if controllerWildcard {
			argValues[route.ControllerName[1:]] = pathData.ControllerName
		}
		if methodWildcard {
			argValues[route.MethodName[1:]] = pathData.MethodName
		}
		// In theory all routes should be defined and pre-populated, the route controllers may not be though
		// with wildcard routes
		if pathData.TypeOfController == nil {
			if controllerWildcard || methodWildcard {
				if controller := ControllerTypeByName(pathData.ControllerNamespace+pathData.ControllerName, route.ModuleSource); controller != nil {
					// Wildcard match boundary
					pathData.TypeOfController = controller
					// See if the path exists in the module based
				} else {
					log.Errorf("Reverse: Controller %s not found in reverse lookup", pathData.ControllerNamespace+pathData.ControllerName)
					err = errors.New("Reverse: Controller not found in reverse lookup")
					return
				}
			}
		}

		if pathData.TypeOfController == nil {
			log.Errorf("Reverse: Controller %s not found in reverse lookup", pathData.ControllerNamespace+pathData.ControllerName)
			err = errors.New("Reverse: Controller not found in reverse lookup")
			return
		}
		var (
			queryValues  = make(url.Values)
			pathElements = strings.Split(route.Path, "/")
		)
		for i, el := range pathElements {
			if el == "" || (el[0] != ':' && el[0] != '*') {
				continue
			}
			val, ok := pathData.FixedParamsByName[el[1:]]
			if !ok {
				val, ok = argValues[el[1:]]
			}
			if !ok {
				val = "<nil>"
				log.Error("Reverse: reverse route missing route argument ", "argument", el[1:])
				panic("Check stack")
				err = errors.New("Missing route arguement")
				return
			}
			pathElements[i] = val
			delete(argValues, el[1:])
			continue
		}

		// Add any args that were not inserted into the path into the query string.
		for k, v := range argValues {
			queryValues.Set(k, v)
		}

		// Calculate the final URL and Method
		urlPath := strings.Join(pathElements, "/")
		if len(queryValues) > 0 {
			urlPath += "?" + queryValues.Encode()
		}

		method := route.Method
		star := false
		if route.Method == "*" {
			method = "GET"
			star = true
		}

		log.Infof("Reversing action %s to %s Using Route %#v",action,urlPath,pathData.Route)

		ad = &ActionDefinition{
			URL:    urlPath,
			Method: method,
			Star:   star,
			Action: action,
			Args:   argValues,
			Host:   "TODO",
		}
		return
	}

	routerLog.Error("Reverse: Failed to find controller for reverse route", "action", action, "arguments", argValues)
	err = errors.New("Reverse: Failed to find controller for reverse route")
	return
}

func RouterFilter(c *Controller, fc []Filter) {
	// Figure out the Controller/Action
	route := MainRouter.Route(c.Request)
	if route == nil {
		c.Result = c.NotFound("No matching route found: " + c.Request.GetRequestURI())
		return
	}

	// The route may want to explicitly return a 404.
	if route.Action == httpStatusCode {
		c.Result = c.NotFound("(intentionally)")
		return
	}

	// Set the action.
	if err := c.SetTypeAction(route.ControllerName, route.MethodName, route.TypeOfController); err != nil {
		c.Result = c.NotFound(err.Error())
		return
	}

	// Add the route and fixed params to the Request Params.
	c.Params.Route = route.Params
	// Assign logger if from module
	if c.Type.ModuleSource != nil && c.Type.ModuleSource != appModule {
		c.Log = c.Type.ModuleSource.Log.New("ip", c.ClientIP,
			"path", c.Request.URL.Path, "method", c.Request.Method)
	}

	// Add the fixed parameters mapped by name.
	// TODO: Pre-calculate this mapping.
	for i, value := range route.FixedParams {
		if c.Params.Fixed == nil {
			c.Params.Fixed = make(url.Values)
		}
		if i < len(c.MethodType.Args) {
			arg := c.MethodType.Args[i]
			c.Params.Fixed.Set(arg.Name, value)
		} else {
			routerLog.Warn("RouterFilter: Too many parameters to action", "action", route.Action, "value", value)
			break
		}
	}

	fc[0](c, fc[1:])
}

// HTTPMethodOverride overrides allowed http methods via form or browser param
func HTTPMethodOverride(c *Controller, fc []Filter) {
	// An array of HTTP verbs allowed.
	verbs := []string{"POST", "PUT", "PATCH", "DELETE"}

	method := strings.ToUpper(c.Request.Method)

	if method == "POST" {
		param := ""
		if f, err := c.Request.GetForm(); err == nil {
			param = strings.ToUpper(f.Get("_method"))
		}

		if len(param) > 0 {
			override := false
			// Check if param is allowed
			for _, verb := range verbs {
				if verb == param {
					override = true
					break
				}
			}

			if override {
				c.Request.Method = param
			} else {
				c.Response.Status = 405
				c.Result = c.RenderError(&Error{
					Title:       "Method not allowed",
					Description: "Method " + param + " is not allowed (valid: " + strings.Join(verbs, ", ") + ")",
				})
				return
			}

		}
	}

	fc[0](c, fc[1:]) // Execute the next filter stage.
}

func init() {
	OnAppStart(func() {
		MainRouter = NewRouter(filepath.Join(BasePath, "conf", "routes"))
		err := MainRouter.Refresh()
		if MainWatcher != nil && Config.BoolDefault("watch.routes", true) {
			MainWatcher.Listen(MainRouter, MainRouter.path)
		} else if err != nil {
			// Not in dev mode and Route loading failed, we should crash.
			routerLog.Panic("init: router initialize error", "error", err)
		}
	})
}
