/*
 * 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 org.apache.ignite.internal.processors.cache;

import java.util.Collection;
import java.util.Collections;
import java.util.Random;
import javax.cache.CacheException;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.IgniteDataStreamer;
import org.apache.ignite.cache.CacheAtomicityMode;
import org.apache.ignite.cache.CacheMode;
import org.apache.ignite.cache.QueryEntity;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.internal.processors.cache.distributed.dht.IgniteClusterReadOnlyException;
import org.apache.ignite.internal.processors.datastreamer.DataStreamerImpl;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.G;
import org.apache.ignite.internal.util.typedef.X;
import org.jetbrains.annotations.Nullable;

import static org.apache.ignite.cache.CacheAtomicityMode.ATOMIC;
import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL;
import static org.apache.ignite.cache.CacheMode.PARTITIONED;
import static org.apache.ignite.cache.CacheMode.REPLICATED;
import static org.junit.Assert.fail;

/**
 * Utility class for testing grid read-only mode.
 */
public class ClusterReadOnlyModeTestUtils {
    /** Replicated atomic cache. */
    private static final String REPL_ATOMIC_CACHE = "repl_atomic_cache";

    /** Replicated transactional cache. */
    private static final String REPL_TX_CACHE = "repl_tx_cache";

    /** Partitioned atomic cache. */
    public static final String PART_ATOMIC_CACHE = "part_atomic_cache";

    /** Partitioned transactional cache. */
    private static final String PART_TX_CACHE = "part_tx_cache";

    /**
     * @return Configured cache names.
     */
    public static Collection<String> cacheNames() {
        return F.asList(REPL_ATOMIC_CACHE, REPL_TX_CACHE, PART_ATOMIC_CACHE, PART_TX_CACHE);
    }

    /**
     * @return Various types of cache configuration.
     */
    public static CacheConfiguration[] cacheConfigurations() {
        return F.asArray(
            cacheConfiguration(REPL_ATOMIC_CACHE, REPLICATED, ATOMIC, null),
            cacheConfiguration(REPL_TX_CACHE, REPLICATED, TRANSACTIONAL, null),
            cacheConfiguration(PART_ATOMIC_CACHE, PARTITIONED, ATOMIC, "part_grp_atomic"),
            cacheConfiguration(PART_TX_CACHE, PARTITIONED, TRANSACTIONAL, "part_grp_tx")
        );
    }

    /**
     * Asserts that all caches in read-only or in read/write mode on all nodes.
     *
     * @param readOnly If {@code true} then cache must be in read only mode, else in read/write mode.
     * @param cacheNames Checked cache names.
     */
    public static void assertCachesReadOnlyMode(boolean readOnly, Collection<String> cacheNames) {
        for (Ignite node : G.allGrids())
            assertCachesReadOnlyMode(node, readOnly, cacheNames);
    }

    /**
     * Asserts that all caches in read-only or in read/write mode. All cache operations will be performed from the
     * {@code node} node.
     *
     * @param node Node initiator cache operations.
     * @param readOnly If {@code true} then cache must be in read only mode, else in read/write mode.
     * @param cacheNames Checked cache names.
     */
    public static void assertCachesReadOnlyMode(Ignite node, boolean readOnly, Collection<String> cacheNames) {
        Random rnd = new Random();

        for (String cacheName : cacheNames) {
            IgniteCache<Integer, Integer> cache = node.cache(cacheName);

            for (int i = 0; i < 10; i++) {
                cache.get(rnd.nextInt(100)); // All gets must succeed.

                if (readOnly) {
                    // All puts must fail.
                    try {
                        cache.put(rnd.nextInt(100), rnd.nextInt());

                        fail("Put must fail for cache " + cacheName);
                    }
                    catch (Exception ignored) {
                        // No-op.
                    }

                    // All removes must fail.
                    try {
                        cache.remove(rnd.nextInt(100));

                        fail("Remove must fail for cache " + cacheName);
                    }
                    catch (Exception ignored) {
                        // No-op.
                    }
                }
                else {
                    int key = rnd.nextInt(100);

                    cache.put(key, rnd.nextInt()); // All puts must succeed.

                    cache.remove(key); // All removes must succeed.
                }
            }
        }
    }

    /**
     * @param readOnly If {@code true} then data streamer must fail, else succeed.
     * @param cacheNames Checked cache names.
     */
    public static void assertDataStreamerReadOnlyMode(boolean readOnly, Collection<String> cacheNames) {
        Random rnd = new Random();

        for (Ignite ignite : G.allGrids()) {
            for (String cacheName : cacheNames) {
                boolean failed = false;

                try (IgniteDataStreamer<Integer, Integer> streamer = ignite.dataStreamer(cacheName)) {
                    for (int i = 0; i < 10; i++) {
                        ((DataStreamerImpl)streamer).maxRemapCount(5);

                        int key = rnd.nextInt(1000);

                        streamer.addData(key, rnd.nextInt());

                        streamer.removeData(key);
                    }
                }
                catch (CacheException ignored) {
                    failed = true;
                }

                if (failed != readOnly)
                    fail("Streaming to " + cacheName + " must " + (readOnly ? "fail" : "succeed"));
            }
        }
    }

    /**
     * Checks that given {@code ex} exception has a cause of {@link IgniteClusterReadOnlyException}.
     *
     * @param ex Exception for the check.
     * @param name Name of object (optional).
     */
    public static void checkRootCause(Throwable ex, @Nullable String name) {
        if (!X.hasCause(ex, IgniteClusterReadOnlyException.class))
            throw new AssertionError("IgniteClusterReadOnlyException not found on " + name, ex);
    }

    /**
     * @param cacheMode Cache mode.
     * @param atomicityMode Atomicity mode.
     * @param grpName Cache group name.
     */
    private static CacheConfiguration<Integer, Integer> cacheConfiguration(String name, CacheMode cacheMode,
        CacheAtomicityMode atomicityMode, String grpName) {
        return new CacheConfiguration<Integer, Integer>()
            .setName(name)
            .setCacheMode(cacheMode)
            .setAtomicityMode(atomicityMode)
            .setGroupName(grpName)
            .setBackups(1)
            .setQueryEntities(Collections.singletonList(new QueryEntity(Integer.class, Integer.class)));
    }

    /** */
    private ClusterReadOnlyModeTestUtils() {
    }
}
