package k8s

import (
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// ResourceEventHandler handles Add, Update or Delete Events for resources.
type ResourceEventHandler struct {
	Ev chan<- interface{}
}

// OnAdd is called on Add Events.
func (reh *ResourceEventHandler) OnAdd(obj interface{}) {
	eventHandlerFunc(reh.Ev, obj)
}

// OnUpdate is called on Update Events.
// Ignores useless changes.
func (reh *ResourceEventHandler) OnUpdate(oldObj, newObj interface{}) {
	if objChanged(oldObj, newObj) {
		eventHandlerFunc(reh.Ev, newObj)
	}
}

// OnDelete is called on Delete Events.
func (reh *ResourceEventHandler) OnDelete(obj interface{}) {
	eventHandlerFunc(reh.Ev, obj)
}

// eventHandlerFunc will pass the obj on to the events channel or drop it.
// This is so passing the events along won't block in the case of high volume.
// The events are only used for signaling anyway so dropping a few is ok.
func eventHandlerFunc(events chan<- interface{}, obj interface{}) {
	select {
	case events <- obj:
	default:
	}
}

func objChanged(oldObj, newObj interface{}) bool {
	if oldObj == nil || newObj == nil {
		return true
	}

	if oldObj.(metav1.Object).GetResourceVersion() == newObj.(metav1.Object).GetResourceVersion() {
		return false
	}

	if _, ok := oldObj.(*corev1.Endpoints); ok {
		return endpointsChanged(oldObj.(*corev1.Endpoints), newObj.(*corev1.Endpoints))
	}

	return true
}

func endpointsChanged(a, b *corev1.Endpoints) bool {
	if len(a.Subsets) != len(b.Subsets) {
		return true
	}

	for i, sa := range a.Subsets {
		sb := b.Subsets[i]
		if subsetsChanged(sa, sb) {
			return true
		}
	}

	return false
}

func subsetsChanged(sa, sb corev1.EndpointSubset) bool {
	if len(sa.Addresses) != len(sb.Addresses) {
		return true
	}

	if len(sa.Ports) != len(sb.Ports) {
		return true
	}

	// in Addresses and Ports, we should be able to rely on
	// these being sorted and able to be compared
	// they are supposed to be in a canonical format
	for addr, aaddr := range sa.Addresses {
		baddr := sb.Addresses[addr]
		if aaddr.IP != baddr.IP {
			return true
		}

		if aaddr.Hostname != baddr.Hostname {
			return true
		}
	}

	for port, aport := range sa.Ports {
		bport := sb.Ports[port]
		if aport.Name != bport.Name {
			return true
		}
		if aport.Port != bport.Port {
			return true
		}

		if aport.Protocol != bport.Protocol {
			return true
		}
	}

	return false
}
