// Copyright 2019 Yunion
//
// 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 isolated_device

import (
	"fmt"

	runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"

	"yunion.io/x/jsonutils"
	"yunion.io/x/pkg/errors"

	"yunion.io/x/onecloud/pkg/apis"
	api "yunion.io/x/onecloud/pkg/apis/compute"
	hostapi "yunion.io/x/onecloud/pkg/apis/host"
)

type containerCDIFactory func() (IContainerCDIManager, error)

var (
	containerDeviceManagers = make(map[ContainerDeviceType]IContainerDeviceManager)
	containerCDIManagers    = make(map[apis.ContainerCDIKind]containerCDIFactory)
)

type ContainerDeviceType string

const (
	ContainerDeviceTypeCphAMDGPU      ContainerDeviceType = api.CONTAINER_DEV_CPH_AMD_GPU
	ContainerDeviceTypeCphASOPBinder  ContainerDeviceType = api.CONTAINER_DEV_CPH_AOSP_BINDER
	ContainerNetintCAASIC             ContainerDeviceType = api.CONTAINER_DEV_NETINT_CA_ASIC
	ContainerNetintCAQuadra           ContainerDeviceType = api.CONTAINER_DEV_NETINT_CA_QUADRA
	ContainerDeviceTypeNvidiaGpu      ContainerDeviceType = api.CONTAINER_DEV_NVIDIA_GPU
	ContainerDeviceTypeNvidiaMps      ContainerDeviceType = api.CONTAINER_DEV_NVIDIA_MPS
	ContainerDeviceTypeNvidiaGpuShare ContainerDeviceType = api.CONTAINER_DEV_NVIDIA_GPU_SHARE
	ContainerDeviceTypeAscendNpu      ContainerDeviceType = api.CONTAINER_DEV_ASCEND_NPU
	ContainerDeviceTypeVastaitechGpu  ContainerDeviceType = api.CONTAINER_DEV_VASTAITECH_GPU
)

func GetContainerDeviceManager(t ContainerDeviceType) (IContainerDeviceManager, error) {
	man, ok := containerDeviceManagers[t]
	if !ok {
		return nil, errors.Wrapf(errors.ErrNotFound, "not found container device manager by %q", t)
	}
	return man, nil
}

func RegisterContainerDeviceManager(man IContainerDeviceManager) {
	if _, ok := containerDeviceManagers[man.GetType()]; ok {
		panic(fmt.Sprintf("container device manager %s is already registered", man.GetType()))
	}
	containerDeviceManagers[man.GetType()] = man
}

func GetContainerCDIManager(t apis.ContainerCDIKind) (IContainerCDIManager, error) {
	manF, ok := containerCDIManagers[t]
	if !ok {
		return nil, errors.Wrapf(errors.ErrNotFound, "not found container cdi manager by %q", t)
	}
	return manF()
}

func RegisterContainerCDIManaer(t apis.ContainerCDIKind, factoryFunc func() (IContainerCDIManager, error)) {
	if _, ok := containerCDIManagers[t]; ok {
		panic(fmt.Sprintf("CDI manager %s is already registered", t))
	}
	containerCDIManagers[t] = factoryFunc
}

type ContainerDevice struct {
	Path          string              `json:"path"`
	Type          ContainerDeviceType `json:"type"`
	VirtualNumber int                 `json:"virtual_number"`
}

type ContainerDeviceConfiguration struct {
	Devices []*ContainerDevice `json:"devices"`
}

type IContainerDeviceManager interface {
	GetType() ContainerDeviceType
	NewDevices(dev *ContainerDevice) ([]IDevice, error)
	NewContainerDevices(input *hostapi.ContainerCreateInput, dev *hostapi.ContainerDevice) ([]*runtimeapi.Device, []*runtimeapi.Device, error)
	ProbeDevices() ([]IDevice, error)
	GetContainerExtraConfigures(devs []*hostapi.ContainerDevice) ([]*runtimeapi.KeyValue, []*runtimeapi.Mount)
}

type IContainerCDIManager interface {
	GetKind() apis.ContainerCDIKind
	GetSpecFilePath() string
	GetDeviceName(dev *hostapi.ContainerDevice) (string, error)
}

func GetContainerCDIDevices(devs []*hostapi.ContainerDevice) ([]*runtimeapi.CDIDevice, error) {
	errs := []error{}
	retDevs := make([]*runtimeapi.CDIDevice, 0)
	for i := range devs {
		dev := devs[i]
		cdiDev, err := getContainerCDIDevice(dev)
		if err != nil {
			errs = append(errs, errors.Wrapf(err, "get CDI by %s", jsonutils.Marshal(dev)))
			continue
		}
		retDevs = append(retDevs, cdiDev)
	}
	return retDevs, nil
}

func getContainerCDIDevice(dev *hostapi.ContainerDevice) (*runtimeapi.CDIDevice, error) {
	isoDev := dev.IsolatedDevice
	if isoDev == nil {
		return nil, errors.Wrap(errors.ErrNotEmpty, "isolated_device is nil")
	}
	cdi := isoDev.CDI
	if cdi == nil {
		return nil, errors.Wrap(errors.ErrNotEmpty, "CDI is nil")
	}
	man, err := GetContainerCDIManager(isoDev.CDI.Kind)
	if err != nil {
		return nil, errors.Wrapf(err, "GetContainerCDIManager by %s", cdi.Kind)
	}
	name, err := man.GetDeviceName(dev)
	if err != nil {
		return nil, errors.Wrapf(err, "get CDI device name")
	}
	return &runtimeapi.CDIDevice{
		Name: fmt.Sprintf("%s=%s", man.GetKind(), name),
	}, nil
}
