package iris

import (
	"bytes"
	"net"
	"net/http"
	"net/http/pprof"
	"os"
	"sort"
	"strings"
	"sync"

	"github.com/iris-contrib/errors"
	"github.com/kataras/iris/config"
	"github.com/kataras/iris/logger"
	"github.com/kataras/iris/utils"
	"github.com/valyala/fasthttp"
	"github.com/valyala/fasthttp/fasthttpadaptor"
)

const (
	// MethodGet "GET"
	MethodGet = "GET"
	// MethodPost "POST"
	MethodPost = "POST"
	// MethodPut "PUT"
	MethodPut = "PUT"
	// MethodDelete "DELETE"
	MethodDelete = "DELETE"
	// MethodConnect "CONNECT"
	MethodConnect = "CONNECT"
	// MethodHead "HEAD"
	MethodHead = "HEAD"
	// MethodPatch "PATCH"
	MethodPatch = "PATCH"
	// MethodOptions "OPTIONS"
	MethodOptions = "OPTIONS"
	// MethodTrace "TRACE"
	MethodTrace = "TRACE"
)

var (
	// AllMethods "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD", "PATCH", "OPTIONS", "TRACE"
	AllMethods = [...]string{MethodGet, MethodPost, MethodPut, MethodDelete, MethodConnect, MethodHead, MethodPatch, MethodOptions, MethodTrace}

	/* methods as []byte, these are really used by iris */
	// methodGetBytes "GET"
	methodGetBytes = []byte(MethodGet)
	// methodPostBytes "POST"
	methodPostBytes = []byte(MethodPost)
	// methodPutBytes "PUT"
	methodPutBytes = []byte(MethodPut)
	// methodDeleteBytes "DELETE"
	methodDeleteBytes = []byte(MethodDelete)
	// methodConnectBytes "CONNECT"
	methodConnectBytes = []byte(MethodConnect)
	// methodHeadBytes "HEAD"
	methodHeadBytes = []byte(MethodHead)
	// methodPatchBytes "PATCH"
	methodPatchBytes = []byte(MethodPatch)
	// methodOptionsBytes "OPTIONS"
	methodOptionsBytes = []byte(MethodOptions)
	// methodTraceBytes "TRACE"
	methodTraceBytes = []byte(MethodTrace)
	/* */

)

const (
	// StatusContinue http status '100'
	StatusContinue = 100
	// StatusSwitchingProtocols http status '101'
	StatusSwitchingProtocols = 101

	// StatusOK http status '200'
	StatusOK = 200
	// StatusCreated http status '201'
	StatusCreated = 201
	// StatusAccepted http status '202'
	StatusAccepted = 202
	// StatusNonAuthoritativeInfo http status '203'
	StatusNonAuthoritativeInfo = 203
	// StatusNoContent http status '204'
	StatusNoContent = 204
	// StatusResetContent http status '205'
	StatusResetContent = 205
	// StatusPartialContent http status '206'
	StatusPartialContent = 206

	// StatusMultipleChoices http status '300'
	StatusMultipleChoices = 300
	// StatusMovedPermanently http status '301'
	StatusMovedPermanently = 301
	// StatusFound http status '302'
	StatusFound = 302
	// StatusSeeOther http status '303'
	StatusSeeOther = 303
	// StatusNotModified http status '304'
	StatusNotModified = 304
	// StatusUseProxy http status '305'
	StatusUseProxy = 305
	// StatusTemporaryRedirect http status '307'
	StatusTemporaryRedirect = 307

	// StatusBadRequest http status '400'
	StatusBadRequest = 400
	// StatusUnauthorized http status '401'
	StatusUnauthorized = 401
	// StatusPaymentRequired http status '402'
	StatusPaymentRequired = 402
	// StatusForbidden http status '403'
	StatusForbidden = 403
	// StatusNotFound http status '404'
	StatusNotFound = 404
	// StatusMethodNotAllowed http status '405'
	StatusMethodNotAllowed = 405
	// StatusNotAcceptable http status '406'
	StatusNotAcceptable = 406
	// StatusProxyAuthRequired http status '407'
	StatusProxyAuthRequired = 407
	// StatusRequestTimeout http status '408'
	StatusRequestTimeout = 408
	// StatusConflict http status '409'
	StatusConflict = 409
	// StatusGone http status '410'
	StatusGone = 410
	// StatusLengthRequired http status '411'
	StatusLengthRequired = 411
	// StatusPreconditionFailed http status '412'
	StatusPreconditionFailed = 412
	// StatusRequestEntityTooLarge http status '413'
	StatusRequestEntityTooLarge = 413
	// StatusRequestURITooLong http status '414'
	StatusRequestURITooLong = 414
	// StatusUnsupportedMediaType http status '415'
	StatusUnsupportedMediaType = 415
	// StatusRequestedRangeNotSatisfiable http status '416'
	StatusRequestedRangeNotSatisfiable = 416
	// StatusExpectationFailed http status '417'
	StatusExpectationFailed = 417
	// StatusTeapot http status '418'
	StatusTeapot = 418
	// StatusPreconditionRequired http status '428'
	StatusPreconditionRequired = 428
	// StatusTooManyRequests http status '429'
	StatusTooManyRequests = 429
	// StatusRequestHeaderFieldsTooLarge http status '431'
	StatusRequestHeaderFieldsTooLarge = 431
	// StatusUnavailableForLegalReasons http status '451'
	StatusUnavailableForLegalReasons = 451

	// StatusInternalServerError http status '500'
	StatusInternalServerError = 500
	// StatusNotImplemented http status '501'
	StatusNotImplemented = 501
	// StatusBadGateway http status '502'
	StatusBadGateway = 502
	// StatusServiceUnavailable http status '503'
	StatusServiceUnavailable = 503
	// StatusGatewayTimeout http status '504'
	StatusGatewayTimeout = 504
	// StatusHTTPVersionNotSupported http status '505'
	StatusHTTPVersionNotSupported = 505
	// StatusNetworkAuthenticationRequired http status '511'
	StatusNetworkAuthenticationRequired = 511
)

