/*
 * 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.datastructures;

import java.io.Closeable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CyclicBarrier;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteAtomicLong;
import org.apache.ignite.IgniteAtomicReference;
import org.apache.ignite.IgniteAtomicSequence;
import org.apache.ignite.IgniteAtomicStamped;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteCountDownLatch;
import org.apache.ignite.IgniteException;
import org.apache.ignite.IgniteLock;
import org.apache.ignite.IgniteQueue;
import org.apache.ignite.IgniteSemaphore;
import org.apache.ignite.IgniteSet;
import org.apache.ignite.cache.CacheAtomicityMode;
import org.apache.ignite.cache.CacheMode;
import org.apache.ignite.configuration.AtomicConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.IgniteInternalFuture;
import org.apache.ignite.lang.IgniteUuid;
import org.apache.ignite.testframework.GridTestUtils;
import org.junit.Test;

import static org.apache.ignite.cache.CacheAtomicityMode.ATOMIC;
import static org.apache.ignite.cache.CacheMode.PARTITIONED;

/**
 *
 */
public class IgniteDataStructureUniqueNameTest extends IgniteCollectionAbstractTest {
    /** {@inheritDoc} */
    @Override protected int gridCount() {
        return 3;
    }

    /** {@inheritDoc} */
    @Override protected CacheMode collectionCacheMode() {
        return PARTITIONED;
    }

    /** {@inheritDoc} */
    @Override protected CacheAtomicityMode collectionCacheAtomicityMode() {
        return ATOMIC;
    }

