// RAINBOND, Application Management Platform
// Copyright (C) 2014-2017 Goodrain Co., Ltd.

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. For any non-GPL usage of Rainbond,
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
// must be obtained first.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package v1

import (
	"crypto/sha256"
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
	"sort"
	"strings"
	"time"

	"github.com/sirupsen/logrus"

	"github.com/golang/protobuf/ptypes"
	"github.com/golang/protobuf/ptypes/duration"
)

const (
	// DefaultAccessLog is the name of the log channel (stdout in docker environment)
	DefaultAccessLog = "/dev/stdout"

	// DefaultLbType defines the default load balancer policy
	DefaultLbType = LbTypeRoundRobin

	// LDSName is the name of listener-discovery-service (LDS) cluster
	LDSName = "lds"

	// RDSName is the name of route-discovery-service (RDS) cluster
	RDSName = "rds"

	// SDSName is the name of service-discovery-service (SDS) cluster
	SDSName = "sds"

	// CDSName is the name of cluster-discovery-service (CDS) cluster
	CDSName = "cds"

	// RDSAll is the special name for HTTP PROXY route
	RDSAll = "http_proxy"

	// VirtualListenerName is the name for traffic capture listener
	VirtualListenerName = "virtual"

	// ClusterTypeStrictDNS name for clusters of type 'strict_dns'
	ClusterTypeStrictDNS = "strict_dns"

	// ClusterTypeStatic name for clusters of type 'static'
	ClusterTypeStatic = "static"

	// ClusterTypeOriginalDST name for clusters of type 'original_dst'
	ClusterTypeOriginalDST = "original_dst"

	// ClusterTypeSDS name for clusters of type 'sds'
	ClusterTypeSDS = "sds"

	// LbTypeRoundRobin is the name for round-robin LB
	LbTypeRoundRobin = "round_robin"

	// LbTypeLeastRequest is the name for least request LB
	LbTypeLeastRequest = "least_request"

	// LbTypeRingHash is the name for ring hash LB
	LbTypeRingHash = "ring_hash"

	// LbTypeRandom is the name for random LB
	LbTypeRandom = "random"

	// LbTypeOriginalDST is the name for LB of original_dst
	LbTypeOriginalDST = "original_dst_lb"

	// ClusterFeatureHTTP2 is the feature to use HTTP/2 for a cluster
	ClusterFeatureHTTP2 = "http2"

	// HTTPConnectionManager is the name of HTTP filter.
	HTTPConnectionManager = "http_connection_manager"

	// TCPProxyFilter is the name of the TCP Proxy network filter.
	TCPProxyFilter = "tcp_proxy"

	// CORSFilter is the name of the CORS network filter
	CORSFilter = "cors"

	// MongoProxyFilter is the name of the Mongo Proxy network filter.
	MongoProxyFilter = "mongo_proxy"

	// RedisProxyFilter is the name of the Redis Proxy network filter.
	RedisProxyFilter = "redis_proxy"

	// RedisDefaultOpTimeout is the op timeout used for Redis Proxy filter
	// Currently it is set to 30s (conversion happens in the filter)
	// TODO - Allow this to be configured.
	RedisDefaultOpTimeout = 30 * time.Second

	// WildcardAddress binds to all IP addresses
	WildcardAddress = "0.0.0.0"

	// LocalhostAddress for local binding
	LocalhostAddress = "127.0.0.1"

	// EgressTraceOperation denotes the name of trace operation for Envoy
	EgressTraceOperation = "egress"

	// IngressTraceOperation denotes the name of trace operation for Envoy
	IngressTraceOperation = "ingress"

	// ZipkinTraceDriverType denotes the Zipkin HTTP trace driver
	ZipkinTraceDriverType = "zipkin"

	// ZipkinCollectorCluster denotes the cluster where zipkin server is running
	ZipkinCollectorCluster = "zipkin"

	// ZipkinCollectorEndpoint denotes the REST endpoint where Envoy posts Zipkin spans
	ZipkinCollectorEndpoint = "/api/v1/spans"

	// MaxClusterNameLength is the maximum cluster name length
	MaxClusterNameLength = 189 // TODO: use MeshConfig.StatNameLength instead

	// Headers with special meaning in Envoy

	// HeaderMethod is the method header.
	HeaderMethod = ":method"
	// HeaderAuthority is the authority header.
	HeaderAuthority = ":authority"
	// HeaderScheme is the scheme header.
	HeaderScheme = ":scheme"
	// MixerFilter name and its attributes
	MixerFilter = "mixer"

	router  = "router"
	auto    = "auto"
	decoder = "decoder"
	read    = "read"
	both    = "both"
)

var (
	// ValidateClusters is an environment variable that can be set to false to disable
	// cluster validation in RDS, in case problems are discovered.
	ValidateClusters = true
)

