package com.atlassian.bitbucket.mesh.cluster;

import com.atlassian.bitbucket.mesh.config.ApplicationConfig;
import com.atlassian.bitbucket.mesh.config.NodeConfig;
import com.atlassian.bitbucket.mesh.test.cluster.MeshClusterNode;
import com.atlassian.bitbucket.mesh.test.cluster.MeshControlPlane;
import com.google.common.collect.ImmutableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;

import javax.annotation.Nonnull;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.Callable;

import static java.util.Objects.requireNonNull;

/**
 * {@link MeshClusterNode} instance that represents the wired node and registers itself with the control plane
 * on startup.
 */
public class WiredClusterNode implements ApplicationListener<ContextRefreshedEvent>, MeshClusterNode {

    private static final Logger log = LoggerFactory.getLogger(WiredClusterNode.class);

    private final ApplicationConfig config;
    private final MeshControlPlane controlPlane;
    private final String rpcUrl;

    public WiredClusterNode(ApplicationConfig config, MeshControlPlane controlPlane, String rpcUrl) {
        this.config = config;
        this.controlPlane = controlPlane;
        this.rpcUrl = rpcUrl;
    }

    @Nonnull
    @Override
    public String getId() {
        String configuredId = config.getNode().getId();
        return NodeConfig.UNCONFIGURED_NODE_ID.equals(configuredId) ? "local" : configuredId;
    }

    @Nonnull
    @Override
    public String getName() {
        return config.getNode().getName();
    }

    @Nonnull
    @Override
    public List<Path> getShards() {
        //Don't let the cluster manager wipe our data
        return ImmutableList.of();
    }

    @Nonnull
    @Override
    public String getUrl() {
        return rpcUrl;
    }

    @Override
    public void onApplicationEvent(@Nonnull ContextRefreshedEvent ignored) {
        register();
    }

    public void register() {
        if (controlPlane.registerNode(this) == null) {
            log.warn("Failed to register wired node {} ({})", getId(), rpcUrl);
        }
    }

    public void unregister() {
        if (!controlPlane.unregisterNode(this)) {
            log.warn("Failed to unregister wired node {} ({})", getId(), rpcUrl);
        }
    }

    @Override
    public <T> T whileOffline(@Nonnull Callable<T> action) throws Exception {
        requireNonNull(action, "action");

        //Rather than actually shutting down the node and restarting it, which would be slow, we simply
        //remove it from the cluster (and the topology), and then put it back
        unregister();
        try {
            return action.call();
        } finally {
            register();
        }
    }
}
