package api

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"

	"github.com/docker/libnetwork"
	"github.com/docker/libnetwork/netlabel"
	"github.com/docker/libnetwork/netutils"
	"github.com/docker/libnetwork/types"
	"github.com/gorilla/mux"
)

var (
	successResponse  = responseStatus{Status: "Success", StatusCode: http.StatusOK}
	createdResponse  = responseStatus{Status: "Created", StatusCode: http.StatusCreated}
	mismatchResponse = responseStatus{Status: "Body/URI parameter mismatch", StatusCode: http.StatusBadRequest}
	badQueryResponse = responseStatus{Status: "Unsupported query", StatusCode: http.StatusBadRequest}
)

const (
	// Resource name regex
	// Gorilla mux encloses the passed pattern with '^' and '$'. So we need to do some tricks
	// to have mux eventually build a query regex which matches empty or word string (`^$|[\w]+`)
	regex = "[a-zA-Z_0-9-]+"
	qregx = "$|" + regex
	// Router URL variable definition
	nwName   = "{" + urlNwName + ":" + regex + "}"
	nwNameQr = "{" + urlNwName + ":" + qregx + "}"
	nwID     = "{" + urlNwID + ":" + regex + "}"
	nwPIDQr  = "{" + urlNwPID + ":" + qregx + "}"
	epName   = "{" + urlEpName + ":" + regex + "}"
	epNameQr = "{" + urlEpName + ":" + qregx + "}"
	epID     = "{" + urlEpID + ":" + regex + "}"
	epPIDQr  = "{" + urlEpPID + ":" + qregx + "}"
	sbID     = "{" + urlSbID + ":" + regex + "}"
	sbPIDQr  = "{" + urlSbPID + ":" + qregx + "}"
	cnIDQr   = "{" + urlCnID + ":" + qregx + "}"
	cnPIDQr  = "{" + urlCnPID + ":" + qregx + "}"

	// Internal URL variable name.They can be anything as
	// long as they do not collide with query fields.
	urlNwName = "network-name"
	urlNwID   = "network-id"
	urlNwPID  = "network-partial-id"
	urlEpName = "endpoint-name"
	urlEpID   = "endpoint-id"
	urlEpPID  = "endpoint-partial-id"
	urlSbID   = "sandbox-id"
	urlSbPID  = "sandbox-partial-id"
	urlCnID   = "container-id"
	urlCnPID  = "container-partial-id"
)

// NewHTTPHandler creates and initialize the HTTP handler to serve the requests for libnetwork
func NewHTTPHandler(c libnetwork.NetworkController) func(w http.ResponseWriter, req *http.Request) {
	h := &httpHandler{c: c}
	h.initRouter()
	return h.handleRequest
}

type responseStatus struct {
	Status     string
	StatusCode int
}

func (r *responseStatus) isOK() bool {
	return r.StatusCode == http.StatusOK || r.StatusCode == http.StatusCreated
}

type processor func(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus)

type httpHandler struct {
	c libnetwork.NetworkController
	r *mux.Router
}

func (h *httpHandler) handleRequest(w http.ResponseWriter, req *http.Request) {
	// Make sure the service is there
	if h.c == nil {
		http.Error(w, "NetworkController is not available", http.StatusServiceUnavailable)
		return
	}

	// Get handler from router and execute it
	h.r.ServeHTTP(w, req)
}

