/*
Copyright 2023 The Kubernetes 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 listers

import (
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/tools/cache"
)

// ResourceIndexer wraps an indexer, resource, and optional namespace for a given type.
// This is intended for use by listers (generated by lister-gen) only.
type ResourceIndexer[T runtime.Object] struct {
	indexer   cache.Indexer
	resource  schema.GroupResource
	namespace string // empty for non-namespaced types
}

// New returns a new instance of a lister (resource indexer) wrapping the given indexer and resource for the specified type.
// This is intended for use by listers (generated by lister-gen) only.
func New[T runtime.Object](indexer cache.Indexer, resource schema.GroupResource) ResourceIndexer[T] {
	return ResourceIndexer[T]{indexer: indexer, resource: resource}
}

// NewNamespaced returns a new instance of a namespaced lister (resource indexer) wrapping the given parent and namespace for the specified type.
// This is intended for use by listers (generated by lister-gen) only.
func NewNamespaced[T runtime.Object](parent ResourceIndexer[T], namespace string) ResourceIndexer[T] {
	return ResourceIndexer[T]{indexer: parent.indexer, resource: parent.resource, namespace: namespace}
}

// List lists all resources in the indexer matching the given selector.
func (l ResourceIndexer[T]) List(selector labels.Selector) (ret []T, err error) {
	// ListAllByNamespace reverts to ListAll on empty namespaces
	err = cache.ListAllByNamespace(l.indexer, l.namespace, selector, func(m interface{}) {
		ret = append(ret, m.(T))
	})
	return ret, err
}

// Get retrieves the resource from the index for a given name.
func (l ResourceIndexer[T]) Get(name string) (T, error) {
	var key string
	if l.namespace == "" {
		key = name
	} else {
		key = l.namespace + "/" + name
	}
	obj, exists, err := l.indexer.GetByKey(key)
	if err != nil {
		return *new(T), err
	}
	if !exists {
		return *new(T), errors.NewNotFound(l.resource, name)
	}
	return obj.(T), nil
}
