// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package builtinconfig

import (
	"strings"

	"sigs.k8s.io/kustomize/api/types"
	"sigs.k8s.io/kustomize/kyaml/resid"
)

// NameBackReferences is an association between a gvk.GVK (a ReferralTarget)
// and a list of Referrers that could refer to it.
//
// It is used to handle name changes, and can be thought of as a
// a contact list.  If you change your own contact info (name,
// phone number, etc.), you must tell your contacts or they won't
// know about the change.
//
// For example, ConfigMaps can be used by Pods and everything that
// contains a Pod; Deployment, Job, StatefulSet, etc.
// The ConfigMap is the ReferralTarget, the others are Referrers.
//
// If the the name of a ConfigMap instance changed from 'alice' to 'bob',
// one must
//  - visit all objects that could refer to the ConfigMap (the Referrers)
//  - see if they mention 'alice',
//  - if so, change the Referrer's name reference to 'bob'.
//
// The NameBackReferences instance to aid in this could look like
//   {
//     kind: ConfigMap
//     version: v1
//     fieldSpecs:
//     - kind: Pod
//       version: v1
//       path: spec/volumes/configMap/name
//     - kind: Deployment
//       path: spec/template/spec/volumes/configMap/name
//     - kind: Job
//       path: spec/template/spec/volumes/configMap/name
//       (etc.)
//   }
type NameBackReferences struct {
	resid.Gvk `json:",inline,omitempty" yaml:",inline,omitempty"`
	// TODO: rename json 'fieldSpecs' to 'referrers' for clarity.
	// This will, however, break anyone using a custom config.
	Referrers types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
}

func (n NameBackReferences) String() string {
	var r []string
	for _, f := range n.Referrers {
		r = append(r, f.String())
	}
	return n.Gvk.String() + ":  (\n" +
		strings.Join(r, "\n") + "\n)"
}

type nbrSlice []NameBackReferences

func (s nbrSlice) Len() int      { return len(s) }
func (s nbrSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s nbrSlice) Less(i, j int) bool {
	return s[i].Gvk.IsLessThan(s[j].Gvk)
}

func (s nbrSlice) mergeAll(o nbrSlice) (result nbrSlice, err error) {
	result = s
	for _, r := range o {
		result, err = result.mergeOne(r)
		if err != nil {
			return nil, err
		}
	}
	return result, nil
}

func (s nbrSlice) mergeOne(other NameBackReferences) (nbrSlice, error) {
	var result nbrSlice
	var err error
	found := false
	for _, c := range s {
		if c.Gvk.Equals(other.Gvk) {
			c.Referrers, err = c.Referrers.MergeAll(other.Referrers)
			if err != nil {
				return nil, err
			}
			found = true
		}
		result = append(result, c)
	}

	if !found {
		result = append(result, other)
	}
	return result, nil
}
