package com.hazelcast.client.cluster;

import com.hazelcast.client.config.ClientConfig;
import com.hazelcast.client.spi.properties.ClientProperty;
import com.hazelcast.client.test.TestHazelcastFactory;
import com.hazelcast.cluster.ClusterState;
import com.hazelcast.config.Config;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.logging.ILogger;
import com.hazelcast.logging.Logger;
import com.hazelcast.test.HazelcastParallelClassRunner;
import com.hazelcast.test.annotation.ParallelTest;
import com.hazelcast.test.annotation.QuickTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;

import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import static com.hazelcast.internal.cluster.impl.AdvancedClusterStateTest.changeClusterStateEventually;
import static com.hazelcast.test.HazelcastTestSupport.assertClusterSizeEventually;
import static com.hazelcast.test.HazelcastTestSupport.randomMapName;
import static com.hazelcast.test.HazelcastTestSupport.waitAllForSafeState;
import static com.hazelcast.test.HazelcastTestSupport.warmUpPartitions;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

@RunWith(HazelcastParallelClassRunner.class)
@Category({QuickTest.class, ParallelTest.class})
public class ClientClusterStateTest {

    private TestHazelcastFactory factory;

    private HazelcastInstance[] instances;

    private HazelcastInstance instance;

    @Before
    public void before() {
        factory = new TestHazelcastFactory();
        instances = factory.newInstances(new Config(), 4);
        for (HazelcastInstance instance : instances) {
            assertClusterSizeEventually(4, instance);
        }
        instance = instances[instances.length - 1];
    }

    @After
    public void after() {
        factory.terminateAll();
    }

    @Test
    public void testClient_canConnect_whenClusterState_frozen() {
        instance.getCluster().changeClusterState(ClusterState.FROZEN);
        factory.newHazelcastClient();
    }

    @Test
    public void testClient_canExecuteWriteOperations_whenClusterState_frozen() {
        warmUpPartitions(instances);

        changeClusterStateEventually(instance, ClusterState.FROZEN);
        HazelcastInstance client = factory.newHazelcastClient();
        IMap<Object, Object> map = client.getMap(randomMapName());
        map.put(1, 1);
    }

    @Test
    public void testClient_canExecuteReadOperations_whenClusterState_frozen() {
        warmUpPartitions(instances);

        changeClusterStateEventually(instance, ClusterState.FROZEN);
        HazelcastInstance client = factory.newHazelcastClient();
        IMap<Object, Object> map = client.getMap(randomMapName());
        map.get(1);
    }

    @Test
    public void testClient_canConnect_whenClusterState_passive() {
        instance.getCluster().changeClusterState(ClusterState.PASSIVE);
        factory.newHazelcastClient();
    }

    @Test(expected = IllegalStateException.class)
    public void testClient_canNotExecuteWriteOperations_whenClusterState_passive() {
        warmUpPartitions(instances);

        ClientConfig clientConfig = new ClientConfig().setProperty(ClientProperty.INVOCATION_TIMEOUT_SECONDS.getName(), "3");
        HazelcastInstance client = factory.newHazelcastClient(clientConfig);
        IMap<Object, Object> map = client.getMap(randomMapName());
        changeClusterStateEventually(instance, ClusterState.PASSIVE);
        map.put(1, 1);
    }

    @Test
    public void testClient_canExecuteReadOperations_whenClusterState_passive() {
        warmUpPartitions(instances);

        HazelcastInstance client = factory.newHazelcastClient();
        IMap<Object, Object> map = client.getMap(randomMapName());
        changeClusterStateEventually(instance, ClusterState.PASSIVE);
        map.get(1);
    }

    @Test
    public void testClient_canConnect_whenClusterState_goesBackToActive_fromPassive() {
        instance.getCluster().changeClusterState(ClusterState.PASSIVE);
        instance.getCluster().changeClusterState(ClusterState.ACTIVE);
        factory.newHazelcastClient();
    }

    @Test
    public void testClient_canExecuteOperations_whenClusterState_goesBackToActive_fromPassive() {
        warmUpPartitions(instances);

        changeClusterStateEventually(instance, ClusterState.PASSIVE);
        instance.getCluster().changeClusterState(ClusterState.ACTIVE);
        HazelcastInstance client = factory.newHazelcastClient();
        IMap<Object, Object> map = client.getMap(randomMapName());
        map.put(1, 1);
    }

    @Test
    public void testClusterShutdownDuringMapPutAll() {
        warmUpPartitions(instances);
        waitAllForSafeState(instances);

        HazelcastInstance client = factory.newHazelcastClient();
        final IMap<Object, Object> map = client.getMap(randomMapName());

        final HashMap values = new HashMap<Double, Double>();
        for (int i = 0; i < 1000; i++) {
            double value = Math.random();
            values.put(value, value);
        }

        final int numThreads = 10;
        final CountDownLatch threadsFinished = new CountDownLatch(numThreads);
        final CountDownLatch threadsStarted = new CountDownLatch(numThreads);

        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < numThreads; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    ILogger logger = Logger.getLogger(getClass());
                    boolean finished = false;
                    threadsStarted.countDown();
                    logger.info("putAll thread started");
                    while (!finished) {
                        try {
                            map.putAll(values);
                            Thread.sleep(100);
                        } catch (IllegalStateException e) {
                           logger.warning("Expected exception for Map putAll during cluster shutdown:", e);
                            finished = true;
                        } catch (InterruptedException e) {
                            // do nothing
                        }
                    }
                    threadsFinished.countDown();
                    logger.info("putAll thread finishing. Current finished thread count is:" + (numThreads - threadsFinished
                            .getCount()));
                }
            });
        }

        try {
            assertTrue("All threads could not be started", threadsStarted.await(1, TimeUnit.MINUTES));
        } catch (InterruptedException e) {
            fail("All threads could not be started due to InterruptedException. Could not start " + threadsStarted.getCount()
                    + " threads out of " + numThreads);
        }

        instance.getCluster().shutdown();

        executor.shutdown();

        try {
            assertTrue("All threads could not be finished", threadsFinished.await(2, TimeUnit.MINUTES));
        } catch (InterruptedException e) {
            fail("All threads could not be finished due to InterruptedException. Could not finish " + threadsFinished.getCount()
                    + " threads out of " + numThreads);
        } finally {
            executor.shutdownNow();
        }
    }
}

@@ -418,32 +418,96 @@
 logging.Logger;%0A
+import com.hazelcast.spi.exception.TargetDisconnectedException;%0A
 import com.hazel
@@ -5840,54 +5840,8 @@
 ));%0A
-                    boolean finished = false;%0A
     
@@ -5876,32 +5876,32 @@
 ed.countDown();%0A
+
                 
@@ -5973,17 +5973,12 @@
 le (
-!finished
+true
 ) %7B%0A
@@ -6301,24 +6301,223 @@
     
- finished = true
+break;%0A                        %7D catch (TargetDisconnectedException e) %7B%0A                            logger.warning(%22Expected exception for Map putAll during cluster shutdown:%22, e);%0A                            break
 ;%0A  
@@ -6615,16 +6615,16 @@
 nothing%0A
-
         
@@ -6633,32 +6633,33 @@
               %7D%0A
+%0A
                 

Fix ClientClusterStateTest