// ListenersALPNProtocols denotes the the list of ALPN protocols that the listener
// should expose
var ListenersALPNProtocols = []string{"h2", "http/1.1"}

// convertDuration converts to golang duration and logs errors
func convertDuration(d *duration.Duration) time.Duration {
	if d == nil {
		return 0
	}
	dur, err := ptypes.Duration(d)
	if err != nil {
		logrus.Warnf("error converting duration %#v, using 0: %v", d, err)
	}
	return dur
}

func protoDurationToMS(dur *duration.Duration) int64 {
	return int64(convertDuration(dur) / time.Millisecond)
}

// Config defines the schema for Envoy JSON configuration format
type Config struct {
	RootRuntime        *RootRuntime   `json:"runtime,omitempty"`
	Listeners          Listeners      `json:"listeners"`
	LDS                *LDSCluster    `json:"lds,omitempty"`
	Admin              Admin          `json:"admin"`
	ClusterManager     ClusterManager `json:"cluster_manager"`
	StatsdUDPIPAddress string         `json:"statsd_udp_ip_address,omitempty"`
	Tracing            *Tracing       `json:"tracing,omitempty"`

	// Special value used to hash all referenced values (e.g. TLS secrets)
	Hash []byte `json:"-"`
}

// Tracing definition
type Tracing struct {
	HTTPTracer HTTPTracer `json:"http"`
}

// HTTPTracer definition
type HTTPTracer struct {
	HTTPTraceDriver HTTPTraceDriver `json:"driver"`
}

// HTTPTraceDriver definition
type HTTPTraceDriver struct {
	HTTPTraceDriverType   string                `json:"type"`
	HTTPTraceDriverConfig HTTPTraceDriverConfig `json:"config"`
}

// HTTPTraceDriverConfig definition
type HTTPTraceDriverConfig struct {
	CollectorCluster  string `json:"collector_cluster"`
	CollectorEndpoint string `json:"collector_endpoint"`
}

// RootRuntime definition.
// See https://envoyproxy.github.io/envoy/configuration/overview/overview.html
type RootRuntime struct {
	SymlinkRoot          string `json:"symlink_root"`
	Subdirectory         string `json:"subdirectory"`
	OverrideSubdirectory string `json:"override_subdirectory,omitempty"`
}

// AbortFilter definition
type AbortFilter struct {
	Percent    int `json:"abort_percent,omitempty"`
	HTTPStatus int `json:"http_status,omitempty"`
}

// DelayFilter definition
type DelayFilter struct {
	Type     string `json:"type,omitempty"`
	Percent  int    `json:"fixed_delay_percent,omitempty"`
	Duration int64  `json:"fixed_duration_ms,omitempty"`
}

// AppendedHeader definition
type AppendedHeader struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

// Header definition
type Header struct {
	Name  string `json:"name"`
	Value string `json:"value"`
	Regex bool   `json:"regex,omitempty"`
}

// FilterFaultConfig definition
type FilterFaultConfig struct {
	Abort           *AbortFilter `json:"abort,omitempty"`
	Delay           *DelayFilter `json:"delay,omitempty"`
	Headers         Headers      `json:"headers,omitempty"`
	UpstreamCluster string       `json:"upstream_cluster,omitempty"`
}

// FilterRouterConfig definition
type FilterRouterConfig struct {
	// DynamicStats defaults to true
	DynamicStats bool `json:"dynamic_stats,omitempty"`
}

// HTTPFilter definition
type HTTPFilter struct {
	Type   string      `json:"type"`
	Name   string      `json:"name"`
	Config interface{} `json:"config"`
}

// Runtime definition
type Runtime struct {
	Key     string `json:"key"`
	Default int    `json:"default"`
}

// Decorator definition
type Decorator struct {
	Operation string `json:"operation"`
}

// HTTPRoute definition
type HTTPRoute struct {
	Runtime *Runtime `json:"runtime,omitempty"`

	Path   string `json:"path,omitempty"`
	Prefix string `json:"prefix,omitempty"`
	Regex  string `json:"regex,omitempty"`

	PrefixRewrite string `json:"prefix_rewrite,omitempty"`
	HostRewrite   string `json:"host_rewrite,omitempty"`

	PathRedirect string `json:"path_redirect,omitempty"`
	HostRedirect string `json:"host_redirect,omitempty"`

	Cluster          string           `json:"cluster,omitempty"`
	WeightedClusters *WeightedCluster `json:"weighted_clusters,omitempty"`

	Headers      Headers           `json:"headers,omitempty"`
	TimeoutMS    int64             `json:"timeout_ms"`
	RetryPolicy  *RetryPolicy      `json:"retry_policy,omitempty"`
	OpaqueConfig map[string]string `json:"opaque_config,omitempty"`

	AutoHostRewrite  bool `json:"auto_host_rewrite,omitempty"`
	WebsocketUpgrade bool `json:"use_websocket,omitempty"`

	ShadowCluster *ShadowCluster `json:"shadow,omitempty"`

	HeadersToAdd []AppendedHeader `json:"request_headers_to_add,omitempty"`

	CORSPolicy *CORSPolicy `json:"cors,omitempty"`

	Decorator *Decorator `json:"decorator,omitempty"`

	// clusters contains the set of referenced clusters in the route; the field is special
	// and used only to aggregate cluster information after composing routes
	Clusters Clusters `json:"-"`

	// faults contains the set of referenced faults in the route; the field is special
	// and used only to aggregate fault filter information after composing routes
	faults []*HTTPFilter
}