var statusText = map[int]string{
	StatusContinue:           "Continue",
	StatusSwitchingProtocols: "Switching Protocols",

	StatusOK:                   "OK",
	StatusCreated:              "Created",
	StatusAccepted:             "Accepted",
	StatusNonAuthoritativeInfo: "Non-Authoritative Information",
	StatusNoContent:            "No Content",
	StatusResetContent:         "Reset Content",
	StatusPartialContent:       "Partial Content",

	StatusMultipleChoices:   "Multiple Choices",
	StatusMovedPermanently:  "Moved Permanently",
	StatusFound:             "Found",
	StatusSeeOther:          "See Other",
	StatusNotModified:       "Not Modified",
	StatusUseProxy:          "Use Proxy",
	StatusTemporaryRedirect: "Temporary Redirect",

	StatusBadRequest:                   "Bad Request",
	StatusUnauthorized:                 "Unauthorized",
	StatusPaymentRequired:              "Payment Required",
	StatusForbidden:                    "Forbidden",
	StatusNotFound:                     "Not Found",
	StatusMethodNotAllowed:             "Method Not Allowed",
	StatusNotAcceptable:                "Not Acceptable",
	StatusProxyAuthRequired:            "Proxy Authentication Required",
	StatusRequestTimeout:               "Request Timeout",
	StatusConflict:                     "Conflict",
	StatusGone:                         "Gone",
	StatusLengthRequired:               "Length Required",
	StatusPreconditionFailed:           "Precondition Failed",
	StatusRequestEntityTooLarge:        "Request Entity Too Large",
	StatusRequestURITooLong:            "Request URI Too Long",
	StatusUnsupportedMediaType:         "Unsupported Media Type",
	StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
	StatusExpectationFailed:            "Expectation Failed",
	StatusTeapot:                       "I'm a teapot",
	StatusPreconditionRequired:         "Precondition Required",
	StatusTooManyRequests:              "Too Many Requests",
	StatusRequestHeaderFieldsTooLarge:  "Request Header Fields Too Large",
	StatusUnavailableForLegalReasons:   "Unavailable For Legal Reasons",

	StatusInternalServerError:           "Internal Server Error",
	StatusNotImplemented:                "Not Implemented",
	StatusBadGateway:                    "Bad Gateway",
	StatusServiceUnavailable:            "Service Unavailable",
	StatusGatewayTimeout:                "Gateway Timeout",
	StatusHTTPVersionNotSupported:       "HTTP Version Not Supported",
	StatusNetworkAuthenticationRequired: "Network Authentication Required",
}

// StatusText returns a text for the HTTP status code. It returns the empty
// string if the code is unknown.
func StatusText(code int) string {
	return statusText[code]
}

// Errors introduced by server.
var (
	errServerPortAlreadyUsed = errors.New("Server can't run, port is already used")
	errServerAlreadyStarted  = errors.New("Server is already started and listening")
	errServerConfigMissing   = errors.New("Empty Config for server")
	errServerHandlerMissing  = errors.New("Handler is missing from server, can't start without handler")
	errServerIsClosed        = errors.New("Can't close the server, propably is already closed or never started")
	errServerRemoveUnix      = errors.New("Unexpected error when trying to remove unix socket file. Addr: %s | Trace: %s")
	errServerChmod           = errors.New("Cannot chmod %#o for %q: %s")
)

// Server the http server
type Server struct {
	*fasthttp.Server
	listener net.Listener
	Config   *config.Server
	tls      bool
	mu       sync.Mutex
}

// newServer returns a pointer to a Server object, and set it's options if any,  nothing more
func newServer(c *config.Server) *Server {
	s := &Server{Server: &fasthttp.Server{Name: config.ServerName}, Config: c}
	return s
}

// SetHandler sets the handler in order to listen on client requests
func (s *Server) SetHandler(mux *serveMux) {
	if s.Server != nil {
		s.Server.Handler = mux.ServeRequest()
	}
}

// IsListening returns true if server is listening/started, otherwise false
func (s *Server) IsListening() bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.listener != nil && s.listener.Addr().String() != ""
}

// IsSecure returns true if server uses TLS, otherwise false
func (s *Server) IsSecure() bool {
	return s.tls
}

// Listener returns the net.Listener which this server (is) listening to
func (s *Server) Listener() net.Listener {
	return s.listener
}

// Host returns the Listener().Addr().String(), if server is not listening it returns the config.ListeningAddr
func (s *Server) Host() (host string) {
	if s.IsListening() {
		return s.Listener().Addr().String()
	}
	return s.Config.ListeningAddr

}

// VirtualHost returns the s.Config.ListeningAddr
//
func (s *Server) VirtualHost() (host string) {
	// check the addr if :8080 do it 0.0.0.0:8080 ,we need the hostname for many cases
	a := s.Config.ListeningAddr
	//check if contains hostname, we need the full host, :8080 should be : 127.0.0.1:8080
	if portIdx := strings.IndexByte(a, ':'); portIdx == 0 {
		// then the : is the first letter, so we dont have setted a hostname, lets set it
		s.Config.ListeningAddr = config.DefaultServerHostname + a
	}
	return s.Config.ListeningAddr
}

