package network // import "github.com/docker/docker/daemon/network"

import (
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/errdefs"
	"github.com/docker/docker/runconfig"
	"github.com/pkg/errors"
)

// FilterNetworks filters network list according to user specified filter
// and returns user chosen networks
func FilterNetworks(nws []types.NetworkResource, filter filters.Args) ([]types.NetworkResource, error) {
	// if filter is empty, return original network list
	if filter.Len() == 0 {
		return nws, nil
	}

	displayNet := nws[:0]
	for _, nw := range nws {
		if filter.Contains("driver") {
			if !filter.ExactMatch("driver", nw.Driver) {
				continue
			}
		}
		if filter.Contains("name") {
			if !filter.Match("name", nw.Name) {
				continue
			}
		}
		if filter.Contains("id") {
			if !filter.Match("id", nw.ID) {
				continue
			}
		}
		if filter.Contains("label") {
			if !filter.MatchKVList("label", nw.Labels) {
				continue
			}
		}
		if filter.Contains("scope") {
			if !filter.ExactMatch("scope", nw.Scope) {
				continue
			}
		}

		if filter.Contains("idOrName") {
			if !filter.Match("name", nw.Name) && !filter.Match("id", nw.Name) {
				continue
			}
		}
		displayNet = append(displayNet, nw)
	}

	if values := filter.Get("dangling"); len(values) > 0 {
		if len(values) > 1 {
			return nil, errdefs.InvalidParameter(errors.New(`got more than one value for filter key "dangling"`))
		}

		var danglingOnly bool
		switch values[0] {
		case "0", "false":
			// dangling is false already
		case "1", "true":
			danglingOnly = true
		default:
			return nil, errdefs.InvalidParameter(errors.New(`invalid value for filter 'dangling', must be "true" (or "1"), or "false" (or "0")`))
		}

		displayNet = filterNetworkByUse(displayNet, danglingOnly)
	}

	if filter.Contains("type") {
		typeNet := []types.NetworkResource{}
		errFilter := filter.WalkValues("type", func(fval string) error {
			passList, err := filterNetworkByType(displayNet, fval)
			if err != nil {
				return err
			}
			typeNet = append(typeNet, passList...)
			return nil
		})
		if errFilter != nil {
			return nil, errFilter
		}
		displayNet = typeNet
	}

	return displayNet, nil
}

func filterNetworkByUse(nws []types.NetworkResource, danglingOnly bool) []types.NetworkResource {
	retNws := []types.NetworkResource{}

	filterFunc := func(nw types.NetworkResource) bool {
		if danglingOnly {
			return !runconfig.IsPreDefinedNetwork(nw.Name) && len(nw.Containers) == 0 && len(nw.Services) == 0
		}
		return runconfig.IsPreDefinedNetwork(nw.Name) || len(nw.Containers) > 0 || len(nw.Services) > 0
	}

	for _, nw := range nws {
		if filterFunc(nw) {
			retNws = append(retNws, nw)
		}
	}

	return retNws
}

func filterNetworkByType(nws []types.NetworkResource, netType string) ([]types.NetworkResource, error) {
	retNws := []types.NetworkResource{}
	switch netType {
	case "builtin":
		for _, nw := range nws {
			if runconfig.IsPreDefinedNetwork(nw.Name) {
				retNws = append(retNws, nw)
			}
		}
	case "custom":
		for _, nw := range nws {
			if !runconfig.IsPreDefinedNetwork(nw.Name) {
				retNws = append(retNws, nw)
			}
		}
	default:
		return nil, errors.Errorf("invalid filter: 'type'='%s'", netType)
	}
	return retNws, nil
}