// Redirect returns true if route contains redirect logic
func (route *HTTPRoute) Redirect() bool {
	return route.HostRedirect != "" || route.PathRedirect != ""
}

// CatchAll returns true if the route matches all requests
func (route *HTTPRoute) CatchAll() bool {
	return len(route.Headers) == 0 && route.Path == "" && route.Prefix == "/"
}

//BasicHash returns hash string by route path\prefix\header
func (route *HTTPRoute) BasicHash() string {
	key := sha256.New()
	var header string
	sort.Sort(route.Headers)
	for _, h := range route.Headers {
		header += h.Name + h.Value
	}
	key.Write([]byte(route.Path + route.Prefix + header))
	return string(key.Sum(nil))
}

// CombinePathPrefix checks that the route applies for a given path and prefix
// match and updates the path and the prefix in the route. If the route is
// incompatible with the path or the prefix, returns nil.  Either path or
// prefix must be set but not both.  The resulting route must match exactly the
// requests that match both the original route and the supplied path and
// prefix.
func (route *HTTPRoute) CombinePathPrefix(path, prefix string) *HTTPRoute {
	switch {
	case path == "" && route.Path == "" && strings.HasPrefix(route.Prefix, prefix):
		// pick the longest prefix if both are prefix matches
		return route
	case path == "" && route.Path == "" && strings.HasPrefix(prefix, route.Prefix):
		route.Prefix = prefix
		return route
	case prefix == "" && route.Prefix == "" && route.Path == path:
		// pick only if path matches if both are path matches
		return route
	case path == "" && route.Prefix == "" && strings.HasPrefix(route.Path, prefix):
		// if mixed, pick if route path satisfies the prefix
		return route
	case prefix == "" && route.Path == "" && strings.HasPrefix(path, route.Prefix):
		// if mixed, pick if route prefix satisfies the path and change route to path
		route.Path = path
		route.Prefix = ""
		return route
	default:
		return nil
	}
}

// CORSPolicy definition
// See: https://www.envoyproxy.io/envoy/configuration/http_filters/cors_filter.html#config-http-filters-cors
type CORSPolicy struct {
	Enabled          bool     `json:"enabled,omitempty"`
	AllowCredentials bool     `json:"allow_credentials,omitempty"`
	AllowMethods     string   `json:"allow_methods,omitempty"`
	AllowHeaders     string   `json:"allow_headers,omitempty"`
	ExposeHeaders    string   `json:"expose_headers,omitempty"`
	MaxAge           int      `json:"max_age,string,omitempty"`
	AllowOrigin      []string `json:"allow_origin,omitempty"`
}

// RetryPolicy definition
// See: https://lyft.github.io/envoy/docs/configuration/http_conn_man/route_config/route.html#retry-policy
type RetryPolicy struct {
	Policy          string `json:"retry_on"` //if unset, set to 5xx,connect-failure,refused-stream
	NumRetries      int    `json:"num_retries,omitempty"`
	PerTryTimeoutMS int64  `json:"per_try_timeout_ms,omitempty"`
}

// ShadowCluster definition
// See: https://www.envoyproxy.io/envoy/configuration/http_conn_man/route_config/route.html?
// highlight=shadow#config-http-conn-man-route-table-route-shadow
type ShadowCluster struct {
	Cluster string `json:"cluster"`
}

// WeightedCluster definition
// See https://envoyproxy.github.io/envoy/configuration/http_conn_man/route_config/route.html
type WeightedCluster struct {
	Clusters         []*WeightedClusterEntry `json:"clusters"`
	RuntimeKeyPrefix string                  `json:"runtime_key_prefix,omitempty"`
}

// WeightedClusterEntry definition. Describes the format of each entry in the WeightedCluster
type WeightedClusterEntry struct {
	Name   string `json:"name"`
	Weight int    `json:"weight"`
}

// VirtualHost definition
type VirtualHost struct {
	Name    string       `json:"name"`
	Domains []string     `json:"domains"`
	Routes  []*HTTPRoute `json:"routes"`
}

func (host *VirtualHost) clusters() Clusters {
	out := make(Clusters, 0)
	for _, route := range host.Routes {
		out = append(out, route.Clusters...)
	}
	return out
}