func (h *httpHandler) initRouter() {
	m := map[string][]struct {
		url string
		qrs []string
		fct processor
	}{
		"GET": {
			// Order matters
			{"/networks", []string{"name", nwNameQr}, procGetNetworks},
			{"/networks", []string{"partial-id", nwPIDQr}, procGetNetworks},
			{"/networks", nil, procGetNetworks},
			{"/networks/" + nwID, nil, procGetNetwork},
			{"/networks/" + nwID + "/endpoints", []string{"name", epNameQr}, procGetEndpoints},
			{"/networks/" + nwID + "/endpoints", []string{"partial-id", epPIDQr}, procGetEndpoints},
			{"/networks/" + nwID + "/endpoints", nil, procGetEndpoints},
			{"/networks/" + nwID + "/endpoints/" + epID, nil, procGetEndpoint},
			{"/services", []string{"network", nwNameQr}, procGetServices},
			{"/services", []string{"name", epNameQr}, procGetServices},
			{"/services", []string{"partial-id", epPIDQr}, procGetServices},
			{"/services", nil, procGetServices},
			{"/services/" + epID, nil, procGetService},
			{"/services/" + epID + "/backend", nil, procGetSandbox},
			{"/sandboxes", []string{"partial-container-id", cnPIDQr}, procGetSandboxes},
			{"/sandboxes", []string{"container-id", cnIDQr}, procGetSandboxes},
			{"/sandboxes", []string{"partial-id", sbPIDQr}, procGetSandboxes},
			{"/sandboxes", nil, procGetSandboxes},
			{"/sandboxes/" + sbID, nil, procGetSandbox},
		},
		"POST": {
			{"/networks", nil, procCreateNetwork},
			{"/networks/" + nwID + "/endpoints", nil, procCreateEndpoint},
			{"/networks/" + nwID + "/endpoints/" + epID + "/sandboxes", nil, procJoinEndpoint},
			{"/services", nil, procPublishService},
			{"/services/" + epID + "/backend", nil, procAttachBackend},
			{"/sandboxes", nil, procCreateSandbox},
		},
		"DELETE": {
			{"/networks/" + nwID, nil, procDeleteNetwork},
			{"/networks/" + nwID + "/endpoints/" + epID, nil, procDeleteEndpoint},
			{"/networks/" + nwID + "/endpoints/" + epID + "/sandboxes/" + sbID, nil, procLeaveEndpoint},
			{"/services/" + epID, nil, procUnpublishService},
			{"/services/" + epID + "/backend/" + sbID, nil, procDetachBackend},
			{"/sandboxes/" + sbID, nil, procDeleteSandbox},
		},
	}

	h.r = mux.NewRouter()
	for method, routes := range m {
		for _, route := range routes {
			r := h.r.Path("/{.*}" + route.url).Methods(method).HandlerFunc(makeHandler(h.c, route.fct))
			if route.qrs != nil {
				r.Queries(route.qrs...)
			}

			r = h.r.Path(route.url).Methods(method).HandlerFunc(makeHandler(h.c, route.fct))
			if route.qrs != nil {
				r.Queries(route.qrs...)
			}
		}
	}
}

func makeHandler(ctrl libnetwork.NetworkController, fct processor) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		var (
			body []byte
			err  error
		)
		if req.Body != nil {
			body, err = ioutil.ReadAll(req.Body)
			if err != nil {
				http.Error(w, "Invalid body: "+err.Error(), http.StatusBadRequest)
				return
			}
		}

		res, rsp := fct(ctrl, mux.Vars(req), body)
		if !rsp.isOK() {
			http.Error(w, rsp.Status, rsp.StatusCode)
			return
		}
		if res != nil {
			writeJSON(w, rsp.StatusCode, res)
		}
	}
}

/*****************
 Resource Builders
******************/

func buildNetworkResource(nw libnetwork.Network) *networkResource {
	r := &networkResource{}
	if nw != nil {
		r.Name = nw.Name()
		r.ID = nw.ID()
		r.Type = nw.Type()
		epl := nw.Endpoints()
		r.Endpoints = make([]*endpointResource, 0, len(epl))
		for _, e := range epl {
			epr := buildEndpointResource(e)
			r.Endpoints = append(r.Endpoints, epr)
		}
	}
	return r
}

func buildEndpointResource(ep libnetwork.Endpoint) *endpointResource {
	r := &endpointResource{}
	if ep != nil {
		r.Name = ep.Name()
		r.ID = ep.ID()
		r.Network = ep.Network()
	}
	return r
}

func buildSandboxResource(sb libnetwork.Sandbox) *sandboxResource {
	r := &sandboxResource{}
	if sb != nil {
		r.ID = sb.ID()
		r.Key = sb.Key()
		r.ContainerID = sb.ContainerID()
	}
	return r
}

/****************
 Options Parsers
*****************/