// FullHost returns the scheme+host
func (s *Server) FullHost() string {
	scheme := "http://"
	// we need to be able to take that before(for testing &debugging) and after server's listen
	if s.IsSecure() || (s.Config.CertFile != "" && s.Config.KeyFile != "") {
		scheme = "https://"
	}
	return scheme + s.VirtualHost()
}

// Hostname returns the hostname part only, if host == localhost:8080 it will return the localhost
// if server is not listening it returns the config.ListeningAddr's hostname part
func (s *Server) Hostname() (hostname string) {
	if s.IsListening() {
		fullhost := s.Listener().Addr().String()
		hostname = fullhost[0:strings.IndexByte(fullhost, ':')] // no the port
	} else {
		hostname = s.VirtualHostname()
	}
	return
}

// VirtualHostname returns the hostname that user registers, host path maybe differs from the real which is HostString, which taken from a net.listener
func (s *Server) VirtualHostname() (hostname string) {
	hostname = s.Config.ListeningAddr
	if idx := strings.IndexByte(hostname, ':'); idx > 1 { // at least after second char
		hostname = hostname[0:idx]
	} else {
		hostname = config.DefaultServerHostname
	}
	return
}

func (s *Server) listen() error {
	if s.IsListening() {
		return errServerAlreadyStarted.Return()
	}
	listener, err := net.Listen("tcp4", s.Config.ListeningAddr)

	if err != nil {
		return err
	}

	go s.serve(listener) // we don't catch underline errors, we catched all already
	return nil

}

func (s *Server) listenUNIX() error {

	mode := s.Config.Mode
	addr := s.Config.ListeningAddr

	if errOs := os.Remove(addr); errOs != nil && !os.IsNotExist(errOs) {
		return errServerRemoveUnix.Format(s.Config.ListeningAddr, errOs.Error())
	}

	listener, err := net.Listen("unix", addr)

	if err != nil {
		return errServerPortAlreadyUsed.Return()
	}

	if err = os.Chmod(addr, mode); err != nil {
		return errServerChmod.Format(mode, addr, err.Error())
	}

	go s.serve(listener) // we don't catch underline errors, we catched all already
	return nil

}

//Serve just serves a listener, it is a blocking action, plugin.PostListen is not fired here.
func (s *Server) serve(l net.Listener) error {
	s.mu.Lock()
	s.listener = l
	s.mu.Unlock()
	if s.Config.CertFile != "" && s.Config.KeyFile != "" {
		s.tls = true
		return s.Server.ServeTLS(s.listener, s.Config.CertFile, s.Config.KeyFile)
	}
	s.tls = false
	return s.Server.Serve(s.listener)
}

// Open opens/starts/runs/listens (to) the server, listen tls if Cert && Key is registed, listenUNIX if Mode is registed, otherwise listen
func (s *Server) Open() error {
	if s.IsListening() {
		return errServerAlreadyStarted.Return()
	}

	if s.Config.ListeningAddr == "" {
		return errServerConfigMissing.Return()
	}

	if s.Handler == nil {
		return errServerHandlerMissing.Return()
	}

	// check the addr if :8080 do it 0.0.0.0:8080 ,we need the hostname for many cases
	a := s.Config.ListeningAddr
	//check if contains hostname, we need the full host, :8080 should be : 127.0.0.1:8080
	if portIdx := strings.IndexByte(a, ':'); portIdx == 0 {
		// then the : is the first letter, so we dont have setted a hostname, lets set it
		s.Config.ListeningAddr = config.DefaultServerHostname + a
	}

	if s.Config.Mode > 0 {
		return s.listenUNIX()
	}
	return s.listen()
}

// close closes the server
func (s *Server) Close() (err error) {
	if !s.IsListening() {
		return errServerIsClosed.Return()
	}

	err = s.listener.Close()

	return
}

// errHandler returns na error with message: 'Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context)
// It seems to be a  +type Points to: +pointer.'
var errHandler = errors.New("Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context)\n It seems to be a  %T Points to: %v.")

type (
	// Handler the main Iris Handler interface.
	Handler interface {
		Serve(ctx *Context)
	}

	// HandlerFunc type is an adapter to allow the use of
	// ordinary functions as HTTP handlers.  If f is a function
	// with the appropriate signature, HandlerFunc(f) is a
	// Handler that calls f.
	HandlerFunc func(*Context)
	// Middleware is just a slice of Handler []func(c *Context)
	Middleware []Handler

	// HandlerAPI empty interface used for .API
	HandlerAPI interface{}
)

// Serve implements the Handler, is like ServeHTTP but for Iris
func (h HandlerFunc) Serve(ctx *Context) {
	h(ctx)
}

// ToHandler converts an httapi.Handler or http.HandlerFunc to an iris.Handler
func ToHandler(handler interface{}) Handler {
	//this is not the best way to do it, but I dont have any options right now.
	switch handler.(type) {
	case Handler:
		//it's already an iris handler
		return handler.(Handler)
	case http.Handler:
		//it's http.Handler
		h := fasthttpadaptor.NewFastHTTPHandlerFunc(handler.(http.Handler).ServeHTTP)

		return ToHandlerFastHTTP(h)
	case func(http.ResponseWriter, *http.Request):
		//it's http.HandlerFunc
		h := fasthttpadaptor.NewFastHTTPHandlerFunc(handler.(func(http.ResponseWriter, *http.Request)))
		return ToHandlerFastHTTP(h)
	default:
		panic(errHandler.Format(handler, handler))
	}
}