//UniqVirtualHost according to the rules of VirtualHost in http route
//merge the VirtualHost that have same domain
//if have same domain, prifix, path and header,support weight
func UniqVirtualHost(vhs []*VirtualHost) (revhs []*VirtualHost) {
	var domains = make(map[string]*VirtualHost, 0)
	for _, vh := range vhs {
		for _, domain := range vh.Domains {
			if cahcevh, ok := domains[domain]; ok {
				cahcevh.Routes = append(cahcevh.Routes, vh.Routes...)
			} else {
				domains[domain] = vh
			}
		}
	}
	for _, v := range domains {
		//supprot weight if have same prifix, path and header
		var keys = make(map[string]*HTTPRoute, 0)
		for _, route := range v.Routes {
			key := route.BasicHash()
			if cacheroute, ok := keys[key]; ok {
				cacheroute.WeightedClusters.Clusters = append(cacheroute.WeightedClusters.Clusters, route.WeightedClusters.Clusters...)
			} else {
				keys[key] = route
			}
		}
		var routes []*HTTPRoute
		for _, v := range keys {
			var total int
			var i int
			for i = 0; i < len(v.WeightedClusters.Clusters)-1; i++ {
				total += v.WeightedClusters.Clusters[i].Weight
				if total >= 100 {
					break
				}
			}
			if total > 100 {
				v.WeightedClusters.Clusters[i].Weight = v.WeightedClusters.Clusters[i].Weight - (total - 100)
				if i+1 < len(v.WeightedClusters.Clusters) {
					for j := i + 1; j < len(v.WeightedClusters.Clusters); j++ {
						v.WeightedClusters.Clusters[j].Weight = 0
					}
				}
			}
			if total == 100 && i+1 < len(v.WeightedClusters.Clusters) {
				for j := i + 1; j < len(v.WeightedClusters.Clusters); j++ {
					v.WeightedClusters.Clusters[j].Weight = 0
				}
			}
			if total < 100 {
				v.WeightedClusters.Clusters[i].Weight = 100 - total
			}
			routes = append(routes, v)
		}
		v.Routes = routes
		revhs = append(revhs, v)
	}
	return
}

// HTTPRouteConfig definition
type HTTPRouteConfig struct {
	ValidateClusters bool           `json:"validate_clusters"`
	VirtualHosts     []*VirtualHost `json:"virtual_hosts"`
}

// HTTPRouteConfigs is a map from the port number to the route config
type HTTPRouteConfigs map[int]*HTTPRouteConfig

// EnsurePort creates a route config if necessary
func (routes HTTPRouteConfigs) EnsurePort(port int) *HTTPRouteConfig {
	config, ok := routes[port]
	if !ok {
		config = &HTTPRouteConfig{ValidateClusters: ValidateClusters}
		routes[port] = config
	}
	return config
}

// Clusters returns the clusters corresponding to the given routes.
func (routes HTTPRouteConfigs) Clusters() Clusters {
	out := make(Clusters, 0)
	for _, config := range routes {
		out = append(out, config.Clusters()...)
	}
	return out
}

// Normalize normalizes the route configs.
func (routes HTTPRouteConfigs) Normalize() HTTPRouteConfigs {
	out := make(HTTPRouteConfigs)

	// sort HTTP routes by virtual hosts, rest should be deterministic
	for port, routeConfig := range routes {
		out[port] = routeConfig.Normalize()
	}

	return out
}

// Combine creates a new route config that is the union of all HTTP routes.
// note that the virtual hosts without an explicit port suffix (IP:PORT) are stripped
// for all routes except the route for port 80.
func (routes HTTPRouteConfigs) Combine() *HTTPRouteConfig {
	out := &HTTPRouteConfig{ValidateClusters: ValidateClusters}
	for port, config := range routes {
		for _, host := range config.VirtualHosts {
			vhost := &VirtualHost{
				Name:   host.Name,
				Routes: host.Routes,
			}
			for _, domain := range host.Domains {
				if port == 80 || strings.Contains(domain, ":") {
					vhost.Domains = append(vhost.Domains, domain)
				}
			}

			if len(vhost.Domains) > 0 {
				out.VirtualHosts = append(out.VirtualHosts, vhost)
			}
		}
	}
	return out.Normalize()
}

// faults aggregates fault filters across virtual hosts in single http_conn_man
func (rc *HTTPRouteConfig) faults() []*HTTPFilter {
	out := make([]*HTTPFilter, 0)
	for _, host := range rc.VirtualHosts {
		for _, route := range host.Routes {
			out = append(out, route.faults...)
		}
	}
	return out
}

// Clusters returns the clusters for the given route config.
func (rc *HTTPRouteConfig) Clusters() Clusters {
	out := make(Clusters, 0)
	for _, host := range rc.VirtualHosts {
		out = append(out, host.clusters()...)
	}
	return out
}

