package v1

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

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

func addDefaultingFuncs(scheme *runtime.Scheme) {
	err := 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"
			}
			if len(obj.JenkinsPipelineConfig.ServiceName) == 0 {
				obj.JenkinsPipelineConfig.ServiceName = "jenkins"
			}
			if obj.JenkinsPipelineConfig.Enabled == nil {
				v := true
				obj.JenkinsPipelineConfig.Enabled = &v
			}

			// 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"
				}
			}

			// 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) {
			// 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
			}
		},
		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"
			}
		},
	)
	if err != nil {
		// If one of the conversion functions is malformed, detect it immediately.
		panic(err)
	}
}

func addConversionFuncs(scheme *runtime.Scheme) {
	err := scheme.AddConversionFuncs(
		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 *internal.IdentityProvider, out *IdentityProvider, s conversion.Scope) error {
			if err := convert_runtime_Object_To_runtime_RawExtension(in.Provider, &out.Provider, s); err != nil {
				return err
			}
			out.Name = in.Name
			out.UseAsChallenger = in.UseAsChallenger
			out.UseAsLogin = in.UseAsLogin
			out.MappingMethod = in.MappingMethod
			return nil
		},
		func(in *IdentityProvider, out *internal.IdentityProvider, s conversion.Scope) error {
			if err := convert_runtime_RawExtension_To_runtime_Object(&in.Provider, out.Provider, s); err != nil {
				return err
			}
			if in.Provider.Object != nil {
				var err error
				out.Provider, err = internal.Scheme.ConvertToVersion(in.Provider.Object, internal.SchemeGroupVersion)
				if err != nil {
					return err
				}
			}
			out.Name = in.Name
			out.UseAsChallenger = in.UseAsChallenger
			out.UseAsLogin = in.UseAsLogin
			out.MappingMethod = in.MappingMethod
			return nil
		},
		func(in *internal.AdmissionPluginConfig, out *AdmissionPluginConfig, s conversion.Scope) error {
			if err := convert_runtime_Object_To_runtime_RawExtension(in.Configuration, &out.Configuration, s); err != nil {
				return err
			}
			out.Location = in.Location
			return nil
		},
		func(in *AdmissionPluginConfig, out *internal.AdmissionPluginConfig, s conversion.Scope) error {
			if err := convert_runtime_RawExtension_To_runtime_Object(&in.Configuration, out.Configuration, s); err != nil {
				return err
			}
			if in.Configuration.Object != nil {
				var err error
				out.Configuration, err = internal.Scheme.ConvertToVersion(in.Configuration.Object, internal.SchemeGroupVersion)
				if err != nil {
					return err
				}
			}
			out.Location = in.Location
			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,
	)
	if err != nil {
		// If one of the conversion functions is malformed, detect it immediately.
		panic(err)
	}
}

var codec = serializer.NewCodecFactory(internal.Scheme).LegacyCodec(SchemeGroupVersion)

// Convert_runtime_Object_To_runtime_RawExtension is conversion function that assumes that the runtime.Object you've embedded is in
// the same GroupVersion that your containing type is in.  This is significantly better than simply breaking.
// Given an ordered list of preferred external versions for a given encode or conversion call, the behavior of this function could be
// made generic, predictable, and controllable.
func convert_runtime_Object_To_runtime_RawExtension(in runtime.Object, out *runtime.RawExtension, s conversion.Scope) error {
	if in == nil {
		return nil
	}

	externalObject, err := internal.Scheme.ConvertToVersion(in, s.Meta().DestVersion)
	if runtime.IsNotRegisteredError(err) {
		switch cast := in.(type) {
		case *runtime.Unknown:
			out.Raw = cast.Raw
			return nil
		case *runtime.Unstructured:
			bytes, err := runtime.Encode(runtime.UnstructuredJSONScheme, externalObject)
			if err != nil {
				return err
			}
			out.Raw = bytes
			return nil
		}
	}
	if err != nil {
		return err
	}

	bytes, err := runtime.Encode(codec, externalObject)
	if err != nil {
		return err
	}

	out.Raw = bytes
	out.Object = externalObject

	return nil
}

// Convert_runtime_RawExtension_To_runtime_Object well, this is the reason why there was runtime.Embedded.  The `out` here is hopeless.
// The caller doesn't know the type ahead of time and that means this method can't communicate the return value.  This sucks really badly.
// I'm going to set the `in.Object` field can have callers to this function do magic to pull it back out.  I'm also going to bitch about it.
func convert_runtime_RawExtension_To_runtime_Object(in *runtime.RawExtension, out runtime.Object, s conversion.Scope) error {
	if in == nil || len(in.Raw) == 0 || in.Object != nil {
		return nil
	}

	decodedObject, err := runtime.Decode(codec, in.Raw)
	if err != nil {
		in.Object = &runtime.Unknown{Raw: in.Raw}
		return nil
	}

	internalObject, err := internal.Scheme.ConvertToVersion(decodedObject, s.Meta().DestVersion)
	if err != nil {
		return err
	}

	in.Object = internalObject

	return nil
}