func (sc *sandboxCreate) parseOptions() []libnetwork.SandboxOption {
	var setFctList []libnetwork.SandboxOption
	if sc.HostName != "" {
		setFctList = append(setFctList, libnetwork.OptionHostname(sc.HostName))
	}
	if sc.DomainName != "" {
		setFctList = append(setFctList, libnetwork.OptionDomainname(sc.DomainName))
	}
	if sc.HostsPath != "" {
		setFctList = append(setFctList, libnetwork.OptionHostsPath(sc.HostsPath))
	}
	if sc.ResolvConfPath != "" {
		setFctList = append(setFctList, libnetwork.OptionResolvConfPath(sc.ResolvConfPath))
	}
	if sc.UseDefaultSandbox {
		setFctList = append(setFctList, libnetwork.OptionUseDefaultSandbox())
	}
	if sc.UseExternalKey {
		setFctList = append(setFctList, libnetwork.OptionUseExternalKey())
	}
	if sc.DNS != nil {
		for _, d := range sc.DNS {
			setFctList = append(setFctList, libnetwork.OptionDNS(d))
		}
	}
	if sc.ExtraHosts != nil {
		for _, e := range sc.ExtraHosts {
			setFctList = append(setFctList, libnetwork.OptionExtraHost(e.Name, e.Address))
		}
	}
	if sc.ExposedPorts != nil {
		setFctList = append(setFctList, libnetwork.OptionExposedPorts(sc.ExposedPorts))
	}
	if sc.PortMapping != nil {
		setFctList = append(setFctList, libnetwork.OptionPortMapping(sc.PortMapping))
	}
	return setFctList
}

func (ej *endpointJoin) parseOptions() []libnetwork.EndpointOption {
	// priority will go here
	return []libnetwork.EndpointOption{}
}

/******************
 Process functions
*******************/

func processCreateDefaults(c libnetwork.NetworkController, nc *networkCreate) {
	if nc.NetworkType == "" {
		nc.NetworkType = c.Config().Daemon.DefaultDriver
	}
}

/***************************
 NetworkController interface
****************************/
func procCreateNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	var create networkCreate

	err := json.Unmarshal(body, &create)
	if err != nil {
		return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
	}
	processCreateDefaults(c, &create)

	options := []libnetwork.NetworkOption{}
	if val, ok := create.NetworkOpts[netlabel.Internal]; ok {
		internal, err := strconv.ParseBool(val)
		if err != nil {
			return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
		}
		if internal {
			options = append(options, libnetwork.NetworkOptionInternalNetwork())
		}
	}
	if val, ok := create.NetworkOpts[netlabel.EnableIPv6]; ok {
		enableIPv6, err := strconv.ParseBool(val)
		if err != nil {
			return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
		}
		options = append(options, libnetwork.NetworkOptionEnableIPv6(enableIPv6))
	}
	if len(create.DriverOpts) > 0 {
		options = append(options, libnetwork.NetworkOptionDriverOpts(create.DriverOpts))
	}
	nw, err := c.NewNetwork(create.NetworkType, create.Name, options...)
	if err != nil {
		return nil, convertNetworkError(err)
	}

	return nw.ID(), &createdResponse
}

func procGetNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	t, by := detectNetworkTarget(vars)
	nw, errRsp := findNetwork(c, t, by)
	if !errRsp.isOK() {
		return nil, errRsp
	}
	return buildNetworkResource(nw), &successResponse
}

func procGetNetworks(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	var list []*networkResource

	// Look for query filters and validate
	name, queryByName := vars[urlNwName]
	shortID, queryByPid := vars[urlNwPID]
	if queryByName && queryByPid {
		return nil, &badQueryResponse
	}

	if queryByName {
		if nw, errRsp := findNetwork(c, name, byName); errRsp.isOK() {
			list = append(list, buildNetworkResource(nw))
		}
	} else if queryByPid {
		// Return all the prefix-matching networks
		l := func(nw libnetwork.Network) bool {
			if strings.HasPrefix(nw.ID(), shortID) {
				list = append(list, buildNetworkResource(nw))
			}
			return false
		}
		c.WalkNetworks(l)
	} else {
		for _, nw := range c.Networks() {
			list = append(list, buildNetworkResource(nw))
		}
	}

	return list, &successResponse
}