// ToHandlerFunc converts an http.Handler or http.HandlerFunc to an iris.HandlerFunc
func ToHandlerFunc(handler interface{}) HandlerFunc {
	return ToHandler(handler).Serve
}

// ToHandlerFastHTTP converts an fasthttp.RequestHandler to an iris.Handler
func ToHandlerFastHTTP(h fasthttp.RequestHandler) Handler {
	return HandlerFunc((func(ctx *Context) {
		h(ctx.RequestCtx)
	}))
}

// convertToHandlers just make []HandlerFunc to []Handler, although HandlerFunc and Handler are the same
// we need this on some cases we explicit want a interface Handler, it is useless for users.
func convertToHandlers(handlersFn []HandlerFunc) []Handler {
	hlen := len(handlersFn)
	mlist := make([]Handler, hlen)
	for i := 0; i < hlen; i++ {
		mlist[i] = Handler(handlersFn[i])
	}
	return mlist
}

// joinMiddleware uses to create a copy of all middleware and return them in order to use inside the node
func joinMiddleware(middleware1 Middleware, middleware2 Middleware) Middleware {
	nowLen := len(middleware1)
	totalLen := nowLen + len(middleware2)
	// create a new slice of middleware in order to store all handlers, the already handlers(middleware) and the new
	newMiddleware := make(Middleware, totalLen)
	//copy the already middleware to the just created
	copy(newMiddleware, middleware1)
	//start from there we finish, and store the new middleware too
	copy(newMiddleware[nowLen:], middleware2)
	return newMiddleware
}

func profileMiddleware(debugPath string) Middleware {
	htmlMiddleware := HandlerFunc(func(ctx *Context) {
		ctx.SetContentType(contentHTML + "; charset=" + config.Charset)
		ctx.Next()
	})
	indexHandler := ToHandlerFunc(pprof.Index)
	cmdlineHandler := ToHandlerFunc(pprof.Cmdline)
	profileHandler := ToHandlerFunc(pprof.Profile)
	symbolHandler := ToHandlerFunc(pprof.Symbol)
	goroutineHandler := ToHandlerFunc(pprof.Handler("goroutine"))
	heapHandler := ToHandlerFunc(pprof.Handler("heap"))
	threadcreateHandler := ToHandlerFunc(pprof.Handler("threadcreate"))
	debugBlockHandler := ToHandlerFunc(pprof.Handler("block"))

	return Middleware{htmlMiddleware, HandlerFunc(func(ctx *Context) {
		action := ctx.Param("action")
		if len(action) > 1 {
			if strings.Contains(action, "cmdline") {
				cmdlineHandler.Serve((ctx))
			} else if strings.Contains(action, "profile") {
				profileHandler.Serve(ctx)
			} else if strings.Contains(action, "symbol") {
				symbolHandler.Serve(ctx)
			} else if strings.Contains(action, "goroutine") {
				goroutineHandler.Serve(ctx)
			} else if strings.Contains(action, "heap") {
				heapHandler.Serve(ctx)
			} else if strings.Contains(action, "threadcreate") {
				threadcreateHandler.Serve(ctx)
			} else if strings.Contains(action, "debug/block") {
				debugBlockHandler.Serve(ctx)
			}
		} else {
			indexHandler.Serve(ctx)
		}
	})}
}

const (
	// parameterStartByte is very used on the node, it's just contains the byte for the ':' rune/char
	parameterStartByte = byte(':')
	// slashByte is just a byte of '/' rune/char
	slashByte = byte('/')
	// slash is just a string of "/"
	slash = "/"
	// matchEverythingByte is just a byte of '*" rune/char
	matchEverythingByte = byte('*')

	isStatic entryCase = iota
	isRoot
	hasParams
	matchEverything
)

type (
	// PathParameter is a struct which contains Key and Value, used for named path parameters
	PathParameter struct {
		Key   string
		Value string
	}

	// PathParameters type for a slice of PathParameter
	// Tt's a slice of PathParameter type, because it's faster than map
	PathParameters []PathParameter

	// entryCase is the type which the type of muxEntryusing in order to determinate what type (parameterized, anything, static...) is the perticular node
	entryCase uint8

	// muxEntry is the node of a tree of the routes,
	// in order to learn how this is working, google 'trie' or watch this lecture: https://www.youtube.com/watch?v=uhAUk63tLRM
	// this method is used by the BSD's kernel also
	muxEntry struct {
		part        string
		entryCase   entryCase
		hasWildNode bool
		tokens      string
		nodes       []*muxEntry
		middleware  Middleware
		precedence  uint64
		paramsLen   uint8
	}
)

var (
	errMuxEntryConflictsWildcard           = errors.New("Router: Path's part: '%s' conflicts with wildcard '%s' in the route path: '%s' !")
	errMuxEntryMiddlewareAlreadyExists     = errors.New("Router: Middleware were already registered for the path: '%s' !")
	errMuxEntryInvalidWildcard             = errors.New("Router: More than one wildcard found in the path part: '%s' in route's path: '%s' !")
	errMuxEntryConflictsExistingWildcard   = errors.New("Router: Wildcard for route path: '%s' conflicts with existing children in route path: '%s' !")
	errMuxEntryWildcardUnnamed             = errors.New("Router: Unnamed wildcard found in path: '%s' !")
	errMuxEntryWildcardInvalidPlace        = errors.New("Router: Wildcard is only allowed at the end of the path, in the route path: '%s' !")
	errMuxEntryWildcardConflictsMiddleware = errors.New("Router: Wildcard  conflicts with existing middleware for the route path: '%s' !")
	errMuxEntryWildcardMissingSlash        = errors.New("Router: No slash(/) were found before wildcard in the route path: '%s' !")
)