    /** {@inheritDoc} */
    @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
        IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);

        AtomicConfiguration atomicCfg = new AtomicConfiguration();

        atomicCfg.setBackups(1);
        atomicCfg.setCacheMode(PARTITIONED);

        cfg.setAtomicConfiguration(atomicCfg);

        return cfg;
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testUniqueNameMultithreaded() throws Exception {
        testUniqueName(true);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testUniqueNameMultinode() throws Exception {
        testUniqueName(false);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testCreateRemove() throws Exception {
        final String name = IgniteUuid.randomUuid().toString();

        final Ignite ignite = ignite(0);

        assertNull(ignite.atomicLong(name, 0, false));

        IgniteAtomicReference<Integer> ref = ignite.atomicReference(name, 0, true);

        assertNotNull(ref);

        assertSame(ref, ignite.atomicReference(name, 0, true));

        GridTestUtils.assertThrows(log, new Callable<Void>() {
            @Override public Void call() throws Exception {
                ignite.atomicLong(name, 0, false);

                return null;
            }
        }, IgniteException.class, null);

        GridTestUtils.assertThrows(log, new Callable<Void>() {
            @Override public Void call() throws Exception {
                ignite.atomicLong(name, 0, true);

                return null;
            }
        }, IgniteException.class, null);

        ref.close();

        IgniteAtomicLong atomicLong = ignite.atomicLong(name, 0, true);

        assertNotNull(atomicLong);

        assertSame(atomicLong, ignite.atomicLong(name, 0, true));

        GridTestUtils.assertThrows(log, new Callable<Void>() {
            @Override public Void call() throws Exception {
                ignite.atomicReference(name, 0, false);

                return null;
            }
        }, IgniteException.class, null);

        GridTestUtils.assertThrows(log, new Callable<Void>() {
            @Override public Void call() throws Exception {
                ignite.queue(name, 0, config(false));

                return null;
            }
        }, IgniteException.class, null);

        GridTestUtils.assertThrows(log, new Callable<Void>() {
            @Override public Void call() throws Exception {
                ignite.queue(name, 0, null);

                return null;
            }
        }, IgniteException.class, null);

        GridTestUtils.assertThrows(log, new Callable<Void>() {
            @Override public Void call() throws Exception {
                ignite.set(name, config(false));

                return null;
            }
        }, IgniteException.class, null);

        GridTestUtils.assertThrows(log, new Callable<Void>() {
            @Override public Void call() throws Exception {
                ignite.set(name, null);

                return null;
            }
        }, IgniteException.class, null);

        atomicLong.close();

        IgniteQueue<Integer> q = ignite.queue(name, 0, config(false));

        assertNotNull(q);

        assertSame(q, ignite.queue(name, 0, config(false)));

        assertSame(q, ignite.queue(name, 0, null));

        q.close();

        assertNull(ignite.set(name, null));

        IgniteSet<Integer> set = ignite.set(name, config(false));

        assertNotNull(set);

        assertSame(set, ignite.set(name, config(false)));

        assertSame(set, ignite.set(name, null));

        GridTestUtils.assertThrows(log, new Callable<Void>() {
            @Override public Void call() throws Exception {
                ignite.atomicReference(name, 0, false);

                return null;
            }
        }, IgniteException.class, null);

        GridTestUtils.assertThrows(log, new Callable<Void>() {
            @Override public Void call() throws Exception {
                ignite.queue(name, 0, config(false));

                return null;
            }
        }, IgniteException.class, null);

        GridTestUtils.assertThrows(log, new Callable<Void>() {
            @Override public Void call() throws Exception {
                ignite.queue(name, 0, null);

                return null;
            }
        }, IgniteException.class, null);

        set.close();

        ref = ignite.atomicReference(name, 0, true);

        assertNotNull(ref);

        assertSame(ref, ignite.atomicReference(name, 0, true));

        ref.close();
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testUniqueNamePerGroup() throws Exception {
        Ignite ignite = ignite(0);

        IgniteAtomicLong atomicLong = ignite.atomicLong("testName",
            new AtomicConfiguration().setGroupName("group1"),
            0,
            true);

        IgniteAtomicSequence atomicSeq = ignite.atomicSequence("testName",
            new AtomicConfiguration().setGroupName("group2"),
            0,
            true);

        assert atomicLong != null;
        assert atomicSeq != null;

        atomicLong = ignite.atomicLong("testName",
            new AtomicConfiguration().setGroupName("group1"),
            0,
            false);

        atomicSeq = ignite.atomicSequence("testName",
            new AtomicConfiguration().setGroupName("group2"),
            0,
            false);

        assert atomicLong != null;
        assert atomicSeq != null;

        atomicLong.close();
        atomicSeq.close();
    }

    /**
     * @param singleGrid If {@code true} uses single grid.
     * @throws Exception If failed.
     */
    private void testUniqueName(final boolean singleGrid) throws Exception {
        final String name = IgniteUuid.randomUuid().toString();

        final int DS_TYPES = 6;

        final int THREADS = DS_TYPES * 3;

        for (int iter = 0; iter < 20; iter++) {
            log.info("Iteration: " + iter);

            List<IgniteInternalFuture<Object>> futs = new ArrayList<>(THREADS);

            final CyclicBarrier barrier = new CyclicBarrier(THREADS);

            for (int i = 0; i < THREADS; i++) {
                final int idx = i;

                IgniteInternalFuture<Object> fut = GridTestUtils.runAsync(new Callable<Object>() {
                    @Override public Object call() throws Exception {
                        try {
                            Thread.currentThread().setName("test thread-" + idx);

                            barrier.await();

                            Ignite ignite = singleGrid ? ignite(0) : ignite(idx % gridCount());

                            Object res;

                            switch (idx % DS_TYPES) {
                                case 0:
                                    log.info("Create atomic long, grid: " + ignite.name());

                                    res = ignite.atomicLong(name, 0, true);

                                    break;

                                case 1:
                                    log.info("Create atomic sequence, grid: " + ignite.name());

                                    res = ignite.atomicSequence(name, 0, true);

                                    break;

                                case 2:
                                    log.info("Create atomic stamped, grid: " + ignite.name());

                                    res = ignite.atomicStamped(name, 0, true, true);

                                    break;

                                case 3:
                                    log.info("Create atomic reference, grid: " + ignite.name());

                                    res = ignite.atomicReference(name, null, true);

                                    break;

                                case 4:
                                    log.info("Create queue, grid: " + ignite.name());

                                    res = ignite.queue(name, 0, config(false));

                                    break;

                                case 5:
                                    log.info("Create set, grid: " + ignite.name());

                                    res = ignite.set(name, config(false));

                                    break;

                                default:
                                    fail();

                                    return null;
                            }

                            log.info("Thread created: " + res);

                            return res;
                        }
                        catch (IgniteException e) {
                            log.info("Failed: " + e);

                            return e;
                        }
                    }
                });

                futs.add(fut);
            }

            Closeable dataStructure = null;

            int createdCnt = 0;

            for (IgniteInternalFuture<Object> fut : futs) {
                Object res = fut.get();

                if (res instanceof IgniteException || res instanceof IgniteCheckedException)
                    continue;

                assertTrue("Unexpected object: " + res,
                    res instanceof IgniteAtomicLong ||
                        res instanceof IgniteAtomicSequence ||
                        res instanceof IgniteAtomicReference ||
                        res instanceof IgniteAtomicStamped ||
                        res instanceof IgniteCountDownLatch ||
                        res instanceof IgniteQueue ||
                        res instanceof IgniteSet ||
                        res instanceof IgniteSemaphore ||
                        res instanceof IgniteLock);

                log.info("Data structure created: " + dataStructure);

                createdCnt++;

                if (dataStructure != null)
                    assertEquals(dataStructure.getClass(), res.getClass());
                else
                    dataStructure = (Closeable)res;
            }

            assertNotNull(dataStructure);

            assertEquals(3, createdCnt);

            dataStructure.close();
        }
    }
}
