/********************************
*** Multiplexer for Go        ***
*** Bone is under MIT license ***
*** Code by CodingFerret      ***
*** github.com/go-zoo         ***
*********************************/

package bone

import (
	"net/http"
	"net/url"
	"strings"
)

// ListenAndServe wrapper
func (m *Mux) ListenAndServe(port string) error {
	return http.ListenAndServe(port, m)
}

func (m *Mux) parse(rw http.ResponseWriter, req *http.Request) bool {
	for _, r := range m.Routes[req.Method] {
		ok := r.parse(rw, req)
		if ok {
			return true
		}
	}
	// If no HEAD method, default to GET
	if req.Method == "HEAD" {
		for _, r := range m.Routes["GET"] {
			ok := r.parse(rw, req)
			if ok {
				return true
			}
		}
	}
	return false
}

// StaticRoute check if the request path is for Static route
func (m *Mux) staticRoute(rw http.ResponseWriter, req *http.Request) bool {
	for _, s := range m.Routes[static] {
		if len(req.URL.Path) >= s.Size {
			if req.URL.Path[:s.Size] == s.Path {
				s.Handler.ServeHTTP(rw, req)
				return true
			}
		}
	}
	return false
}

// HandleNotFound handle when a request does not match a registered handler.
func (m *Mux) HandleNotFound(rw http.ResponseWriter, req *http.Request) {
	if m.notFound != nil {
		m.notFound.ServeHTTP(rw, req)
	} else {
		http.NotFound(rw, req)
	}
}

// Check if the path don't end with a /
func (m *Mux) validate(rw http.ResponseWriter, req *http.Request) bool {
	plen := len(req.URL.Path)
	if plen > 1 && req.URL.Path[plen-1:] == "/" {
		cleanURL(&req.URL.Path)
		rw.Header().Set("Location", req.URL.String())
		rw.WriteHeader(http.StatusFound)
		return true
	}
	// Retry to find a route that match
	return m.parse(rw, req)
}

func valid(path string) bool {
	plen := len(path)
	if plen > 1 && path[plen-1:] == "/" {
		return false
	}
	return true
}

// Clean url path
func cleanURL(url *string) {
	ulen := len((*url))
	if ulen > 1 {
		if (*url)[ulen-1:] == "/" {
			*url = (*url)[:ulen-1]
			cleanURL(url)
		}
	}
}

// GetValue return the key value, of the current *http.Request
func GetValue(req *http.Request, key string) string {
	return GetAllValues(req)[key]
}

// GetRequestRoute returns the route of given Request
func (m *Mux) GetRequestRoute(req *http.Request) string {
	cleanURL(&req.URL.Path)
	for _, r := range m.Routes[req.Method] {
		if r.Atts != 0 {
			if r.Atts&SUB != 0 {
				return r.Handler.(*Mux).GetRequestRoute(req)
			}
			if r.Match(req) {
				return r.Path
			}
		}
		if req.URL.Path == r.Path {
			return r.Path
		}
	}

	for _, s := range m.Routes[static] {
		if len(req.URL.Path) >= s.Size {
			if req.URL.Path[:s.Size] == s.Path {
				return s.Path
			}
		}
	}

	return "NotFound"
}

// GetQuery return the key value, of the current *http.Request query
func GetQuery(req *http.Request, key string) []string {
	if ok, value := extractQueries(req); ok {
		return value[key]
	}
	return nil
}

// GetAllQueries return all queries of the current *http.Request
func GetAllQueries(req *http.Request) map[string][]string {
	if ok, values := extractQueries(req); ok {
		return values
	}
	return nil
}

func extractQueries(req *http.Request) (bool, map[string][]string) {
	if q, err := url.ParseQuery(req.URL.RawQuery); err == nil {
		var queries = make(map[string][]string)
		for k, v := range q {
			for _, item := range v {
				values := strings.Split(item, ",")
				queries[k] = append(queries[k], values...)
			}
		}
		return true, queries
	}
	return false, nil
}

func (m *Mux) otherMethods(rw http.ResponseWriter, req *http.Request) bool {
	for _, met := range method {
		if met != req.Method {
			for _, r := range m.Routes[met] {
				ok := r.exists(rw, req)
				if ok {
					rw.WriteHeader(http.StatusMethodNotAllowed)
					return true
				}
			}
		}
	}
	return false
}
