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

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.CountDownLatch;
import org.apache.ignite.internal.util.GridConcurrentWeakHashSet;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.apache.ignite.testframework.junits.common.GridCommonTest;
import org.junit.Test;

/**
 * Test for {@link GridConcurrentWeakHashSet}.
 */
@GridCommonTest(group = "Lang")
public class GridConcurrentWeakHashSetSelfTest extends GridCommonAbstractTest {
    /** Time to wait after {@link System#gc} method call. */
    private static final long WAIT_TIME = 3000;

    /** How many time to call GC. */
    private static final int GC_CALL_CNT = 5;

    /**
     * @throws Exception Thrown if test failed.
     */
    @Test
    public void testA() throws Exception {
        Collection<Integer> set = new GridConcurrentWeakHashSet<>();

        Integer i = 1;

        assert set.add(i);
        assert !set.add(i);

        assert set.contains(i);

        assert set.size() == 1;

        Collection<Integer> c = F.asList(2, 3, 4, 5);

        assert set.addAll(c);
        assert !set.addAll(c);

        assert set.containsAll(c);

        assert set.size() == 1 + c.size();

        assert set.remove(i);
        assert !set.remove(i);

        assert !set.contains(i);

        assert set.size() == c.size();

        assert set.removeAll(c);
        assert !set.removeAll(c);

        assert !set.containsAll(c);

        assert set.isEmpty();

        Collection<Integer> c1 = Arrays.asList(1, 3, 5, 7, 9);

        int cnt = 0;

        for (Iterator<Integer> iter = set.iterator(); iter.hasNext(); cnt++)
            c1.contains(iter.next());

        assert set.size() == cnt;

        assert set.size() == set.toArray().length;

        assert set.addAll(c1);

        assert set.retainAll(c);
        assert !set.retainAll(c);

        Collection<Integer> c2 = F.retain(c1, true, c);

        assert set.containsAll(c2);
        assert !set.containsAll(c1);
        assert !set.containsAll(c);

        assert set.size() == c2.size();

        set.clear();

        assert set.isEmpty();

        try {
            set.iterator().next();

            assert false;
        }
        catch (NoSuchElementException ignored) {
            assert true;
        }

        try {
            set.add(null);

            assert false;
        }
        catch (NullPointerException ignored) {
            assert true;
        }
    }

    /**
     * @throws Exception Thrown if test failed.
     */
    @SuppressWarnings({"UnusedAssignment"})
    @Test
    public void testB() throws Exception {
        Collection<SampleBean> set = new GridConcurrentWeakHashSet<>();

        SampleBean bean1 = new SampleBean(1);

        assert set.add(bean1);
        assert !set.add(bean1);

        assert set.size() == 1;

        assert set.contains(bean1);

        bean1 = null;

        gc();

        assert set.isEmpty();

        Collection<SampleBean> c = F.asList(new SampleBean(1), new SampleBean(2), new SampleBean(3), new SampleBean(4));

        assert set.addAll(c);
        assert !set.addAll(c);

        assert set.size() == c.size();

        assert set.containsAll(c);

        c = null;

        gc();

        assert set.isEmpty();

        SampleBean b1 = new SampleBean(1);
        SampleBean b2 = new SampleBean(2);
        SampleBean b3 = new SampleBean(3);
        SampleBean b4 = new SampleBean(4);
        SampleBean b5 = new SampleBean(5);

        set.add(b1);
        set.add(b2);
        set.add(b3);
        set.add(b4);
        set.add(b5);

        Iterator iter = set.iterator();

        assert iter.hasNext();

        b2 = null;
        b3 = null;
        b4 = null;

        gc();

        int cnt = 0;

        while (iter.hasNext()) {
            info(iter.next().toString());

            cnt++;
        }

        assert set.size() == cnt;
    }