// Get returns a value from a key inside this Parameters
// If no parameter with this key given then it returns an empty string
func (params PathParameters) Get(key string) string {
	for _, p := range params {
		if p.Key == key {
			return p.Value
		}
	}
	return ""
}

// String returns a string implementation of all parameters that this PathParameters object keeps
// hasthe form of key1=value1,key2=value2...
func (params PathParameters) String() string {
	var buff bytes.Buffer
	for i := range params {
		buff.WriteString(params[i].Key)
		buff.WriteString("=")
		buff.WriteString(params[i].Value)
		if i < len(params)-1 {
			buff.WriteString(",")
		}

	}
	return buff.String()
}

// ParseParams receives a string and returns PathParameters (slice of PathParameter)
// received string must have this form:  key1=value1,key2=value2...
func ParseParams(str string) PathParameters {
	_paramsstr := strings.Split(str, ",")
	if len(_paramsstr) == 0 {
		return nil
	}

	params := make(PathParameters, 0) // PathParameters{}

	//	for i := 0; i < len(_paramsstr); i++ {
	for i := range _paramsstr {
		idxOfEq := strings.IndexRune(_paramsstr[i], '=')
		if idxOfEq == -1 {
			//error
			return nil
		}

		key := _paramsstr[i][:idxOfEq]
		val := _paramsstr[i][idxOfEq+1:]
		params = append(params, PathParameter{key, val})
	}
	return params
}

// getParamsLen returns the parameters length from a given path
func getParamsLen(path string) uint8 {
	var n uint
	for i := 0; i < len(path); i++ {
		if path[i] != ':' && path[i] != '*' { // ParameterStartByte & MatchEverythingByte
			continue
		}
		n++
	}
	if n >= 255 {
		return 255
	}
	return uint8(n)
}

//
// Copyright (c) 2013 Julien Schmidt, Copyright (c) 2016 Gerasimos Maropoulos All rights reserved.
//
// add adds a muxEntry to the existing muxEntry or to the tree if no muxEntry has the prefix of
func (e *muxEntry) add(path string, middleware Middleware) error {
	fullPath := path
	e.precedence++
	numParams := getParamsLen(path)

	if len(e.part) > 0 || len(e.nodes) > 0 {
	loop:
		for {
			if numParams > e.paramsLen {
				e.paramsLen = numParams
			}

			i := 0
			max := utils.FindLower(len(path), len(e.part))
			for i < max && path[i] == e.part[i] {
				i++
			}

			if i < len(e.part) {
				node := muxEntry{
					part:        e.part[i:],
					hasWildNode: e.hasWildNode,
					tokens:      e.tokens,
					nodes:       e.nodes,
					middleware:  e.middleware,
					precedence:  e.precedence - 1,
				}

				for i := range node.nodes {
					if node.nodes[i].paramsLen > node.paramsLen {
						node.paramsLen = node.nodes[i].paramsLen
					}
				}

				e.nodes = []*muxEntry{&node}
				e.tokens = string([]byte{e.part[i]})
				e.part = path[:i]
				e.middleware = nil
				e.hasWildNode = false
			}

			if i < len(path) {
				path = path[i:]

				if e.hasWildNode {
					e = e.nodes[0]
					e.precedence++

					if numParams > e.paramsLen {
						e.paramsLen = numParams
					}
					numParams--

					if len(path) >= len(e.part) && e.part == path[:len(e.part)] {

						if len(e.part) >= len(path) || path[len(e.part)] == '/' {
							continue loop
						}
					}
					return errMuxEntryConflictsWildcard.Format(path, e.part, fullPath)
				}

				c := path[0]

				if e.entryCase == hasParams && c == '/' && len(e.nodes) == 1 {
					e = e.nodes[0]
					e.precedence++
					continue loop
				}
				//we need the i here to be re-setting, so use the same i variable as we declare it on line 176
				for i := range e.tokens {
					if c == e.tokens[i] {
						i = e.precedenceTo(i)
						e = e.nodes[i]
						continue loop
					}
				}

				if c != parameterStartByte && c != matchEverythingByte {

					e.tokens += string([]byte{c})
					node := &muxEntry{
						paramsLen: numParams,
					}
					e.nodes = append(e.nodes, node)
					e.precedenceTo(len(e.tokens) - 1)
					e = node
				}
				e.addNode(numParams, path, fullPath, middleware)
				return nil

			} else if i == len(path) {
				if e.middleware != nil {
					return errMuxEntryMiddlewareAlreadyExists.Format(fullPath)
				}
				e.middleware = middleware
			}
			return nil
		}
	} else {
		e.addNode(numParams, path, fullPath, middleware)
		e.entryCase = isRoot
	}
	return nil
}

