package v1

import (
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/conversion"
	"k8s.io/kubernetes/pkg/runtime"
	"k8s.io/kubernetes/pkg/util/sets"

	"github.com/openshift/origin/pkg/api/extension"
	internal "github.com/openshift/origin/pkg/cmd/server/api"
	"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
)

func addDefaultingFuncs(scheme *runtime.Scheme) error {
	return scheme.AddDefaultingFuncs(
		func(obj *MasterConfig) {
			if len(obj.APILevels) == 0 {
				obj.APILevels = internal.DefaultOpenShiftAPILevels
			}
			if len(obj.Controllers) == 0 {
				obj.Controllers = ControllersAll
			}
			if obj.ServingInfo.RequestTimeoutSeconds == 0 {
				obj.ServingInfo.RequestTimeoutSeconds = 60 * 60
			}
			if obj.ServingInfo.MaxRequestsInFlight == 0 {
				obj.ServingInfo.MaxRequestsInFlight = 500
			}
			if len(obj.PolicyConfig.OpenShiftInfrastructureNamespace) == 0 {
				obj.PolicyConfig.OpenShiftInfrastructureNamespace = bootstrappolicy.DefaultOpenShiftInfraNamespace
			}
			if len(obj.RoutingConfig.Subdomain) == 0 {
				obj.RoutingConfig.Subdomain = "router.default.svc.cluster.local"
			}
			if len(obj.JenkinsPipelineConfig.TemplateNamespace) == 0 {
				obj.JenkinsPipelineConfig.TemplateNamespace = "openshift"
			}
			if len(obj.JenkinsPipelineConfig.TemplateName) == 0 {
				obj.JenkinsPipelineConfig.TemplateName = "jenkins-ephemeral"
			}
			if len(obj.JenkinsPipelineConfig.ServiceName) == 0 {
				obj.JenkinsPipelineConfig.ServiceName = "jenkins"
			}
			if obj.JenkinsPipelineConfig.AutoProvisionEnabled == nil {
				v := true
				obj.JenkinsPipelineConfig.AutoProvisionEnabled = &v
			}

			if obj.MasterClients.OpenShiftLoopbackClientConnectionOverrides == nil {
				obj.MasterClients.OpenShiftLoopbackClientConnectionOverrides = &ClientConnectionOverrides{}
			}
			// historical values
			if obj.MasterClients.OpenShiftLoopbackClientConnectionOverrides.QPS <= 0 {
				obj.MasterClients.OpenShiftLoopbackClientConnectionOverrides.QPS = 150.0
			}
			if obj.MasterClients.OpenShiftLoopbackClientConnectionOverrides.Burst <= 0 {
				obj.MasterClients.OpenShiftLoopbackClientConnectionOverrides.Burst = 300
			}
			setDefault_ClientConnectionOverrides(obj.MasterClients.OpenShiftLoopbackClientConnectionOverrides)

			if obj.MasterClients.ExternalKubernetesClientConnectionOverrides == nil {
				obj.MasterClients.ExternalKubernetesClientConnectionOverrides = &ClientConnectionOverrides{}
			}
			// historical values
			if obj.MasterClients.ExternalKubernetesClientConnectionOverrides.QPS <= 0 {
				obj.MasterClients.ExternalKubernetesClientConnectionOverrides.QPS = 100.0
			}
			if obj.MasterClients.ExternalKubernetesClientConnectionOverrides.Burst <= 0 {
				obj.MasterClients.ExternalKubernetesClientConnectionOverrides.Burst = 200
			}
			setDefault_ClientConnectionOverrides(obj.MasterClients.ExternalKubernetesClientConnectionOverrides)

			// Populate the new NetworkConfig.ServiceNetworkCIDR field from the KubernetesMasterConfig.ServicesSubnet field if needed
			if len(obj.NetworkConfig.ServiceNetworkCIDR) == 0 {
				if obj.KubernetesMasterConfig != nil && len(obj.KubernetesMasterConfig.ServicesSubnet) > 0 {
					// if a subnet is set in the kubernetes master config, use that
					obj.NetworkConfig.ServiceNetworkCIDR = obj.KubernetesMasterConfig.ServicesSubnet
				} else {
					// default ServiceClusterIPRange used by kubernetes if nothing is specified
					obj.NetworkConfig.ServiceNetworkCIDR = "10.0.0.0/24"
				}
			}

			// TODO Detect cloud provider when not using built-in kubernetes
			kubeConfig := obj.KubernetesMasterConfig
			noCloudProvider := kubeConfig != nil && (len(kubeConfig.ControllerArguments["cloud-provider"]) == 0 || kubeConfig.ControllerArguments["cloud-provider"][0] == "")

			if noCloudProvider && len(obj.NetworkConfig.IngressIPNetworkCIDR) == 0 {
				cidr := internal.DefaultIngressIPNetworkCIDR
				if !(internal.CIDRsOverlap(cidr, obj.NetworkConfig.ClusterNetworkCIDR) || internal.CIDRsOverlap(cidr, obj.NetworkConfig.ServiceNetworkCIDR)) {
					obj.NetworkConfig.IngressIPNetworkCIDR = cidr
				}
			}

			// Historically, the clientCA was incorrectly used as the master's server cert CA bundle
			// If missing from the config, migrate the ClientCA into that field
			if obj.OAuthConfig != nil && obj.OAuthConfig.MasterCA == nil {
				s := obj.ServingInfo.ClientCA
				// The final value of OAuthConfig.MasterCA should never be nil
				obj.OAuthConfig.MasterCA = &s
			}
		},
		func(obj *KubernetesMasterConfig) {
			if obj.MasterCount == 0 {
				obj.MasterCount = 1
			}
			if len(obj.APILevels) == 0 {
				obj.APILevels = internal.DefaultKubernetesAPILevels
			}
			if len(obj.ServicesNodePortRange) == 0 {
				obj.ServicesNodePortRange = "30000-32767"
			}
			if len(obj.PodEvictionTimeout) == 0 {
				obj.PodEvictionTimeout = "5m"
			}
		},
		func(obj *NodeConfig) {
			if obj.MasterClientConnectionOverrides == nil {
				obj.MasterClientConnectionOverrides = &ClientConnectionOverrides{
					// historical values
					QPS:   10.0,
					Burst: 20,
				}
			}
			setDefault_ClientConnectionOverrides(obj.MasterClientConnectionOverrides)

			// Defaults/migrations for NetworkConfig
			if len(obj.NetworkConfig.NetworkPluginName) == 0 {
				obj.NetworkConfig.NetworkPluginName = obj.DeprecatedNetworkPluginName
			}
			if obj.NetworkConfig.MTU == 0 {
				obj.NetworkConfig.MTU = 1450
			}
			if len(obj.IPTablesSyncPeriod) == 0 {
				obj.IPTablesSyncPeriod = "30s"
			}

			// Auth cache defaults
			if len(obj.AuthConfig.AuthenticationCacheTTL) == 0 {
				obj.AuthConfig.AuthenticationCacheTTL = "5m"
			}
			if obj.AuthConfig.AuthenticationCacheSize == 0 {
				obj.AuthConfig.AuthenticationCacheSize = 1000
			}
			if len(obj.AuthConfig.AuthorizationCacheTTL) == 0 {
				obj.AuthConfig.AuthorizationCacheTTL = "5m"
			}
			if obj.AuthConfig.AuthorizationCacheSize == 0 {
				obj.AuthConfig.AuthorizationCacheSize = 1000
			}

			// EnableUnidling by default
			if obj.EnableUnidling == nil {
				v := true
				obj.EnableUnidling = &v
			}
		},
		func(obj *EtcdStorageConfig) {
			if len(obj.KubernetesStorageVersion) == 0 {
				obj.KubernetesStorageVersion = "v1"
			}
			if len(obj.KubernetesStoragePrefix) == 0 {
				obj.KubernetesStoragePrefix = "kubernetes.io"
			}
			if len(obj.OpenShiftStorageVersion) == 0 {
				obj.OpenShiftStorageVersion = internal.DefaultOpenShiftStorageVersionLevel
			}
			if len(obj.OpenShiftStoragePrefix) == 0 {
				obj.OpenShiftStoragePrefix = "openshift.io"
			}
		},
		func(obj *DockerConfig) {
			if len(obj.ExecHandlerName) == 0 {
				obj.ExecHandlerName = DockerExecHandlerNative
			}
		},
		func(obj *ServingInfo) {
			if len(obj.BindNetwork) == 0 {
				obj.BindNetwork = "tcp4"
			}
		},
		func(obj *ImagePolicyConfig) {
			if obj.MaxImagesBulkImportedPerRepository == 0 {
				obj.MaxImagesBulkImportedPerRepository = 5
			}
			if obj.MaxScheduledImageImportsPerMinute == 0 {
				obj.MaxScheduledImageImportsPerMinute = 60
			}
			if obj.ScheduledImageImportMinimumIntervalSeconds == 0 {
				obj.ScheduledImageImportMinimumIntervalSeconds = 15 * 60
			}
		},
		func(obj *DNSConfig) {
			if len(obj.BindNetwork) == 0 {
				obj.BindNetwork = "tcp4"
			}
		},
		func(obj *SecurityAllocator) {
			if len(obj.UIDAllocatorRange) == 0 {
				obj.UIDAllocatorRange = "1000000000-1999999999/10000"
			}
			if len(obj.MCSAllocatorRange) == 0 {
				obj.MCSAllocatorRange = "s0:/2"
			}
			if obj.MCSLabelsPerProject == 0 {
				obj.MCSLabelsPerProject = 5
			}
		},
		func(obj *IdentityProvider) {
			if len(obj.MappingMethod) == 0 {
				// By default, only let one identity provider authenticate a particular user
				// If multiple identity providers collide, the second one in will fail to auth
				// The admin can set this to "add" if they want to allow new identities to join existing users
				obj.MappingMethod = "claim"
			}
		},
		func(obj *GrantConfig) {
			if len(obj.ServiceAccountMethod) == 0 {
				obj.ServiceAccountMethod = "prompt"
			}
		},
	)
}