func procCreateSandbox(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	var create sandboxCreate

	err := json.Unmarshal(body, &create)
	if err != nil {
		return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
	}

	sb, err := c.NewSandbox(create.ContainerID, create.parseOptions()...)
	if err != nil {
		return "", convertNetworkError(err)
	}

	return sb.ID(), &createdResponse
}

/******************
 Network interface
*******************/
func procCreateEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	var ec endpointCreate

	err := json.Unmarshal(body, &ec)
	if err != nil {
		return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
	}

	nwT, nwBy := detectNetworkTarget(vars)
	n, errRsp := findNetwork(c, nwT, nwBy)
	if !errRsp.isOK() {
		return "", errRsp
	}

	var setFctList []libnetwork.EndpointOption
	for _, str := range ec.MyAliases {
		setFctList = append(setFctList, libnetwork.CreateOptionMyAlias(str))
	}

	ep, err := n.CreateEndpoint(ec.Name, setFctList...)
	if err != nil {
		return "", convertNetworkError(err)
	}

	return ep.ID(), &createdResponse
}

func procGetEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	nwT, nwBy := detectNetworkTarget(vars)
	epT, epBy := detectEndpointTarget(vars)

	ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
	if !errRsp.isOK() {
		return nil, errRsp
	}

	return buildEndpointResource(ep), &successResponse
}

func procGetEndpoints(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	// Look for query filters and validate
	name, queryByName := vars[urlEpName]
	shortID, queryByPid := vars[urlEpPID]
	if queryByName && queryByPid {
		return nil, &badQueryResponse
	}

	nwT, nwBy := detectNetworkTarget(vars)
	nw, errRsp := findNetwork(c, nwT, nwBy)
	if !errRsp.isOK() {
		return nil, errRsp
	}

	var list []*endpointResource

	// If query parameter is specified, return a filtered collection
	if queryByName {
		if ep, errRsp := findEndpoint(c, nwT, name, nwBy, byName); errRsp.isOK() {
			list = append(list, buildEndpointResource(ep))
		}
	} else if queryByPid {
		// Return all the prefix-matching endpoints
		l := func(ep libnetwork.Endpoint) bool {
			if strings.HasPrefix(ep.ID(), shortID) {
				list = append(list, buildEndpointResource(ep))
			}
			return false
		}
		nw.WalkEndpoints(l)
	} else {
		for _, ep := range nw.Endpoints() {
			epr := buildEndpointResource(ep)
			list = append(list, epr)
		}
	}

	return list, &successResponse
}

func procDeleteNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	target, by := detectNetworkTarget(vars)

	nw, errRsp := findNetwork(c, target, by)
	if !errRsp.isOK() {
		return nil, errRsp
	}

	err := nw.Delete()
	if err != nil {
		return nil, convertNetworkError(err)
	}

	return nil, &successResponse
}

/******************
 Endpoint interface
*******************/
func procJoinEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	var ej endpointJoin
	var setFctList []libnetwork.EndpointOption
	err := json.Unmarshal(body, &ej)
	if err != nil {
		return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
	}

	nwT, nwBy := detectNetworkTarget(vars)
	epT, epBy := detectEndpointTarget(vars)

	ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
	if !errRsp.isOK() {
		return nil, errRsp
	}

	sb, errRsp := findSandbox(c, ej.SandboxID, byID)
	if !errRsp.isOK() {
		return nil, errRsp
	}

	for _, str := range ej.Aliases {
		name, alias, err := netutils.ParseAlias(str)
		if err != nil {
			return "", convertNetworkError(err)
		}
		setFctList = append(setFctList, libnetwork.CreateOptionAlias(name, alias))
	}

	err = ep.Join(sb, setFctList...)
	if err != nil {
		return nil, convertNetworkError(err)
	}
	return sb.Key(), &successResponse
}

func procLeaveEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	nwT, nwBy := detectNetworkTarget(vars)
	epT, epBy := detectEndpointTarget(vars)

	ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
	if !errRsp.isOK() {
		return nil, errRsp
	}

	sb, errRsp := findSandbox(c, vars[urlSbID], byID)
	if !errRsp.isOK() {
		return nil, errRsp
	}

	err := ep.Leave(sb)
	if err != nil {
		return nil, convertNetworkError(err)
	}

	return nil, &successResponse
}

func procDeleteEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	nwT, nwBy := detectNetworkTarget(vars)
	epT, epBy := detectEndpointTarget(vars)

	ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
	if !errRsp.isOK() {
		return nil, errRsp
	}

	err := ep.Delete(false)
	if err != nil {
		return nil, convertNetworkError(err)
	}

	return nil, &successResponse
}

/******************
 Service interface
*******************/
func procGetServices(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	// Look for query filters and validate
	nwName, filterByNwName := vars[urlNwName]
	svName, queryBySvName := vars[urlEpName]
	shortID, queryBySvPID := vars[urlEpPID]

	if filterByNwName && queryBySvName || filterByNwName && queryBySvPID || queryBySvName && queryBySvPID {
		return nil, &badQueryResponse
	}

	var list []*endpointResource

	switch {
	case filterByNwName:
		// return all service present on the specified network
		nw, errRsp := findNetwork(c, nwName, byName)
		if !errRsp.isOK() {
			return list, &successResponse
		}
		for _, ep := range nw.Endpoints() {
			epr := buildEndpointResource(ep)
			list = append(list, epr)
		}
	case queryBySvName:
		// Look in each network for the service with the specified name
		l := func(ep libnetwork.Endpoint) bool {
			if ep.Name() == svName {
				list = append(list, buildEndpointResource(ep))
				return true
			}
			return false
		}
		for _, nw := range c.Networks() {
			nw.WalkEndpoints(l)
		}
	case queryBySvPID:
		// Return all the prefix-matching services
		l := func(ep libnetwork.Endpoint) bool {
			if strings.HasPrefix(ep.ID(), shortID) {
				list = append(list, buildEndpointResource(ep))
			}
			return false
		}
		for _, nw := range c.Networks() {
			nw.WalkEndpoints(l)
		}
	default:
		for _, nw := range c.Networks() {
			for _, ep := range nw.Endpoints() {
				epr := buildEndpointResource(ep)
				list = append(list, epr)
			}
		}
	}

	return list, &successResponse
}

func procGetService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	epT, epBy := detectEndpointTarget(vars)
	sv, errRsp := findService(c, epT, epBy)
	if !errRsp.isOK() {
		return nil, endpointToService(errRsp)
	}
	return buildEndpointResource(sv), &successResponse
}

func procPublishService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	var sp servicePublish

	err := json.Unmarshal(body, &sp)
	if err != nil {
		return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
	}

	n, errRsp := findNetwork(c, sp.Network, byName)
	if !errRsp.isOK() {
		return "", errRsp
	}

	var setFctList []libnetwork.EndpointOption
	for _, str := range sp.MyAliases {
		setFctList = append(setFctList, libnetwork.CreateOptionMyAlias(str))
	}

	ep, err := n.CreateEndpoint(sp.Name, setFctList...)
	if err != nil {
		return "", endpointToService(convertNetworkError(err))
	}

	return ep.ID(), &createdResponse
}

func procUnpublishService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	var sd serviceDelete

	if body != nil {
		err := json.Unmarshal(body, &sd)
		if err != nil {
			return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
		}
	}

	epT, epBy := detectEndpointTarget(vars)
	sv, errRsp := findService(c, epT, epBy)
	if !errRsp.isOK() {
		return nil, errRsp
	}

	if err := sv.Delete(sd.Force); err != nil {
		return nil, endpointToService(convertNetworkError(err))
	}
	return nil, &successResponse
}