// addNode adds a muxEntry as children to other muxEntry
func (e *muxEntry) addNode(numParams uint8, path string, fullPath string, middleware Middleware) error {
	var offset int

	for i, max := 0, len(path); numParams > 0; i++ {
		c := path[i]
		if c != parameterStartByte && c != matchEverythingByte {
			continue
		}

		end := i + 1
		for end < max && path[end] != slashByte {
			switch path[end] {
			case parameterStartByte, matchEverythingByte:
				/*
				   panic("only one wildcard per path segment is allowed, has: '" +
				   	path[i:] + "' in path '" + fullPath + "'")
				*/
				return errMuxEntryInvalidWildcard.Format(path[i:], fullPath)
			default:
				end++
			}
		}

		if len(e.nodes) > 0 {
			return errMuxEntryConflictsExistingWildcard.Format(path[i:end], fullPath)
		}

		if end-i < 2 {
			return errMuxEntryWildcardUnnamed.Format(fullPath)
		}

		if c == parameterStartByte {

			if i > 0 {
				e.part = path[offset:i]
				offset = i
			}

			child := &muxEntry{
				entryCase: hasParams,
				paramsLen: numParams,
			}
			e.nodes = []*muxEntry{child}
			e.hasWildNode = true
			e = child
			e.precedence++
			numParams--

			if end < max {
				e.part = path[offset:end]
				offset = end

				child := &muxEntry{
					paramsLen:  numParams,
					precedence: 1,
				}
				e.nodes = []*muxEntry{child}
				e = child
			}

		} else {
			if end != max || numParams > 1 {
				return errMuxEntryWildcardInvalidPlace.Format(fullPath)
			}

			if len(e.part) > 0 && e.part[len(e.part)-1] == '/' {
				return errMuxEntryWildcardConflictsMiddleware.Format(fullPath)
			}

			i--
			if path[i] != slashByte {
				return errMuxEntryWildcardMissingSlash.Format(fullPath)
			}

			e.part = path[offset:i]

			child := &muxEntry{
				hasWildNode: true,
				entryCase:   matchEverything,
				paramsLen:   1,
			}
			e.nodes = []*muxEntry{child}
			e.tokens = string(path[i])
			e = child
			e.precedence++

			child = &muxEntry{
				part:       path[i:],
				entryCase:  matchEverything,
				paramsLen:  1,
				middleware: middleware,
				precedence: 1,
			}
			e.nodes = []*muxEntry{child}

			return nil
		}
	}

	e.part = path[offset:]
	e.middleware = middleware

	return nil
}

// get is used by the Router, it finds and returns the correct muxEntry for a path
func (e *muxEntry) get(path string, _params PathParameters) (middleware Middleware, params PathParameters, mustRedirect bool) {
	params = _params
loop:
	for {
		if len(path) > len(e.part) {
			if path[:len(e.part)] == e.part {
				path = path[len(e.part):]

				if !e.hasWildNode {
					c := path[0]
					for i := range e.tokens {
						if c == e.tokens[i] {
							e = e.nodes[i]
							continue loop
						}
					}

					mustRedirect = (path == slash && e.middleware != nil)
					return
				}

				e = e.nodes[0]
				switch e.entryCase {
				case hasParams:

					end := 0
					for end < len(path) && path[end] != '/' {
						end++
					}

					if cap(params) < int(e.paramsLen) {
						params = make(PathParameters, 0, e.paramsLen)
					}
					i := len(params)
					params = params[:i+1]
					params[i].Key = e.part[1:]
					params[i].Value = path[:end]

					if end < len(path) {
						if len(e.nodes) > 0 {
							path = path[end:]
							e = e.nodes[0]
							continue loop
						}

						mustRedirect = (len(path) == end+1)
						return
					}

					if middleware = e.middleware; middleware != nil {
						return
					} else if len(e.nodes) == 1 {
						e = e.nodes[0]
						mustRedirect = (e.part == slash && e.middleware != nil)
					}

					return

				case matchEverything:
					if cap(params) < int(e.paramsLen) {
						params = make(PathParameters, 0, e.paramsLen)
					}
					i := len(params)
					params = params[:i+1]
					params[i].Key = e.part[2:]
					params[i].Value = path

					middleware = e.middleware
					return

				default:
					return
				}
			}
		} else if path == e.part {
			if middleware = e.middleware; middleware != nil {
				return
			}

			if path == slash && e.hasWildNode && e.entryCase != isRoot {
				mustRedirect = true
				return
			}

			for i := range e.tokens {
				if e.tokens[i] == '/' {
					e = e.nodes[i]
					mustRedirect = (len(e.part) == 1 && e.middleware != nil) ||
						(e.entryCase == matchEverything && e.nodes[0].middleware != nil)
					return
				}
			}

			return
		}

		mustRedirect = (path == slash) ||
			(len(e.part) == len(path)+1 && e.part[len(path)] == '/' &&
				path == e.part[:len(e.part)-1] && e.middleware != nil)
		return
	}
}

// precedenceTo just adds the priority of this muxEntry by an index
func (e *muxEntry) precedenceTo(index int) int {
	e.nodes[index].precedence++
	_precedence := e.nodes[index].precedence

	newindex := index
	for newindex > 0 && e.nodes[newindex-1].precedence < _precedence {
		tmpN := e.nodes[newindex-1]
		e.nodes[newindex-1] = e.nodes[newindex]
		e.nodes[newindex] = tmpN

		newindex--
	}

	if newindex != index {
		e.tokens = e.tokens[:newindex] +
			e.tokens[index:index+1] +
			e.tokens[newindex:index] + e.tokens[index+1:]
	}

	return newindex
}

//
//
//