    /**
     * @throws Exception Thrown if test failed.
     */
    @Test
    public void testC() throws Exception {
        final Collection<SampleBean> set = new GridConcurrentWeakHashSet<>();

        int threadCnt = 2;

        final int cnt = 5;

        final CountDownLatch start = new CountDownLatch(1);
        final CountDownLatch stop = new CountDownLatch(threadCnt);

        Runnable r = new Runnable() {
            @Override public void run() {
                try {
                    start.await();

                    for (int i = 0; i < cnt; i++) {
                        for (int j = 0; j < cnt; j++)
                            set.add(new SampleBean(i));
                    }
                }
                catch (Exception e) {
                    error(e.getMessage());
                }

                stop.countDown();
            }
        };

        for (int i = 0; i < threadCnt; i++)
            new Thread(r).start();

        start.countDown();

        stop.await();

        assert set.size() == cnt;

        gc();

        assert set.isEmpty();
    }

    /**
     * @throws Exception Thrown if test failed.
     */
    @Test
    public void testD() throws Exception {
        final Collection<SampleBean> set = new GridConcurrentWeakHashSet<>();

        final int cnt = 100;

        final CountDownLatch start = new CountDownLatch(1);
        final CountDownLatch stop = new CountDownLatch(3);

        new Thread() {
            @Override public void run() {
                try {
                    start.await();

                    for (int i = 0; i < cnt; i++) {
                        for (int j = 0; j < cnt; j++)
                            set.add(new SampleBean(i));
                    }
                }
                catch (Exception e) {
                    error(e.getMessage());
                }

                stop.countDown();
            }
        }.start();

        new Thread() {
            @Override public void run() {
                try {
                    start.await();

                    for (int i = 0; i < cnt; i++) {
                        for (int j = 0; j < cnt; j++)
                            set.remove(new SampleBean(i));
                    }
                }
                catch (Exception e) {
                    error(e.getMessage());
                }

                stop.countDown();
            }
        }.start();

        new Thread() {
            @Override public void run() {
                try {
                    start.await();

                    while (stop.getCount() > 1) {
                        for (SampleBean b : set) {
                            // No-op.
                        }
                    }
                }
                catch (Exception e) {
                    error(e.getMessage());
                }

                stop.countDown();
            }
        }.start();

        start.countDown();

        stop.await();

        gc();

        assert set.isEmpty();
    }

    /**
     * Calls garbage collector and wait.
     *
     * @throws Exception if any thread has interrupted the current thread while waiting.
     */
    private void gc() throws Exception {
        Runtime rt = Runtime.getRuntime();

        long freeMem0 = rt.freeMemory();
        long freeMem = Long.MAX_VALUE;

        int cnt = 0;

        while (freeMem0 < freeMem && cnt < GC_CALL_CNT) {
            System.gc();

            U.sleep(WAIT_TIME);

            cnt++;

            freeMem = freeMem0;
            freeMem0 = rt.freeMemory();
        }
    }

    /**
     * Sample bean for this test.
     */
    private static class SampleBean {
        /** Number. */
        private int num;

        /** String. */
        private String str;

        /**
         * Creates sample bean.
         *
         * @param num Number.
         */
        private SampleBean(int num) {
            this.num = num;

            str = String.valueOf(num);
        }

        /** {@inheritDoc} */
        @SuppressWarnings({"RedundantIfStatement"})
        @Override public boolean equals(Object o) {
            if (this == o)
                return true;

            if (!(o instanceof SampleBean))
                return false;

            SampleBean that = (SampleBean)o;

            if (num != that.num)
                return false;

            if (str != null ? !str.equals(that.str) : that.str != null)
                return false;

            return true;
        }

        /** {@inheritDoc} */
        @Override public int hashCode() {
            int result = num;

            result = 31 * result + (str != null ? str.hashCode() : 0);

            return result;
        }

        /** {@inheritDoc} */
        @Override public String toString() {
            return S.toString(SampleBean.class, this);
        }
    }
}