func addConversionFuncs(scheme *runtime.Scheme) error {
	return scheme.AddConversionFuncs(
		convert_runtime_Object_To_runtime_RawExtension,
		convert_runtime_RawExtension_To_runtime_Object,

		func(in *NodeConfig, out *internal.NodeConfig, s conversion.Scope) error {
			return s.DefaultConvert(in, out, conversion.IgnoreMissingFields)
		},
		func(in *internal.NodeConfig, out *NodeConfig, s conversion.Scope) error {
			return s.DefaultConvert(in, out, conversion.IgnoreMissingFields)
		},
		func(in *KubernetesMasterConfig, out *internal.KubernetesMasterConfig, s conversion.Scope) error {
			if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
				return err
			}

			if out.DisabledAPIGroupVersions == nil {
				out.DisabledAPIGroupVersions = map[string][]string{}
			}

			// the APILevels (whitelist) needs to be converted into an internal blacklist
			if len(in.APILevels) == 0 {
				out.DisabledAPIGroupVersions[internal.APIGroupKube] = []string{"*"}

			} else {
				availableLevels := internal.KubeAPIGroupsToAllowedVersions[internal.APIGroupKube]
				whitelistedLevels := sets.NewString(in.APILevels...)
				blacklistedLevels := []string{}

				for _, curr := range availableLevels {
					if !whitelistedLevels.Has(curr) {
						blacklistedLevels = append(blacklistedLevels, curr)
					}
				}

				if len(blacklistedLevels) > 0 {
					out.DisabledAPIGroupVersions[internal.APIGroupKube] = blacklistedLevels
				}
			}

			return nil
		},
		func(in *internal.KubernetesMasterConfig, out *KubernetesMasterConfig, s conversion.Scope) error {
			// internal doesn't have all fields: APILevels
			return s.DefaultConvert(in, out, conversion.IgnoreMissingFields)
		},
		func(in *ServingInfo, out *internal.ServingInfo, s conversion.Scope) error {
			if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
				return err
			}
			out.ServerCert.CertFile = in.CertFile
			out.ServerCert.KeyFile = in.KeyFile
			return nil
		},
		func(in *internal.ServingInfo, out *ServingInfo, s conversion.Scope) error {
			if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
				return err
			}
			out.CertFile = in.ServerCert.CertFile
			out.KeyFile = in.ServerCert.KeyFile
			return nil
		},
		func(in *RemoteConnectionInfo, out *internal.RemoteConnectionInfo, s conversion.Scope) error {
			out.URL = in.URL
			out.CA = in.CA
			out.ClientCert.CertFile = in.CertFile
			out.ClientCert.KeyFile = in.KeyFile
			return nil
		},
		func(in *internal.RemoteConnectionInfo, out *RemoteConnectionInfo, s conversion.Scope) error {
			out.URL = in.URL
			out.CA = in.CA
			out.CertFile = in.ClientCert.CertFile
			out.KeyFile = in.ClientCert.KeyFile
			return nil
		},
		func(in *EtcdConnectionInfo, out *internal.EtcdConnectionInfo, s conversion.Scope) error {
			out.URLs = in.URLs
			out.CA = in.CA
			out.ClientCert.CertFile = in.CertFile
			out.ClientCert.KeyFile = in.KeyFile
			return nil
		},
		func(in *internal.EtcdConnectionInfo, out *EtcdConnectionInfo, s conversion.Scope) error {
			out.URLs = in.URLs
			out.CA = in.CA
			out.CertFile = in.ClientCert.CertFile
			out.KeyFile = in.ClientCert.KeyFile
			return nil
		},
		func(in *KubeletConnectionInfo, out *internal.KubeletConnectionInfo, s conversion.Scope) error {
			out.Port = in.Port
			out.CA = in.CA
			out.ClientCert.CertFile = in.CertFile
			out.ClientCert.KeyFile = in.KeyFile
			return nil
		},
		func(in *internal.KubeletConnectionInfo, out *KubeletConnectionInfo, s conversion.Scope) error {
			out.Port = in.Port
			out.CA = in.CA
			out.CertFile = in.ClientCert.CertFile
			out.KeyFile = in.ClientCert.KeyFile
			return nil
		},
		func(in *MasterVolumeConfig, out *internal.MasterVolumeConfig, s conversion.Scope) error {
			out.DynamicProvisioningEnabled = (in.DynamicProvisioningEnabled == nil) || (*in.DynamicProvisioningEnabled)
			return nil
		},
		func(in *internal.MasterVolumeConfig, out *MasterVolumeConfig, s conversion.Scope) error {
			enabled := in.DynamicProvisioningEnabled
			out.DynamicProvisioningEnabled = &enabled
			return nil
		},

		api.Convert_resource_Quantity_To_resource_Quantity,
		api.Convert_bool_To_Pointer_bool,
		api.Convert_Pointer_bool_To_bool,
	)
}