type (
	// Route contains some useful information about a route
	Route interface {
		// Name returns the name of the route
		Name() string
		// Subdomain returns the subdomain,if any
		Subdomain() string
		// Method returns the http method
		Method() string
		// Path returns the path
		Path() string
		// Middleware returns the slice of Handler([]Handler) registed to this route
		Middleware() Middleware
	}

	route struct {
		// if no name given then it's the subdomain+path
		name           string
		subdomain      string
		method         []byte
		methodStr      string
		path           string
		middleware     Middleware
		formattedPath  string
		formattedParts int
	}

	bySubdomain []*route
)

// Sorting happens when the mux's request handler initialized
func (s bySubdomain) Len() int {
	return len(s)
}
func (s bySubdomain) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}
func (s bySubdomain) Less(i, j int) bool {
	return len(s[i].Subdomain()) > len(s[j].Subdomain())
}

var _ Route = &route{}

func newRoute(method []byte, subdomain string, path string, middleware Middleware) *route {
	r := &route{name: path + subdomain, method: method, subdomain: subdomain, path: path, middleware: middleware}
	r.formatPath()
	return r
}

func (r *route) formatPath() {
	// we don't care about performance here.
	n1Len := strings.Count(r.path, ":")
	isMatchEverything := len(r.path) > 0 && r.path[len(r.path)-1] == matchEverythingByte
	if n1Len == 0 && !isMatchEverything {
		// its a static
		return
	}
	if n1Len == 0 && isMatchEverything {
		//if we have something like: /mypath/anything/* -> /mypatch/anything/%v
		r.formattedPath = r.path[0:len(r.path)-2] + "%v"
		r.formattedParts++
		return
	}

	tempPath := r.path
	splittedN1 := strings.Split(r.path, "/")

	for _, v := range splittedN1 {
		if len(v) > 0 {
			if v[0] == ':' || v[0] == matchEverythingByte {
				r.formattedParts++
				tempPath = strings.Replace(tempPath, v, "%v", -1) // n1Len, but let it we don't care about performance here.
			}
		}

	}
	r.formattedPath = tempPath
}

func (r *route) setName(newName string) {
	r.name = newName
}

func (r route) Name() string {
	return r.name
}

func (r route) Subdomain() string {
	return r.subdomain
}

func (r route) Method() string {
	if r.methodStr == "" {
		r.methodStr = string(r.method)
	}
	return r.methodStr
}

func (r route) Path() string {
	return r.path
}

func (r route) Middleware() Middleware {
	return r.middleware
}

const (
	// subdomainIndicator where './' exists in a registed path then it contains subdomain
	subdomainIndicator = "./"
	// dynamicSubdomainIndicator where a registed path starts with '*.' then it contains a dynamic subdomain, if subdomain == "*." then its dynamic
	dynamicSubdomainIndicator = "*."
)

type (
	muxTree struct {
		method []byte
		// subdomain is empty for default-hostname routes,
		// ex: mysubdomain.
		subdomain string
		entry     *muxEntry
		next      *muxTree
	}

	serveMux struct {
		cPool   *sync.Pool
		tree    *muxTree
		lookups []*route

		api           *muxAPI
		errorHandlers map[int]Handler
		logger        *logger.Logger
		// the main server host's name, ex:  localhost, 127.0.0.1, iris-go.com
		hostname string
		// if any of the trees contains not empty subdomain
		hosts bool
		// if false then searching by unescaped path
		// defaults to true
		escapePath bool
		// if false then the /something it's not the same as /something/
		// defaults to true
		correctPath bool
		mu          sync.Mutex
	}
)

func newServeMux(contextPool sync.Pool, logger *logger.Logger) *serveMux {
	mux := &serveMux{
		cPool:         &contextPool,
		lookups:       make([]*route, 0),
		errorHandlers: make(map[int]Handler, 0),
		hostname:      "127.0.0.1",
		escapePath:    !config.DefaultDisablePathEscape,
		correctPath:   !config.DefaultDisablePathCorrection,
		logger:        logger,
	}

	return mux
}

func (mux *serveMux) setHostname(h string) {
	mux.hostname = h
}

func (mux *serveMux) setEscapePath(b bool) {
	mux.escapePath = b
}

func (mux *serveMux) setCorrectPath(b bool) {
	mux.correctPath = b
}

// registerError registers a handler to a http status
func (mux *serveMux) registerError(statusCode int, handler Handler) {
	mux.mu.Lock()
	func(statusCode int, handler Handler) {
		mux.errorHandlers[statusCode] = HandlerFunc(func(ctx *Context) {
			ctx.ResetBody()
			ctx.SetStatusCode(statusCode)
			handler.Serve(ctx)
		})
	}(statusCode, handler)
	mux.mu.Unlock()
}

// fireError fires an error
func (mux *serveMux) fireError(statusCode int, ctx *Context) {
	mux.mu.Lock()
	errHandler := mux.errorHandlers[statusCode]
	if errHandler == nil {
		errHandler = HandlerFunc(func(ctx *Context) {
			ctx.ResetBody()
			ctx.SetStatusCode(statusCode)
			ctx.SetBodyString(statusText[statusCode])
		})
		mux.errorHandlers[statusCode] = errHandler
	}
	mux.mu.Unlock()

	errHandler.Serve(ctx)
}

func (mux *serveMux) getTree(method []byte, subdomain string) (tree *muxTree) {
	tree = mux.tree
	for tree != nil {
		if bytes.Equal(tree.method, method) && tree.subdomain == subdomain {
			return
		}
		tree = tree.next
	}
	// tree is nil here, return that.
	return
}

