/**
# Copyright 2024 NVIDIA CORPORATION
#
# 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.
**/

package runtime

import (
	"fmt"

	"github.com/urfave/cli/v2"

	"github.com/NVIDIA/nvidia-container-toolkit/tools/container"
	"github.com/NVIDIA/nvidia-container-toolkit/tools/container/runtime/containerd"
	"github.com/NVIDIA/nvidia-container-toolkit/tools/container/runtime/crio"
	"github.com/NVIDIA/nvidia-container-toolkit/tools/container/runtime/docker"
)

const (
	defaultSetAsDefault = true
	// defaultRuntimeName specifies the NVIDIA runtime to be use as the default runtime if setting the default runtime is enabled
	defaultRuntimeName   = "nvidia"
	defaultHostRootMount = "/host"

	runtimeSpecificDefault = "RUNTIME_SPECIFIC_DEFAULT"
)

type Options struct {
	container.Options

	containerdOptions containerd.Options
	crioOptions       crio.Options
}

func Flags(opts *Options) []cli.Flag {
	flags := []cli.Flag{
		&cli.StringFlag{
			Name:        "config",
			Usage:       "Path to the runtime config file",
			Value:       runtimeSpecificDefault,
			Destination: &opts.Config,
			EnvVars:     []string{"RUNTIME_CONFIG", "CONTAINERD_CONFIG", "DOCKER_CONFIG"},
		},
		&cli.StringFlag{
			Name:        "socket",
			Usage:       "Path to the runtime socket file",
			Value:       runtimeSpecificDefault,
			Destination: &opts.Socket,
			EnvVars:     []string{"RUNTIME_SOCKET", "CONTAINERD_SOCKET", "DOCKER_SOCKET"},
		},
		&cli.StringFlag{
			Name:        "restart-mode",
			Usage:       "Specify how the runtime should be restarted; If 'none' is selected it will not be restarted [signal | systemd | none ]",
			Value:       runtimeSpecificDefault,
			Destination: &opts.RestartMode,
			EnvVars:     []string{"RUNTIME_RESTART_MODE"},
		},
		&cli.StringFlag{
			Name:        "host-root",
			Usage:       "Specify the path to the host root to be used when restarting the runtime using systemd",
			Value:       defaultHostRootMount,
			Destination: &opts.HostRootMount,
			EnvVars:     []string{"HOST_ROOT_MOUNT"},
		},
		&cli.StringFlag{
			Name:        "runtime-name",
			Aliases:     []string{"nvidia-runtime-name", "runtime-class"},
			Usage:       "Specify the name of the `nvidia` runtime. If set-as-default is selected, the runtime is used as the default runtime.",
			Value:       defaultRuntimeName,
			Destination: &opts.RuntimeName,
			EnvVars:     []string{"NVIDIA_RUNTIME_NAME", "CONTAINERD_RUNTIME_CLASS", "DOCKER_RUNTIME_NAME"},
		},
		&cli.BoolFlag{
			Name:        "set-as-default",
			Usage:       "Set the `nvidia` runtime as the default runtime.",
			Value:       defaultSetAsDefault,
			Destination: &opts.SetAsDefault,
			EnvVars:     []string{"NVIDIA_RUNTIME_SET_AS_DEFAULT", "CONTAINERD_SET_AS_DEFAULT", "DOCKER_SET_AS_DEFAULT"},
			Hidden:      true,
		},
	}

	flags = append(flags, containerd.Flags(&opts.containerdOptions)...)
	flags = append(flags, crio.Flags(&opts.crioOptions)...)

	return flags
}

// ValidateOptions checks whether the specified options are valid
func ValidateOptions(opts *Options, runtime string, toolkitRoot string) error {
	// We set this option here to ensure that it is available in future calls.
	opts.RuntimeDir = toolkitRoot

	// Apply the runtime-specific config changes.
	switch runtime {
	case containerd.Name:
		if opts.Config == runtimeSpecificDefault {
			opts.Config = containerd.DefaultConfig
		}
		if opts.Socket == runtimeSpecificDefault {
			opts.Socket = containerd.DefaultSocket
		}
		if opts.RestartMode == runtimeSpecificDefault {
			opts.RestartMode = containerd.DefaultRestartMode
		}
	case crio.Name:
		if opts.Config == runtimeSpecificDefault {
			opts.Config = crio.DefaultConfig
		}
		if opts.Socket == runtimeSpecificDefault {
			opts.Socket = crio.DefaultSocket
		}
		if opts.RestartMode == runtimeSpecificDefault {
			opts.RestartMode = crio.DefaultRestartMode
		}
	case docker.Name:
		if opts.Config == runtimeSpecificDefault {
			opts.Config = docker.DefaultConfig
		}
		if opts.Socket == runtimeSpecificDefault {
			opts.Socket = docker.DefaultSocket
		}
		if opts.RestartMode == runtimeSpecificDefault {
			opts.RestartMode = docker.DefaultRestartMode
		}
	default:
		return fmt.Errorf("undefined runtime %v", runtime)
	}

	return nil
}

func Setup(c *cli.Context, opts *Options, runtime string) error {
	switch runtime {
	case containerd.Name:
		return containerd.Setup(c, &opts.Options, &opts.containerdOptions)
	case crio.Name:
		return crio.Setup(c, &opts.Options, &opts.crioOptions)
	case docker.Name:
		return docker.Setup(c, &opts.Options)
	default:
		return fmt.Errorf("undefined runtime %v", runtime)
	}
}

func Cleanup(c *cli.Context, opts *Options, runtime string) error {
	switch runtime {
	case containerd.Name:
		return containerd.Cleanup(c, &opts.Options, &opts.containerdOptions)
	case crio.Name:
		return crio.Cleanup(c, &opts.Options, &opts.crioOptions)
	case docker.Name:
		return docker.Cleanup(c, &opts.Options)
	default:
		return fmt.Errorf("undefined runtime %v", runtime)
	}
}

func GetLowlevelRuntimePaths(opts *Options, runtime string) ([]string, error) {
	switch runtime {
	case containerd.Name:
		return containerd.GetLowlevelRuntimePaths(&opts.Options, &opts.containerdOptions)
	case crio.Name:
		return crio.GetLowlevelRuntimePaths(&opts.Options)
	case docker.Name:
		return docker.GetLowlevelRuntimePaths(&opts.Options)
	default:
		return nil, fmt.Errorf("undefined runtime %v", runtime)
	}
}