// Normalize normalizes the route config.
func (rc *HTTPRouteConfig) Normalize() *HTTPRouteConfig {
	hosts := make([]*VirtualHost, len(rc.VirtualHosts))
	copy(hosts, rc.VirtualHosts)
	sort.Slice(hosts, func(i, j int) bool { return hosts[i].Name < hosts[j].Name })
	return &HTTPRouteConfig{ValidateClusters: ValidateClusters, VirtualHosts: hosts}
}

// AccessLog definition.
type AccessLog struct {
	Path   string `json:"path"`
	Format string `json:"format,omitempty"`
	Filter string `json:"filter,omitempty"`
}

// HTTPFilterConfig definition
type HTTPFilterConfig struct {
	CodecType         string                 `json:"codec_type"`
	StatPrefix        string                 `json:"stat_prefix"`
	GenerateRequestID bool                   `json:"generate_request_id,omitempty"`
	UseRemoteAddress  bool                   `json:"use_remote_address,omitempty"`
	Tracing           *HTTPFilterTraceConfig `json:"tracing,omitempty"`
	RouteConfig       *HTTPRouteConfig       `json:"route_config,omitempty"`
	RDS               *RDS                   `json:"rds,omitempty"`
	Filters           []HTTPFilter           `json:"filters"`
	AccessLog         []AccessLog            `json:"access_log,omitempty"`
}

// IsNetworkFilterConfig marks HTTPFilterConfig as an implementation of NetworkFilterConfig
func (*HTTPFilterConfig) IsNetworkFilterConfig() {}

// HTTPFilterTraceConfig definition
type HTTPFilterTraceConfig struct {
	OperationName string `json:"operation_name"`
}

// TCPRoute definition
type TCPRoute struct {
	Cluster           string   `json:"cluster"`
	DestinationIPList []string `json:"destination_ip_list,omitempty"`
	DestinationPorts  string   `json:"destination_ports,omitempty"`
	SourceIPList      []string `json:"source_ip_list,omitempty"`
	SourcePorts       string   `json:"source_ports,omitempty"`

	// special value to retain dependent cluster definition for TCP routes.
	clusterRef *Cluster
}

// TCPRouteByRoute sorts TCP routes over all route sub fields.
type TCPRouteByRoute []*TCPRoute

func (r TCPRouteByRoute) Len() int {
	return len(r)
}

func (r TCPRouteByRoute) Swap(i, j int) {
	r[i], r[j] = r[j], r[i]
}

func (r TCPRouteByRoute) Less(i, j int) bool {
	if r[i].Cluster != r[j].Cluster {
		return r[i].Cluster < r[j].Cluster
	}

	compare := func(a, b []string) bool {
		lenA, lenB := len(a), len(b)
		min := lenA
		if min > lenB {
			min = lenB
		}
		for k := 0; k < min; k++ {
			if a[k] != b[k] {
				return a[k] < b[k]
			}
		}
		return lenA < lenB
	}

	if less := compare(r[i].DestinationIPList, r[j].DestinationIPList); less {
		return less
	}
	if r[i].DestinationPorts != r[j].DestinationPorts {
		return r[i].DestinationPorts < r[j].DestinationPorts
	}
	if less := compare(r[i].SourceIPList, r[j].SourceIPList); less {
		return less
	}
	if r[i].SourcePorts != r[j].SourcePorts {
		return r[i].SourcePorts < r[j].SourcePorts
	}
	return false
}

// TCPProxyFilterConfig definition
type TCPProxyFilterConfig struct {
	StatPrefix  string          `json:"stat_prefix"`
	RouteConfig *TCPRouteConfig `json:"route_config"`
}

// IsNetworkFilterConfig marks TCPProxyFilterConfig as an implementation of NetworkFilterConfig
func (*TCPProxyFilterConfig) IsNetworkFilterConfig() {}

// TCPRouteConfig (or generalize as RouteConfig or L4RouteConfig for TCP/UDP?)
type TCPRouteConfig struct {
	Routes []*TCPRoute `json:"routes"`
}

// MongoProxyFilterConfig definition
type MongoProxyFilterConfig struct {
	StatPrefix string `json:"stat_prefix"`
}

// IsNetworkFilterConfig marks MongoProxyFilterConfig as an implementation of NetworkFilterConfig
func (*MongoProxyFilterConfig) IsNetworkFilterConfig() {}

// CORSFilterConfig definition
// See: https://www.envoyproxy.io/envoy/configuration/http_filters/cors_filter.html#config-http-filters-cors
type CORSFilterConfig struct{}

// IsNetworkFilterConfig marks CORSFilterConfig as an implementation of NetworkFilterConfig
func (*CORSFilterConfig) IsNetworkFilterConfig() {}