func procAttachBackend(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	var bk endpointJoin
	var setFctList []libnetwork.EndpointOption
	err := json.Unmarshal(body, &bk)
	if err != nil {
		return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
	}

	epT, epBy := detectEndpointTarget(vars)
	sv, errRsp := findService(c, epT, epBy)
	if !errRsp.isOK() {
		return nil, errRsp
	}

	sb, errRsp := findSandbox(c, bk.SandboxID, byID)
	if !errRsp.isOK() {
		return nil, errRsp
	}

	for _, str := range bk.Aliases {
		name, alias, err := netutils.ParseAlias(str)
		if err != nil {
			return "", convertNetworkError(err)
		}
		setFctList = append(setFctList, libnetwork.CreateOptionAlias(name, alias))
	}

	err = sv.Join(sb, setFctList...)
	if err != nil {
		return nil, convertNetworkError(err)
	}
	return sb.Key(), &successResponse
}

func procDetachBackend(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	epT, epBy := detectEndpointTarget(vars)
	sv, errRsp := findService(c, epT, epBy)
	if !errRsp.isOK() {
		return nil, errRsp
	}

	sb, errRsp := findSandbox(c, vars[urlSbID], byID)
	if !errRsp.isOK() {
		return nil, errRsp
	}

	err := sv.Leave(sb)
	if err != nil {
		return nil, convertNetworkError(err)
	}

	return nil, &successResponse
}

/******************
 Sandbox interface
*******************/
func procGetSandbox(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	if epT, ok := vars[urlEpID]; ok {
		sv, errRsp := findService(c, epT, byID)
		if !errRsp.isOK() {
			return nil, endpointToService(errRsp)
		}
		return buildSandboxResource(sv.Info().Sandbox()), &successResponse
	}

	sbT, by := detectSandboxTarget(vars)
	sb, errRsp := findSandbox(c, sbT, by)
	if !errRsp.isOK() {
		return nil, errRsp
	}
	return buildSandboxResource(sb), &successResponse
}

type cndFnMkr func(string) cndFn
type cndFn func(libnetwork.Sandbox) bool

// list of (query type, condition function makers) couples
var cndMkrList = []struct {
	identifier string
	maker      cndFnMkr
}{
	{urlSbPID, func(id string) cndFn {
		return func(sb libnetwork.Sandbox) bool { return strings.HasPrefix(sb.ID(), id) }
	}},
	{urlCnID, func(id string) cndFn {
		return func(sb libnetwork.Sandbox) bool { return sb.ContainerID() == id }
	}},
	{urlCnPID, func(id string) cndFn {
		return func(sb libnetwork.Sandbox) bool { return strings.HasPrefix(sb.ContainerID(), id) }
	}},
}

func getQueryCondition(vars map[string]string) func(libnetwork.Sandbox) bool {
	for _, im := range cndMkrList {
		if val, ok := vars[im.identifier]; ok {
			return im.maker(val)
		}
	}
	return func(sb libnetwork.Sandbox) bool { return true }
}

func sandboxWalker(condition cndFn, list *[]*sandboxResource) libnetwork.SandboxWalker {
	return func(sb libnetwork.Sandbox) bool {
		if condition(sb) {
			*list = append(*list, buildSandboxResource(sb))
		}
		return false
	}
}

func procGetSandboxes(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	var list []*sandboxResource

	cnd := getQueryCondition(vars)
	c.WalkSandboxes(sandboxWalker(cnd, &list))

	return list, &successResponse
}

func procDeleteSandbox(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
	sbT, by := detectSandboxTarget(vars)

	sb, errRsp := findSandbox(c, sbT, by)
	if !errRsp.isOK() {
		return nil, errRsp
	}

	err := sb.Delete()
	if err != nil {
		return nil, convertNetworkError(err)
	}

	return nil, &successResponse
}

/***********
  Utilities
************/
const (
	byID = iota
	byName
)

func detectNetworkTarget(vars map[string]string) (string, int) {
	if target, ok := vars[urlNwName]; ok {
		return target, byName
	}
	if target, ok := vars[urlNwID]; ok {
		return target, byID
	}
	// vars are populated from the URL, following cannot happen
	panic("Missing URL variable parameter for network")
}

func detectSandboxTarget(vars map[string]string) (string, int) {
	if target, ok := vars[urlSbID]; ok {
		return target, byID
	}
	// vars are populated from the URL, following cannot happen
	panic("Missing URL variable parameter for sandbox")
}

