/**
 * Copyright 2015-2016 Red Hat, Inc, and individual contributors.
 *
 * 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 org.wildfly.swarm.topology.jgroups.runtime;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.jboss.as.network.SocketBinding;
import org.jboss.msc.inject.Injector;
import org.jboss.msc.service.Service;
import org.jboss.msc.service.StartContext;
import org.jboss.msc.service.StartException;
import org.jboss.msc.service.StopContext;
import org.jboss.msc.value.InjectedValue;
import org.wildfly.clustering.dispatcher.CommandDispatcher;
import org.wildfly.clustering.dispatcher.CommandDispatcherFactory;
import org.wildfly.clustering.group.Group;
import org.wildfly.clustering.group.Node;
import org.wildfly.swarm.topology.TopologyConnector;
import org.wildfly.swarm.topology.TopologyMessages;
import org.wildfly.swarm.topology.runtime.Registration;
import org.wildfly.swarm.topology.runtime.TopologyManager;

/**
 * @author Bob McWhirter
 */
public class JGroupsTopologyConnector implements Service<JGroupsTopologyConnector>, Group.Listener, TopologyConnector {

    public JGroupsTopologyConnector() {
    }

    public Injector<CommandDispatcherFactory> getCommandDispatcherFactoryInjector() {
        return this.commandDispatcherFactoryInjector;
    }

    public Injector<TopologyManager> getTopologyManagerInjector() {
        return this.topologyManagerInjector;
    }

    @Override
    public void start(StartContext startContext) throws StartException {
        this.commandDispatcherFactoryInjector.getValue().getGroup().addListener(this);
        this.dispatcher = this.commandDispatcherFactoryInjector.getValue().createCommandDispatcher("netflix.runtime.manager", this);
        this.node = this.commandDispatcherFactoryInjector.getValue().getGroup().getLocalNode();
        try {
            requestAdvertisements();
        } catch (Exception e) {
            throw new StartException(e);
        }
    }

    @Override
    public void stop(StopContext stopContext) {
        this.dispatcher.close();
    }

    @Override
    public JGroupsTopologyConnector getValue() throws IllegalStateException, IllegalArgumentException {
        return this;
    }

    @Override
    public void membershipChanged(List<Node> previousMembers, List<Node> members, boolean merged) {
        try {
            advertiseAll();
        } catch (Exception e) {
            TopologyMessages.MESSAGES.errorStartingAdvertisement(e);
        }
        List<Node> removed = new ArrayList<>();
        removed.addAll(previousMembers);
        removed.removeAll(members);
        removed.forEach((e) -> {
            this.topologyManagerInjector.getValue().unregisterAll(sourceKey(e));
        });
    }

    public synchronized void advertise(String name, SocketBinding binding, String... tags) throws Exception {
        Registration registration = new Registration(sourceKey(this.node), name, binding.getAddress().getHostAddress(), binding.getAbsolutePort(), tags);

        this.registrations.put(name + ":" + binding.getName(), registration);
        advertise(registration);
    }

    public synchronized void advertise(Registration registration) throws Exception {
        this.topologyManagerInjector.getValue().register(registration);
        doAdvertise(registration);
    }

    public synchronized void unadvertise(String appName, SocketBinding binding) throws Exception {
        Registration registration = this.registrations.remove(appName + ":" + binding.getName());
        if (registration != null) {
            this.dispatcher.submitOnCluster(new UnadvertiseCommand(registration));
        }
    }

    protected void requestAdvertisements() throws Exception {
        this.dispatcher.submitOnCluster(new RequestAdvertisementsCommand(), this.node);
    }

    protected synchronized void advertiseAll() throws Exception {
        for (Registration each : this.topologyManagerInjector.getValue().registrationsForSourceKey(sourceKey(this.node))) {
            doAdvertise(each);
        }
    }

    protected void doAdvertise(Registration registration) throws Exception {
        this.dispatcher.submitOnCluster(new AdvertiseCommand(registration));
    }

    void register(Registration registration) {
        this.topologyManagerInjector.getValue().register(registration);
    }

    void unregister(Registration registration) {
        this.topologyManagerInjector.getValue().unregister(registration);
    }

    String sourceKey(Node node) {
        return node.getName() + ":" + node.getSocketAddress().toString();

    }

    private InjectedValue<CommandDispatcherFactory> commandDispatcherFactoryInjector = new InjectedValue<>();

    private InjectedValue<TopologyManager> topologyManagerInjector = new InjectedValue<>();

    private CommandDispatcher<JGroupsTopologyConnector> dispatcher;

    private Node node;

    private Map<String, Registration> registrations = new ConcurrentHashMap<>();

}
