package com.atlassian.stash.internal.cluster;

import com.google.common.io.Closeables;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.nio.serialization.DefaultSerializationServiceBuilder;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;

import static org.mockito.Mockito.mock;

/**
 * Base class for {@link ClusterJoinCheck} implementation unit tests.
 */
public abstract class AbstractClusterJoinCheckTest {

    protected static PipedClusterJoinRequest acceptRequest(PipedClusterJoinRequest connectRequest) throws IOException {
        return new PipedClusterJoinRequest(mock(HazelcastInstance.class),
                new DefaultSerializationServiceBuilder().build(), connectRequest);
    }

    protected static PipedClusterJoinRequest connectRequest() {
        return new PipedClusterJoinRequest(mock(HazelcastInstance.class), ClusterJoinMode.CONNECT);
    }

    protected CheckResult executeJoinCheck(final ClusterJoinCheck connectingCheck, final ClusterJoinCheck acceptingCheck)
            throws IOException, InterruptedException, ExecutionException {
        final PipedClusterJoinRequest connectRequest = connectRequest();
        final PipedClusterJoinRequest acceptRequest = acceptRequest(connectRequest);

        ExecutorService executorService = Executors.newFixedThreadPool(2);
        try {
            List<Future<ClusterJoinCheckResult>> futures = executorService.invokeAll(Arrays.asList(
                    new Callable<ClusterJoinCheckResult>() {
                        @Override
                        public ClusterJoinCheckResult call() throws Exception {
                            try {
                                return connectingCheck.connect(connectRequest);
                            } finally {
                                Closeables.close(connectRequest, true);
                            }
                        }
                    },
                    new Callable<ClusterJoinCheckResult>() {
                        @Override
                        public ClusterJoinCheckResult call() throws Exception {
                            try {
                                return acceptingCheck.accept(acceptRequest);
                            } finally {
                                Closeables.close(acceptRequest, true);
                            }
                        }
                    }
            ), 10, TimeUnit.SECONDS);

            Closeables.close(connectRequest, true);
            Closeables.close(acceptRequest, true);

            return new CheckResult(futures.get(1).get(), futures.get(0).get());
        } finally {
            executorService.shutdown();
        }
    }

    static class CheckResult {

        final ClusterJoinCheckResult acceptResult;
        final ClusterJoinCheckResult connectResult;

        private CheckResult(ClusterJoinCheckResult acceptResult, ClusterJoinCheckResult connectResult) {
            this.acceptResult = acceptResult;
            this.connectResult = connectResult;
        }
    }
}