// RedisConnPool definition
type RedisConnPool struct {
	OperationTimeoutMS int64 `json:"op_timeout_ms"`
}

// RedisProxyFilterConfig definition
type RedisProxyFilterConfig struct {
	ClusterName string         `json:"cluster_name"`
	ConnPool    *RedisConnPool `json:"conn_pool"`
	StatPrefix  string         `json:"stat_prefix"`
}

// IsNetworkFilterConfig marks RedisProxyFilterConfig as an implementation of NetworkFilterConfig
func (*RedisProxyFilterConfig) IsNetworkFilterConfig() {}

// NetworkFilter definition
type NetworkFilter struct {
	Type   string              `json:"-"`
	Name   string              `json:"name"`
	Config NetworkFilterConfig `json:"config"`
}

// NetworkFilterConfig is a marker interface
type NetworkFilterConfig interface {
	IsNetworkFilterConfig()
}

// NetworkFilterTypes maps filter names to types of structs that implement them. It is used when unmarshaling JSON data.
// To add your own NetworkFilter types, add additional entries to this map prior to calling json.Unmarshal.
var NetworkFilterTypes = map[string]reflect.Type{
	RedisProxyFilter:      reflect.TypeOf(RedisProxyFilterConfig{}),
	CORSFilter:            reflect.TypeOf(CORSFilterConfig{}),
	MongoProxyFilter:      reflect.TypeOf(MongoProxyFilterConfig{}),
	TCPProxyFilter:        reflect.TypeOf(TCPProxyFilterConfig{}),
	HTTPConnectionManager: reflect.TypeOf(HTTPFilterConfig{}),
	MixerFilter:           reflect.TypeOf(FilterMixerConfig{}),
}

// UnmarshalJSON handles custom unmarshal logic for the NetworkFilter struct. This is needed because the config field
// depends on the filter name.
func (nf *NetworkFilter) UnmarshalJSON(b []byte) error {

	// First, unmarshal to a generic data structure so we can get the name.
	var j interface{}
	err := json.Unmarshal(b, &j)
	if err != nil {
		return err
	}
	m := j.(map[string]interface{})
	n, ok := m["name"].(string)
	if !ok {
		return errors.New("filter missing name field")
	}

	// Once we have the name, we can look up the concrete type of the config field.
	t, ok := NetworkFilterTypes[n]
	if !ok {
		return fmt.Errorf("unknown filter name: %s", n)
	}
	v := reflect.New(t)

	// Since Unmarshal takes a []byte, we re-marshall the config and then call Unmarshal on it.
	cfgBytes, err := json.Marshal(m["config"])
	if err != nil {
		return err
	}
	err = json.Unmarshal(cfgBytes, v.Interface())
	if err != nil {
		return err
	}

	// Fill in the NetworkFilter
	nf.Name = n
	nf.Type = m["type"].(string)
	nf.Config = v.Interface().(NetworkFilterConfig)
	return nil
}

// Listener definition
type Listener struct {
	Address        string           `json:"address"`
	Name           string           `json:"name,omitempty"`
	Filters        []*NetworkFilter `json:"filters"`
	SSLContext     *SSLContext      `json:"ssl_context,omitempty"`
	BindToPort     bool             `json:"bind_to_port"`
	UseOriginalDst bool             `json:"use_original_dst,omitempty"`
}

// Listeners is a collection of listeners
type Listeners []*Listener

//Append append some listeners
func (l *Listeners) Append(new Listeners) {
	*l = append(*l, new...)
}

//CreateHTTPCommonListener create simple http common listener
//listen port 80
func CreateHTTPCommonListener(name string, vh ...*VirtualHost) *Listener {
	rcg := &HTTPRouteConfig{
		VirtualHosts: vh,
	}
	hsf := HTTPFilter{
		Type:   "decoder",
		Name:   "router",
		Config: make(map[string]string),
	}
	lhc := &HTTPFilterConfig{
		CodecType:   "auto",
		StatPrefix:  "ingress_http",
		RouteConfig: rcg,
		Filters:     []HTTPFilter{hsf},
	}
	lfs := &NetworkFilter{
		Name:   "http_connection_manager",
		Config: lhc,
	}
	plds := &Listener{
		Name:       name,
		Address:    fmt.Sprintf("tcp://127.0.0.1:%d", 80),
		Filters:    []*NetworkFilter{lfs},
		BindToPort: true,
	}
	return plds
}

//CreateTCPCommonListener create tcp simple common listener
//listen the specified port
//associate the specified cluster.
func CreateTCPCommonListener(listenerName, clusterName string, address string) *Listener {
	ptr := &TCPRoute{
		Cluster: clusterName,
	}
	lrs := &TCPRouteConfig{
		Routes: []*TCPRoute{ptr},
	}
	lcg := &TCPProxyFilterConfig{
		StatPrefix:  listenerName,
		RouteConfig: lrs,
	}
	lfs := &NetworkFilter{
		Name:   "tcp_proxy",
		Config: lcg,
	}
	plds := &Listener{
		Name:       listenerName,
		Address:    address,
		Filters:    []*NetworkFilter{lfs},
		BindToPort: true,
	}
	return plds
}