// convert_runtime_Object_To_runtime_RawExtension attempts to convert runtime.Objects to the appropriate target.
func convert_runtime_Object_To_runtime_RawExtension(in *runtime.Object, out *runtime.RawExtension, s conversion.Scope) error {
	return extension.Convert_runtime_Object_To_runtime_RawExtension(internal.Scheme, in, out, s)
}

// convert_runtime_RawExtension_To_runtime_Object attempts to convert an incoming object into the
// appropriate output type.
func convert_runtime_RawExtension_To_runtime_Object(in *runtime.RawExtension, out *runtime.Object, s conversion.Scope) error {
	return extension.Convert_runtime_RawExtension_To_runtime_Object(internal.Scheme, in, out, s)
}

// setDefault_ClientConnectionOverrides defaults a client connection to the pre-1.3 settings of
// being JSON only. Callers must explicitly opt-in to Protobuf support in 1.3+.
func setDefault_ClientConnectionOverrides(overrides *ClientConnectionOverrides) {
	if len(overrides.AcceptContentTypes) == 0 {
		overrides.AcceptContentTypes = "application/json"
	}
	if len(overrides.ContentType) == 0 {
		overrides.ContentType = "application/json"
	}
}

var _ runtime.NestedObjectDecoder = &MasterConfig{}

