/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 servicediscovery

import (
	"sync"
)

import (
	gxset "github.com/dubbogo/gost/container/set"
	"github.com/dubbogo/gost/gof/observer"
	"github.com/dubbogo/gost/log/logger"
)

import (
	"dubbo.apache.org/dubbo-go/v3/common"
	"dubbo.apache.org/dubbo-go/v3/common/extension"
	"dubbo.apache.org/dubbo-go/v3/registry"
)

type ServiceMappingChangedListenerImpl struct {
	oldServiceNames *gxset.HashSet
	listener        registry.NotifyListener
	registryUrl     *common.URL
	serviceUrl      *common.URL
	mappingCache    *sync.Map
	stop            int

	mux sync.Mutex
}

const (
	ServiceMappingListenerStart = iota
	ServiceMappingListenerStop
)

func NewMappingListener(registryUrl *common.URL, serviceUrl *common.URL, oldServiceNames *gxset.HashSet, listener registry.NotifyListener) *ServiceMappingChangedListenerImpl {
	return &ServiceMappingChangedListenerImpl{
		listener:        listener,
		oldServiceNames: oldServiceNames,
		registryUrl:     registryUrl,
		serviceUrl:      serviceUrl,
		stop:            ServiceMappingListenerStart,
		mappingCache:    &sync.Map{},
	}
}

// OnEvent on ServiceMappingChangedEvent the service mapping change event
func (lstn *ServiceMappingChangedListenerImpl) OnEvent(e observer.Event) error {
	var (
		err error
		reg registry.Registry
	)

	lstn.mux.Lock()
	defer lstn.mux.Unlock()

	if lstn.stop == ServiceMappingListenerStop {
		return nil
	}
	sm, ok := e.(*registry.ServiceMappingChangeEvent)
	if !ok {
		return nil
	}
	newServiceNames := sm.GetServiceNames()
	oldServiceNames := lstn.oldServiceNames
	// serviceMapping is orderly
	if newServiceNames.Empty() || oldServiceNames.String() == newServiceNames.String() {
		return nil
	}
	if newServiceNames.Size() > 0 && oldServiceNames.Empty() {
		if reg, err = extension.GetRegistry(lstn.registryUrl.Protocol, lstn.registryUrl); err != nil {
			return err
		}
		if sdreg, ok := reg.(*serviceDiscoveryRegistry); ok {
			sdreg.SubscribeURL(lstn.serviceUrl, lstn.listener, newServiceNames)
		}
		lstn.oldServiceNames = newServiceNames
		return nil
	}
	for _, service := range newServiceNames.Values() {
		if !oldServiceNames.Contains(service) {
			logger.Infof("Service-application mapping changed for service: %s, new applications: %q, old applications: %q.", lstn.serviceUrl.ServiceKey(), oldServiceNames, newServiceNames)
			lstn.mappingCache.Delete(oldServiceNames.String())
			lstn.mappingCache.Store(newServiceNames.String(), newServiceNames)
			if reg, err = extension.GetRegistry(lstn.registryUrl.Protocol, lstn.registryUrl); err != nil {
				return err
			}
			if sdreg, ok := reg.(*serviceDiscoveryRegistry); ok {
				sdreg.SubscribeURL(lstn.serviceUrl, lstn.listener, newServiceNames)
			}
			lstn.oldServiceNames = newServiceNames
			break
		}
	}

	return err
}

// Stop on ServiceMappingChangedEvent the service mapping change event
func (lstn *ServiceMappingChangedListenerImpl) Stop() {
	lstn.stop = ServiceMappingListenerStop
}