// Normalize sorts and de-duplicates listeners by address
func (listeners Listeners) normalize() Listeners {
	out := make(Listeners, 0, len(listeners))
	set := make(map[string]*Listener)
	for _, listener := range listeners {
		if l, collision := set[listener.Address]; collision {
			ol, _ := json.Marshal(*l)
			ll, _ := json.Marshal(*listener)
			logrus.Errorf("Listener collision for %s\n---\n%s\n--- rejected ---\n%s", listener.Address,
				string(ol), string(ll))
			continue
		}
		out = append(out, listener)
		set[listener.Address] = listener
	}
	sort.Slice(out, func(i, j int) bool { return out[i].Address < out[j].Address })
	return out
}

// GetByAddress returns a listener by its address
func (listeners Listeners) GetByAddress(addr string) *Listener {
	for _, listener := range listeners {
		if listener.Address == addr {
			return listener
		}
	}
	return nil
}

// SSLContext definition
type SSLContext struct {
	CertChainFile            string `json:"cert_chain_file"`
	PrivateKeyFile           string `json:"private_key_file"`
	CaCertFile               string `json:"ca_cert_file,omitempty"`
	RequireClientCertificate bool   `json:"require_client_certificate"`
	ALPNProtocols            string `json:"alpn_protocols,omitempty"`
}

// SSLContextExternal definition
type SSLContextExternal struct {
	CaCertFile string `json:"ca_cert_file,omitempty"`
}

// SSLContextWithSAN definition, VerifySubjectAltName cannot be nil.
type SSLContextWithSAN struct {
	CertChainFile        string   `json:"cert_chain_file"`
	PrivateKeyFile       string   `json:"private_key_file"`
	CaCertFile           string   `json:"ca_cert_file,omitempty"`
	VerifySubjectAltName []string `json:"verify_subject_alt_name"`
}

// Admin definition
type Admin struct {
	AccessLogPath string `json:"access_log_path"`
	Address       string `json:"address"`
}

// Host definition
type Host struct {
	URL string `json:"url"`
}

// Cluster definition
type Cluster struct {
	Name                     string            `json:"name"`
	ServiceName              string            `json:"service_name,omitempty"`
	ConnectTimeoutMs         int64             `json:"connect_timeout_ms"`
	Type                     string            `json:"type"`
	LbType                   string            `json:"lb_type"`
	MaxRequestsPerConnection int               `json:"max_requests_per_connection,omitempty"`
	Hosts                    []Host            `json:"hosts,omitempty"`
	SSLContext               interface{}       `json:"ssl_context,omitempty"`
	Features                 string            `json:"features,omitempty"`
	CircuitBreaker           *CircuitBreaker   `json:"circuit_breakers,omitempty"`
	OutlierDetection         *OutlierDetection `json:"outlier_detection,omitempty"`
}

// CircuitBreaker definition
// See: https://lyft.github.io/envoy/docs/configuration/cluster_manager/cluster_circuit_breakers.html#circuit-breakers
type CircuitBreaker struct {
	Default DefaultCBPriority `json:"default"`
}

// DefaultCBPriority defines the circuit breaker for default cluster priority
type DefaultCBPriority struct {
	MaxConnections     int `json:"max_connections"`
	MaxPendingRequests int `json:"max_pending_requests"`
	MaxRequests        int `json:"max_requests"`
	MaxRetries         int `json:"max_retries"`
}

// OutlierDetection definition
// See: https://lyft.github.io/envoy/docs/configuration/cluster_manager/cluster_runtime.html#outlier-detection
type OutlierDetection struct {
	ConsecutiveErrors  int   `json:"consecutive_5xx,omitempty"`
	IntervalMS         int64 `json:"interval_ms,omitempty"`
	BaseEjectionTimeMS int64 `json:"base_ejection_time_ms,omitempty"`
	MaxEjectionPercent int   `json:"max_ejection_percent,omitempty"`
}

// Clusters is a collection of clusters
type Clusters []*Cluster

//Append append some clusters
func (c *Clusters) Append(new Clusters) {
	*c = append(*c, new...)
}

// Normalize deduplicates and sorts clusters by name
func (c Clusters) Normalize() Clusters {
	out := make(Clusters, 0, len(c))
	set := make(map[string]bool)
	for _, cluster := range c {
		if !set[cluster.Name] {
			set[cluster.Name] = true
			out = append(out, cluster)
		}
	}
	sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
	return out
}