// DecodeNestedObjects handles encoding RawExtensions on the MasterConfig, ensuring the
// objects are decoded with the provided decoder.
func (c *MasterConfig) DecodeNestedObjects(d runtime.Decoder) error {
	// decoding failures result in a runtime.Unknown object being created in Object and passed
	// to conversion
	for k, v := range c.AdmissionConfig.PluginConfig {
		extension.DecodeNestedRawExtensionOrUnknown(d, &v.Configuration)
		c.AdmissionConfig.PluginConfig[k] = v
	}
	if c.KubernetesMasterConfig != nil {
		for k, v := range c.KubernetesMasterConfig.AdmissionConfig.PluginConfig {
			extension.DecodeNestedRawExtensionOrUnknown(d, &v.Configuration)
			c.KubernetesMasterConfig.AdmissionConfig.PluginConfig[k] = v
		}
	}
	if c.OAuthConfig != nil {
		for i := range c.OAuthConfig.IdentityProviders {
			extension.DecodeNestedRawExtensionOrUnknown(d, &c.OAuthConfig.IdentityProviders[i].Provider)
		}
	}
	return nil
}

var _ runtime.NestedObjectEncoder = &MasterConfig{}

// EncodeNestedObjects handles encoding RawExtensions on the MasterConfig, ensuring the
// objects are encoded with the provided encoder.
func (c *MasterConfig) EncodeNestedObjects(e runtime.Encoder) error {
	for k, v := range c.AdmissionConfig.PluginConfig {
		if err := extension.EncodeNestedRawExtension(e, &v.Configuration); err != nil {
			return err
		}
		c.AdmissionConfig.PluginConfig[k] = v
	}
	if c.KubernetesMasterConfig != nil {
		for k, v := range c.KubernetesMasterConfig.AdmissionConfig.PluginConfig {
			if err := extension.EncodeNestedRawExtension(e, &v.Configuration); err != nil {
				return err
			}
			c.KubernetesMasterConfig.AdmissionConfig.PluginConfig[k] = v
		}
	}
	if c.OAuthConfig != nil {
		for i := range c.OAuthConfig.IdentityProviders {
			if err := extension.EncodeNestedRawExtension(e, &c.OAuthConfig.IdentityProviders[i].Provider); err != nil {
				return err
			}
		}
	}
	return nil
}