/*
 * This file is part of the KubeVirt project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Copyright 2018 Red Hat, Inc.
 *
 */

package libvmi

import (
	k8sv1 "k8s.io/api/core/v1"
	v1 "kubevirt.io/api/core/v1"
)

func WithSecretDisk(secretName, volumeName string) Option {
	return WithLabelledSecretDisk(secretName, volumeName, "")
}

func WithSysprepSecret(volumeName, secretName string) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		vmi.Spec.Volumes = append(vmi.Spec.Volumes, newSysprepVolume(volumeName, &v1.SysprepSource{
			Secret: &k8sv1.LocalObjectReference{
				Name: secretName,
			},
		}))
	}
}

func WithLabelledSecretDisk(secretName, volumeName, label string) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		vmi.Spec.Volumes = append(vmi.Spec.Volumes, newSecretVolume(secretName, volumeName, label))
		vmi.Spec.Domain.Devices.Disks = append(vmi.Spec.Domain.Devices.Disks, v1.Disk{
			Name: volumeName,
		})
	}
}

func WithConfigMapDisk(configMapName, volumeName string) Option {
	return WithLabelledConfigMapDisk(configMapName, volumeName, "")
}

func WithSysprepConfigMap(volumeName, configMapName string) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		vmi.Spec.Volumes = append(vmi.Spec.Volumes, newSysprepVolume(volumeName, &v1.SysprepSource{
			ConfigMap: &k8sv1.LocalObjectReference{
				Name: configMapName,
			},
		}))
	}
}

func WithLabelledConfigMapDisk(configMapName, volumeName, label string) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		vmi.Spec.Volumes = append(vmi.Spec.Volumes, newConfigMapVolume(configMapName, volumeName, label))
		vmi.Spec.Domain.Devices.Disks = append(vmi.Spec.Domain.Devices.Disks, v1.Disk{
			Name: volumeName,
		})
	}
}

func WithServiceAccountDisk(name string) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		const volumeSuffix = "-disk"
		vmi.Spec.Volumes = append(vmi.Spec.Volumes, newServiceAccountVolume(name, name+volumeSuffix))
		vmi.Spec.Domain.Devices.Disks = append(vmi.Spec.Domain.Devices.Disks, v1.Disk{
			Name: name + volumeSuffix,
		})
	}
}

func WithDownwardAPIDisk(name string) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		vmi.Spec.Volumes = append(vmi.Spec.Volumes, newDownwardAPIVolume(name))
		vmi.Spec.Domain.Devices.Disks = append(vmi.Spec.Domain.Devices.Disks, v1.Disk{
			Name: name,
		})
	}
}

func WithConfigMapFs(configMapName, volumeName string) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		vmi.Spec.Volumes = append(vmi.Spec.Volumes, newConfigMapVolume(configMapName, volumeName, ""))
		vmi.Spec.Domain.Devices.Filesystems = append(vmi.Spec.Domain.Devices.Filesystems, newVirtiofsFilesystem(volumeName))
	}
}

func WithSecretFs(secretName, volumeName string) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		vmi.Spec.Volumes = append(vmi.Spec.Volumes, newSecretVolume(secretName, volumeName, ""))
		vmi.Spec.Domain.Devices.Filesystems = append(vmi.Spec.Domain.Devices.Filesystems, newVirtiofsFilesystem(volumeName))
	}
}

func WithServiceAccountFs(serviceAccountName, volumeName string) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		vmi.Spec.Volumes = append(vmi.Spec.Volumes, newServiceAccountVolume(serviceAccountName, volumeName))
		vmi.Spec.Domain.Devices.Filesystems = append(vmi.Spec.Domain.Devices.Filesystems, newVirtiofsFilesystem(volumeName))
	}
}

func WithDownwardAPIFs(name string) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		vmi.Spec.Volumes = append(vmi.Spec.Volumes, newDownwardAPIVolume(name))
		vmi.Spec.Domain.Devices.Filesystems = append(vmi.Spec.Domain.Devices.Filesystems, newVirtiofsFilesystem(name))
	}
}

func WithLogSerialConsole(enable bool) Option {
	return func(vmi *v1.VirtualMachineInstance) {
		vmi.Spec.Domain.Devices.LogSerialConsole = &enable
	}
}

func newSecretVolume(secretName, volumeName, label string) v1.Volume {
	return v1.Volume{
		Name: volumeName,
		VolumeSource: v1.VolumeSource{
			Secret: &v1.SecretVolumeSource{
				SecretName:  secretName,
				VolumeLabel: label,
			},
		},
	}
}

func newSysprepVolume(volumeName string, source *v1.SysprepSource) v1.Volume {
	return v1.Volume{
		Name: volumeName,
		VolumeSource: v1.VolumeSource{
			Sysprep: source,
		},
	}
}

func newConfigMapVolume(configMapName, volumeName, label string) v1.Volume {
	return v1.Volume{
		Name: volumeName,
		VolumeSource: v1.VolumeSource{
			ConfigMap: &v1.ConfigMapVolumeSource{
				LocalObjectReference: k8sv1.LocalObjectReference{
					Name: configMapName,
				},
				VolumeLabel: label,
			},
		},
	}
}

func newServiceAccountVolume(serviceAccountName, volumeName string) v1.Volume {
	return v1.Volume{
		Name: volumeName,
		VolumeSource: v1.VolumeSource{
			ServiceAccount: &v1.ServiceAccountVolumeSource{
				ServiceAccountName: serviceAccountName,
			},
		},
	}
}

func newDownwardAPIVolume(name string) v1.Volume {
	return v1.Volume{
		Name: name,
		VolumeSource: v1.VolumeSource{
			DownwardAPI: &v1.DownwardAPIVolumeSource{
				Fields: []k8sv1.DownwardAPIVolumeFile{
					{
						Path: "labels",
						FieldRef: &k8sv1.ObjectFieldSelector{
							FieldPath: "metadata.labels",
						},
					},
				},
				VolumeLabel: "",
			},
		},
	}
}