// RoutesByPath sorts routes by their path and/or prefix, such that:
// - Exact path routes are "less than" than prefix path routes
// - Exact path routes are sorted lexicographically
// - Prefix path routes are sorted anti-lexicographically
//
// This order ensures that prefix path routes do not shadow more
// specific routes which share the same prefix.
type RoutesByPath []*HTTPRoute

func (r RoutesByPath) Len() int {
	return len(r)
}

func (r RoutesByPath) Swap(i, j int) {
	r[i], r[j] = r[j], r[i]
}

func (r RoutesByPath) Less(i, j int) bool {
	if r[i].Path != "" {
		if r[j].Path != "" {
			// i and j are both path
			return r[i].Path < r[j].Path
		}
		// i is path and j is prefix => i is "less than" j
		return true
	}
	if r[j].Path != "" {
		// i is prefix nad j is path => j is "less than" i
		return false
	}
	// i and j are both prefix
	return r[i].Prefix > r[j].Prefix
}

// Headers sorts headers
type Headers []Header

func (s Headers) Len() int {
	return len(s)
}

func (s Headers) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func (s Headers) Less(i, j int) bool {
	if s[i].Name == s[j].Name {
		if s[i].Regex == s[j].Regex {
			return s[i].Value < s[j].Value
		}
		// true is less, false is more
		return s[i].Regex
	}
	return s[i].Name < s[j].Name
}

// DiscoveryCluster is a service discovery service definition
type DiscoveryCluster struct {
	Cluster        *Cluster `json:"cluster"`
	RefreshDelayMs int64    `json:"refresh_delay_ms"`
}

// LDSCluster is a reference to LDS cluster by name
type LDSCluster struct {
	Cluster        string `json:"cluster"`
	RefreshDelayMs int64  `json:"refresh_delay_ms"`
}

// CDSCluter is result struct for cds api
type CDSCluter struct {
	Clusters Clusters `json:"clusters"`
}

// DiscoverHost is hosts that make up the service.
type DiscoverHost struct {
	Address string `json:"ip_address"`
	Port    int    `json:"port"`
	Tags    *Tags  `json:"tags,omitempty"`
}

// Tags is Discover host tags
type Tags struct {
	AZ     string `json:"az,omitempty"`
	Canary bool   `json:"canary,omitempty"`

	// Weight is an integer in the range [1, 100] or empty
	Weight int `json:"load_balancing_weight,omitempty"`
}

//DiscoverHosts  is a collection of DiscoverHost
type DiscoverHosts []*DiscoverHost

//SDSHost is result struct for sds api
type SDSHost struct {
	Hosts DiscoverHosts `json:"hosts"`
}

// LDSListener is result struct for lds api
type LDSListener struct {
	Listeners Listeners `json:"listeners"`
}

// RDS definition
type RDS struct {
	Cluster         string `json:"cluster"`
	RouteConfigName string `json:"route_config_name"`
	RefreshDelayMs  int64  `json:"refresh_delay_ms"`
}

// ClusterManager definition
type ClusterManager struct {
	Clusters         Clusters          `json:"clusters"`
	SDS              *DiscoveryCluster `json:"sds,omitempty"`
	CDS              *DiscoveryCluster `json:"cds,omitempty"`
	LocalClusterName string            `json:"local_cluster_name,omitempty"`
}

// FilterMixerConfig definition.
//
// NOTE: all fields marked as DEPRECATED are part of the original v1
// mixerclient configuration. They are deprecated and will be
// eventually removed once proxies are updated.
//
// Going forwards all mixerclient configuration should represeted by
// istio.io/api/mixer/v1/config/client/mixer_filter_config.proto and
// encoded in the `V2` field below.
//
type FilterMixerConfig struct {
	// DEPRECATED: MixerAttributes specifies the static list of attributes that are sent with
	// each request to Mixer.
	MixerAttributes map[string]string `json:"mixer_attributes,omitempty"`

	// DEPRECATED: ForwardAttributes specifies the list of attribute keys and values that
	// are forwarded as an HTTP header to the server side proxy
	ForwardAttributes map[string]string `json:"forward_attributes,omitempty"`

	// DEPRECATED: QuotaName specifies the name of the quota bucket to withdraw tokens from;
	// an empty name means no quota will be charged.
	QuotaName string `json:"quota_name,omitempty"`

	// DEPRECATED: If set to true, disables mixer check calls for TCP connections
	DisableTCPCheckCalls bool `json:"disable_tcp_check_calls,omitempty"`

	// istio.io/api/mixer/v1/config/client configuration protobuf
	// encoded as a generic map using canonical JSON encoding.
	//
	// If `V2` field is not empty, the DEPRECATED fields above should
	// be discarded.
	V2 map[string]interface{} `json:"v2,omitempty"`
}

// IsNetworkFilterConfig marks FilterMixerConfig as an implementation of NetworkFilterConfig
func (*FilterMixerConfig) IsNetworkFilterConfig() {}