func detectEndpointTarget(vars map[string]string) (string, int) {
	if target, ok := vars[urlEpName]; ok {
		return target, byName
	}
	if target, ok := vars[urlEpID]; ok {
		return target, byID
	}
	// vars are populated from the URL, following cannot happen
	panic("Missing URL variable parameter for endpoint")
}

func findNetwork(c libnetwork.NetworkController, s string, by int) (libnetwork.Network, *responseStatus) {
	var (
		nw  libnetwork.Network
		err error
	)
	switch by {
	case byID:
		nw, err = c.NetworkByID(s)
	case byName:
		if s == "" {
			s = c.Config().Daemon.DefaultNetwork
		}
		nw, err = c.NetworkByName(s)
	default:
		panic(fmt.Sprintf("unexpected selector for network search: %d", by))
	}
	if err != nil {
		if _, ok := err.(types.NotFoundError); ok {
			return nil, &responseStatus{Status: "Resource not found: Network", StatusCode: http.StatusNotFound}
		}
		return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
	}
	return nw, &successResponse
}

func findSandbox(c libnetwork.NetworkController, s string, by int) (libnetwork.Sandbox, *responseStatus) {
	var (
		sb  libnetwork.Sandbox
		err error
	)

	switch by {
	case byID:
		sb, err = c.SandboxByID(s)
	default:
		panic(fmt.Sprintf("unexpected selector for sandbox search: %d", by))
	}
	if err != nil {
		if _, ok := err.(types.NotFoundError); ok {
			return nil, &responseStatus{Status: "Resource not found: Sandbox", StatusCode: http.StatusNotFound}
		}
		return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
	}
	return sb, &successResponse
}

func findEndpoint(c libnetwork.NetworkController, ns, es string, nwBy, epBy int) (libnetwork.Endpoint, *responseStatus) {
	nw, errRsp := findNetwork(c, ns, nwBy)
	if !errRsp.isOK() {
		return nil, errRsp
	}
	var (
		err error
		ep  libnetwork.Endpoint
	)
	switch epBy {
	case byID:
		ep, err = nw.EndpointByID(es)
	case byName:
		ep, err = nw.EndpointByName(es)
	default:
		panic(fmt.Sprintf("unexpected selector for endpoint search: %d", epBy))
	}
	if err != nil {
		if _, ok := err.(types.NotFoundError); ok {
			return nil, &responseStatus{Status: "Resource not found: Endpoint", StatusCode: http.StatusNotFound}
		}
		return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
	}
	return ep, &successResponse
}

func findService(c libnetwork.NetworkController, svs string, svBy int) (libnetwork.Endpoint, *responseStatus) {
	for _, nw := range c.Networks() {
		var (
			ep  libnetwork.Endpoint
			err error
		)
		switch svBy {
		case byID:
			ep, err = nw.EndpointByID(svs)
		case byName:
			ep, err = nw.EndpointByName(svs)
		default:
			panic(fmt.Sprintf("unexpected selector for service search: %d", svBy))
		}
		if err == nil {
			return ep, &successResponse
		} else if _, ok := err.(types.NotFoundError); !ok {
			return nil, convertNetworkError(err)
		}
	}
	return nil, &responseStatus{Status: "Service not found", StatusCode: http.StatusNotFound}
}

func endpointToService(rsp *responseStatus) *responseStatus {
	rsp.Status = strings.Replace(rsp.Status, "endpoint", "service", -1)
	return rsp
}

func convertNetworkError(err error) *responseStatus {
	var code int
	switch err.(type) {
	case types.BadRequestError:
		code = http.StatusBadRequest
	case types.ForbiddenError:
		code = http.StatusForbidden
	case types.NotFoundError:
		code = http.StatusNotFound
	case types.TimeoutError:
		code = http.StatusRequestTimeout
	case types.NotImplementedError:
		code = http.StatusNotImplemented
	case types.NoServiceError:
		code = http.StatusServiceUnavailable
	case types.InternalError:
		code = http.StatusInternalServerError
	default:
		code = http.StatusInternalServerError
	}
	return &responseStatus{Status: err.Error(), StatusCode: code}
}

func writeJSON(w http.ResponseWriter, code int, v interface{}) error {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	return json.NewEncoder(w).Encode(v)
}