func (mux *serveMux) register(method []byte, subdomain string, path string, middleware Middleware) *route {
	mux.mu.Lock()
	defer mux.mu.Unlock()

	if subdomain != "" {
		mux.hosts = true
	}

	// add to the lookups, it's just a collection of routes information
	lookup := newRoute(method, subdomain, path, middleware)
	mux.lookups = append(mux.lookups, lookup)

	return lookup

}

// build collects all routes info and adds them to the registry in order to be served from the request handler
// this happens once when server is setting the mux's handler.
func (mux *serveMux) build() {

	sort.Sort(bySubdomain(mux.lookups))
	for _, r := range mux.lookups {
		// add to the registry tree
		tree := mux.getTree(r.method, r.subdomain)
		if tree == nil {
			//first time we register a route to this method with this domain
			tree = &muxTree{method: r.method, subdomain: r.subdomain, entry: &muxEntry{}, next: nil}
			if mux.tree == nil {
				// it's the first entry
				mux.tree = tree
			} else {
				// find the last tree and make the .next to the tree we created before
				lastTree := mux.tree
				for lastTree != nil {
					if lastTree.next == nil {
						lastTree.next = tree
						break
					}
					lastTree = lastTree.next
				}
			}
		}
		// I decide that it's better to explicit give subdomain and a path to it than registedPath(mysubdomain./something) now its: subdomain: mysubdomain., path: /something
		// we have different tree for each of subdomains, now you can use everything you can use with the normal paths ( before you couldn't set /any/*path)
		if err := tree.entry.add(r.path, r.middleware); err != nil {
			mux.logger.Panic(err.Error())
		}
	}
}

func (mux *serveMux) lookup(routeName string) *route {
	for i := range mux.lookups {
		if r := mux.lookups[i]; r.name == routeName {
			return r
		}
	}
	return nil
}

func (mux *serveMux) ServeRequest() fasthttp.RequestHandler {

	// initialize the router once
	mux.build()
	// optimize this once once, we could do that: context.RequestPath(mux.escapePath), but we lose some nanoseconds on if :)
	getRequestPath := func(reqCtx *fasthttp.RequestCtx) string {
		return utils.BytesToString(reqCtx.Path())
	}
	if !mux.escapePath {
		getRequestPath = func(reqCtx *fasthttp.RequestCtx) string { return utils.BytesToString(reqCtx.RequestURI()) }
	}

	return func(reqCtx *fasthttp.RequestCtx) {
		context := mux.cPool.Get().(*Context)
		context.Reset(reqCtx)

		routePath := getRequestPath(reqCtx)
		tree := mux.tree
		for tree != nil {
			if !bytes.Equal(tree.method, reqCtx.Method()) {
				// we break any CORS OPTIONS method
				// but for performance reasons if user wants http method OPTIONS to be served
				// then must register it with .Options(...)
				tree = tree.next
				continue
			}
			// we have at least one subdomain on the root
			if mux.hosts && tree.subdomain != "" {
				// context.VirtualHost() is a slow method because it makes string.Replaces but user can understand that if subdomain then server will have some nano/or/milleseconds performance cost
				requestHost := context.VirtualHostname()
				if requestHost != mux.hostname {
					// we have a subdomain
					if strings.Index(tree.subdomain, dynamicSubdomainIndicator) != -1 {
					} else {
						// mux.host = iris-go.com:8080, the subdomain for example is api.,
						// so the host must be api.iris-go.com:8080
						if tree.subdomain+mux.hostname != requestHost {
							// go to the next tree, we have a subdomain but it is not the correct
							tree = tree.next
							continue
						}

					}
				} else {
					//("it's subdomain but the request is the same as the listening addr mux.host == requestHost =>" + mux.host + "=" + requestHost + " ____ and tree's subdomain was: " + tree.subdomain)
					tree = tree.next
					continue
				}
			}
			middleware, params, mustRedirect := tree.entry.get(routePath, context.Params) // pass the parameters here for 0 allocation
			if middleware != nil {
				// ok we found the correct route, serve it and exit entirely from here
				context.Params = params
				context.middleware = middleware
				//ctx.Request.Header.SetUserAgentBytes(DefaultUserAgent)
				context.Do()
				mux.cPool.Put(context)
				return
			} else if mustRedirect && mux.correctPath && !bytes.Equal(reqCtx.Method(), methodConnectBytes) {

				reqPath := routePath
				pathLen := len(reqPath)

				if pathLen > 1 {

					if reqPath[pathLen-1] == '/' {
						reqPath = reqPath[:pathLen-1] //remove the last /
					} else {
						//it has path prefix, it doesn't ends with / and it hasn't be found, then just add the slash
						reqPath = reqPath + "/"
					}

					context.Request.URI().SetPath(reqPath)
					urlToRedirect := utils.BytesToString(context.Request.RequestURI())

					context.Redirect(urlToRedirect, StatusMovedPermanently) //	StatusMovedPermanently
					// RFC2616 recommends that a short note "SHOULD" be included in the
					// response because older user agents may not understand 301/307.
					// Shouldn't send the response for POST or HEAD; that leaves GET.
					if bytes.Equal(tree.method, methodGetBytes) {
						note := "<a href=\"" + utils.HTMLEscape(urlToRedirect) + "\">Moved Permanently</a>.\n"
						context.Write(note)
					}
					mux.cPool.Put(context)
					return
				}
			}
			// not found
			break
		}
		mux.fireError(StatusNotFound, context)
		mux.cPool.Put(context)
	}
}