func ExtractComponentName(revisionName string) string {
	splits := strings.Split(revisionName, "-")
	return strings.Join(splits[0:len(splits)-1], "-")
}
ExtractComponentName will extract componentName from revisionName
package applicationconfiguration
import (
	"context"
	"fmt"
	"reflect"
	"sort"
	"strings"
	appsv1 "k8s.io/api/apps/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/util/workqueue"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
	"github.com/crossplane/crossplane-runtime/pkg/logging"
	"github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
	util "github.com/crossplane/oam-kubernetes-runtime/pkg/oam/util"
)
const ControllerRevisionComponentLabel = "controller.oam.dev/component"
type ComponentHandler struct {
	Client        client.Client
	Logger        logging.Logger
	RevisionLimit int
}
func (c *ComponentHandler) Create(evt event.CreateEvent, q workqueue.RateLimitingInterface) {
	if !c.createControllerRevision(evt.Meta, evt.Object) {
		return
	}
	for _, req := range c.getRelatedAppConfig(evt.Meta) {
		q.Add(req)
	}
}
func (c *ComponentHandler) Update(evt event.UpdateEvent, q workqueue.RateLimitingInterface) {
	if !c.createControllerRevision(evt.MetaNew, evt.ObjectNew) {
		return
	}
	for _, req := range c.getRelatedAppConfig(evt.MetaNew) {
		q.Add(req)
	}
}
func (c *ComponentHandler) Delete(evt event.DeleteEvent, q workqueue.RateLimitingInterface) {
	for _, req := range c.getRelatedAppConfig(evt.Meta) {
		q.Add(req)
	}
}
func (c *ComponentHandler) Generic(_ event.GenericEvent, _ workqueue.RateLimitingInterface) {
}
func isMatch(appConfigs *v1alpha2.ApplicationConfigurationList, compName string) (bool, types.NamespacedName) {
	for _, app := range appConfigs.Items {
		for _, comp := range app.Spec.Components {
			if comp.ComponentName == compName {
				return true, types.NamespacedName{Namespace: app.Namespace, Name: app.Name}
			}
		}
	}
	return false, types.NamespacedName{}
}
func (c *ComponentHandler) getRelatedAppConfig(object metav1.Object) []reconcile.Request {
	var appConfigs v1alpha2.ApplicationConfigurationList
	err := c.Client.List(context.Background(), &appConfigs)
	if err != nil {
		c.Logger.Info(fmt.Sprintf("error list all applicationConfigurations %v", err))
		return nil
	}
	var reqs []reconcile.Request
	if match, namespaceName := isMatch(&appConfigs, object.GetName()); match {
		reqs = append(reqs, reconcile.Request{NamespacedName: namespaceName})
	}
	return reqs
}
func (c *ComponentHandler) IsRevisionDiff(mt metav1.Object, curComp *v1alpha2.Component) (bool, int64) {
	if curComp.Status.LatestRevision == nil {
		return true, 0
	}
	oldRev := &appsv1.ControllerRevision{}
	if err := c.Client.Get(context.TODO(), client.ObjectKey{Namespace: mt.GetNamespace(), Name: curComp.Status.LatestRevision.Name}, oldRev); err != nil {
		c.Logger.Info(fmt.Sprintf("get old controllerRevision %s error %v, will create new revision", curComp.Status.LatestRevision.Name, err), "componentName", mt.GetName())
		return true, curComp.Status.LatestRevision.Revision
	}
	if oldRev.Name == "" {
		c.Logger.Info(fmt.Sprintf("Not found controllerRevision %s", curComp.Status.LatestRevision.Name), "componentName", mt.GetName())
		return true, curComp.Status.LatestRevision.Revision
	}
	oldComp, err := util.UnpackRevisionData(oldRev)
	if err != nil {
		c.Logger.Info(fmt.Sprintf("Unmarshal old controllerRevision %s error %v, will create new revision", curComp.Status.LatestRevision.Name, err), "componentName", mt.GetName())
		return true, oldRev.Revision
	}
	if reflect.DeepEqual(curComp.Spec, oldComp.Spec) {
		return false, oldRev.Revision
	}
	return true, oldRev.Revision
}
func newTrue() *bool {
	b := true
	return &b
}
func (c *ComponentHandler) createControllerRevision(mt metav1.Object, obj runtime.Object) bool {
	curComp := obj.(*v1alpha2.Component)
	comp := curComp.DeepCopy()
	diff, curRevision := c.IsRevisionDiff(mt, comp)
	if !diff {
		return false
	}
	nextRevision := curRevision + 1
	revisionName := ConstructRevisionName(mt.GetName(), nextRevision)
	if comp.Status.ObservedGeneration != comp.Generation {
		comp.Status.ObservedGeneration = comp.Generation
	}
	comp.Status.LatestRevision = &v1alpha2.Revision{
		Name:     revisionName,
		Revision: nextRevision,
	}
	revision := appsv1.ControllerRevision{
		ObjectMeta: metav1.ObjectMeta{
			Name:      revisionName,
			Namespace: comp.Namespace,
			OwnerReferences: []metav1.OwnerReference{
				{
					APIVersion: v1alpha2.SchemeGroupVersion.String(),
					Kind:       v1alpha2.ComponentKind,
					Name:       comp.Name,
					UID:        comp.UID,
					Controller: newTrue(),
				},
			},
			Labels: map[string]string{
				ControllerRevisionComponentLabel: comp.Name,
			},
		},
		Revision: nextRevision,
		Data:     runtime.RawExtension{Object: comp},
	}
	err := c.Client.Create(context.TODO(), &revision)
	if err != nil {
		c.Logger.Info(fmt.Sprintf("error create controllerRevision %v", err), "componentName", mt.GetName())
		return false
	}
	err = c.Client.Status().Update(context.Background(), comp)
	if err != nil {
		c.Logger.Info(fmt.Sprintf("update component status latestRevision %s err %v", revisionName, err), "componentName", mt.GetName())
		return false
	}
	c.Logger.Info(fmt.Sprintf("ControllerRevision %s created", revisionName))
	if int64(c.RevisionLimit) < nextRevision {
		if err := c.cleanupControllerRevision(comp); err != nil {
			c.Logger.Info(fmt.Sprintf("failed to clean up revisions of Component %v.", err))
		}
	}
	return true
}
func sortedControllerRevision(appConfigs []v1alpha2.ApplicationConfiguration, revisions []appsv1.ControllerRevision,
	revisionLimit int) (sortedRevisions []appsv1.ControllerRevision, toKill int, liveHashes map[string]bool) {
	liveHashes = make(map[string]bool)
	sortedRevisions = revisions
	for _, appConfig := range appConfigs {
		for _, component := range appConfig.Spec.Components {
			if component.RevisionName != "" {
				liveHashes[component.RevisionName] = true
			}
		}
	}
	toKeep := revisionLimit + len(liveHashes)
	toKill = len(sortedRevisions) - toKeep
	if toKill <= 0 {
		toKill = 0
		return
	}
	sort.Sort(historiesByRevision(sortedRevisions))
	return
}
func (c *ComponentHandler) cleanupControllerRevision(curComp *v1alpha2.Component) error {
	labels := &metav1.LabelSelector{
		MatchLabels: map[string]string{
			ControllerRevisionComponentLabel: curComp.Name,
		},
	}
	selector, err := metav1.LabelSelectorAsSelector(labels)
	if err != nil {
		return err
	}
	revisions := &appsv1.ControllerRevisionList{}
	if err := c.Client.List(context.TODO(), revisions, &client.ListOptions{LabelSelector: selector}); err != nil {
		return err
	}
	appConfigs := &v1alpha2.ApplicationConfigurationList{}
	if err := c.Client.List(context.Background(), appConfigs); err != nil {
		return err
	}
	controllerRevisions, toKill, liveHashes := sortedControllerRevision(appConfigs.Items, revisions.Items, c.RevisionLimit)
	for _, revision := range controllerRevisions {
		if toKill <= 0 {
			break
		}
		if hash := revision.GetName(); liveHashes[hash] {
			continue
		}
		revisionToClean := revision
		if err := c.Client.Delete(context.TODO(), &revisionToClean); err != nil {
			return err
		}
		c.Logger.Info(fmt.Sprintf("ControllerRevision %s deleted", revision.Name))
		toKill--
	}
	return nil
}
func ConstructRevisionName(componentName string, revision int64) string {
	return strings.Join([]string{componentName, fmt.Sprintf("v%d", revision)}, "-")
}