/*
Copyright 2020 The Volcano Authors.

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 k8s

import (
	"context"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/events"
	"k8s.io/klog/v2"
	fwk "k8s.io/kube-scheduler/framework"
	"k8s.io/kubernetes/pkg/scheduler/apis/config"
	"k8s.io/kubernetes/pkg/scheduler/framework/parallelize"
	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumebinding"

	"volcano.sh/volcano/pkg/scheduler/api"
)

// Framework is a K8S framework who mainly provides some methods
// about snapshot and plugins such as predicates
type Framework struct {
	snapshot         *Snapshot
	kubeClient       kubernetes.Interface
	informerFactory  informers.SharedInformerFactory
	sharedDRAManager fwk.SharedDRAManager
	sharedCSIManager fwk.CSIManager
}

var _ fwk.Handle = &Framework{}

type Option func(*Framework)

// WithSharedDRAManager sets the shared DRAManager for the framework
func WithSharedDRAManager(sharedDRAManager fwk.SharedDRAManager) Option {
	return func(f *Framework) {
		f.sharedDRAManager = sharedDRAManager
	}
}

// WithSharedCSIManager sets the shared CSIManager for the framework
func WithSharedCSIManager(sharedCSIManager fwk.CSIManager) Option {
	return func(f *Framework) {
		f.sharedCSIManager = sharedCSIManager
	}
}

// WithSnapshotSharedLister sets the SharedLister of the snapshot.
func WithSnapshotSharedLister(snapshot *Snapshot) Option {
	return func(o *Framework) {
		o.snapshot = snapshot
	}
}

// WithClientSet sets clientSet for the scheduling frameworkImpl.
func WithClientSet(clientSet kubernetes.Interface) Option {
	return func(o *Framework) {
		o.kubeClient = clientSet
	}
}

// WithInformerFactory sets informer factory for the scheduling frameworkImpl.
func WithInformerFactory(informerFactory informers.SharedInformerFactory) Option {
	return func(o *Framework) {
		o.informerFactory = informerFactory
	}
}

// SnapshotSharedLister returns the scheduler's SharedLister of the latest NodeInfo
// snapshot. The snapshot is taken at the beginning of a scheduling cycle and remains
// unchanged until a pod finishes "Reserve". There is no guarantee that the information
// remains unchanged after "Reserve".
func (f *Framework) SnapshotSharedLister() fwk.SharedLister {
	return f.snapshot
}

// IterateOverWaitingPods acquires a read lock and iterates over the WaitingPods map.
func (f *Framework) IterateOverWaitingPods(callback func(fwk.WaitingPod)) {
	panic("not implemented")
}

// GetWaitingPod returns a reference to a WaitingPod given its UID.
func (f *Framework) GetWaitingPod(uid types.UID) fwk.WaitingPod {
	panic("not implemented")
}

// HasFilterPlugins returns true if at least one filter plugin is defined.
func (f *Framework) HasFilterPlugins() bool {
	panic("not implemented")
}

// HasScorePlugins returns true if at least one score plugin is defined.
func (f *Framework) HasScorePlugins() bool {
	panic("not implemented")
}

// ListPlugins returns a map of extension point name to plugin names configured at each extension
// point. Returns nil if no plugins where configured.
func (f *Framework) ListPlugins() map[string][]config.Plugin {
	panic("not implemented")
}

// ClientSet returns a kubernetes clientset.
func (f *Framework) ClientSet() kubernetes.Interface {
	return f.kubeClient
}

// SharedInformerFactory returns a shared informer factory.
func (f *Framework) SharedInformerFactory() informers.SharedInformerFactory {
	return f.informerFactory
}

// VolumeBinder returns the volume binder used by scheduler.
func (f *Framework) VolumeBinder() volumebinding.SchedulerVolumeBinder {
	panic("not implemented")
}

// EventRecorder was introduced in k8s v1.19.6 and to be implemented
func (f *Framework) EventRecorder() events.EventRecorderLogger {
	return nil
}

func (f *Framework) AddNominatedPod(logger klog.Logger, pod fwk.PodInfo, nominatingInfo *fwk.NominatingInfo) {
	panic("implement me")
}

func (f *Framework) DeleteNominatedPodIfExists(pod *v1.Pod) {
	panic("implement me")
}

func (f *Framework) UpdateNominatedPod(logger klog.Logger, oldPod *v1.Pod, newPodInfo fwk.PodInfo) {
	panic("implement me")
}

func (f *Framework) NominatedPodsForNode(nodeName string) []fwk.PodInfo {
	panic("implement me")
}

func (f *Framework) RunPreScorePlugins(ctx context.Context, state fwk.CycleState, pod *v1.Pod, nodeinfos []fwk.NodeInfo) *fwk.Status {
	panic("implement me")
}

func (f *Framework) RunScorePlugins(ctx context.Context, state fwk.CycleState, pod *v1.Pod, nodeinfos []fwk.NodeInfo) ([]fwk.NodePluginScores, *fwk.Status) {
	panic("implement me")
}

func (f *Framework) RunFilterPlugins(ctx context.Context, state fwk.CycleState, pod *v1.Pod, info fwk.NodeInfo) *fwk.Status {
	panic("implement me")
}

func (f *Framework) RunPreFilterExtensionAddPod(ctx context.Context, state fwk.CycleState, podToSchedule *v1.Pod, podInfoToAdd fwk.PodInfo, nodeInfo fwk.NodeInfo) *fwk.Status {
	panic("implement me")
}

func (f *Framework) RunPreFilterExtensionRemovePod(ctx context.Context, state fwk.CycleState, podToSchedule *v1.Pod, podInfoToRemove fwk.PodInfo, nodeInfo fwk.NodeInfo) *fwk.Status {
	panic("implement me")
}

func (f *Framework) RejectWaitingPod(uid types.UID) bool {
	panic("implement me")
}

func (f *Framework) AddPodInPreBind(uid types.UID, cancel context.CancelCauseFunc) {}

func (f *Framework) GetPodInPreBind(uid types.UID) fwk.PodInPreBind {
	return nil
}

func (f *Framework) RemovePodInPreBind(uid types.UID) {}

func (f *Framework) KubeConfig() *rest.Config {
	panic("implement me")
}

func (f *Framework) RunFilterPluginsWithNominatedPods(ctx context.Context, state fwk.CycleState, pod *v1.Pod, info fwk.NodeInfo) *fwk.Status {
	panic("implement me")
}

func (f *Framework) Extenders() []fwk.Extender {
	panic("implement me")
}

func (f *Framework) Parallelizer() fwk.Parallelizer {
	return parallelize.NewParallelizer(16)
}

func (f *Framework) Activate(logger klog.Logger, pods map[string]*v1.Pod) {
	panic("implement me")
}

func (f *Framework) SharedDRAManager() fwk.SharedDRAManager {
	return f.sharedDRAManager
}

func (f *Framework) APICacher() fwk.APICacher {
	return nil
}

func (f *Framework) APIDispatcher() fwk.APIDispatcher {
	return nil
}

func (f *Framework) ProfileName() string {
	return ""
}

func (f *Framework) SharedCSIManager() fwk.CSIManager {
	return f.sharedCSIManager
}

func (f *Framework) PodGroupManager() fwk.PodGroupManager {
	return nil
}

func (f *Framework) SignPod(ctx context.Context, pod *v1.Pod) fwk.PodSignature {
	return nil
}

// VolcanoNodeInfos returns a list of volcano NodeInfo.
func (f *Framework) VolcanoNodeInfos() []*api.NodeInfo {
	return f.snapshot.VolcanoNodeInfos()
}

// GetVolcanoNodeInfo returns the volcano NodeInfo of the given node name.
func (f *Framework) GetVolcanoNodeInfo(nodeName string) (*api.NodeInfo, error) {
	return f.snapshot.GetVolcanoNodeInfo(nodeName)
}

// NewFramework is the constructor of Framework
func NewFramework(nodeMap map[string]fwk.NodeInfo, opts ...Option) *Framework {
	fw := &Framework{}

	for _, opt := range opts {
		opt(fw)
	}

	// If no snapshot is provided, create a new one with the given nodeMap, it's mainly used in volcano batch scheduler(session scheduling).
	if fw.snapshot == nil {
		snapshot := NewSnapshot(nodeMap)
		fw.snapshot = snapshot
	}

	return fw
}
