/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch 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.elasticsearch.common.recycler;

import org.elasticsearch.common.recycler.Recycler.V;
import org.elasticsearch.test.ESTestCase;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public abstract class AbstractRecyclerTestCase extends ESTestCase {

    // marker states for data
    protected static final byte FRESH = 1;
    protected static final byte RECYCLED = 2;
    protected static final byte DEAD = 42;

    protected static final Recycler.C<byte[]> RECYCLER_C = new AbstractRecyclerC<byte[]>() {

        @Override
        public byte[] newInstance(int sizing) {
            byte[] value = new byte[10];
            // "fresh" is intentionally not 0 to ensure we covered this code path
            Arrays.fill(value, FRESH);
            return value;
        }

        @Override
        public void recycle(byte[] value) {
            Arrays.fill(value, RECYCLED);
        }

        @Override
        public void destroy(byte[] value) {
            // we cannot really free the internals of a byte[], so mark it for verification
            Arrays.fill(value, DEAD);
        }

    };

    protected void assertFresh(byte[] data) {
        assertNotNull(data);
        for (int i = 0; i < data.length; ++i) {
            assertEquals(FRESH, data[i]);
        }
    }

    protected void assertRecycled(byte[] data) {
        assertNotNull(data);
        for (int i = 0; i < data.length; ++i) {
            assertEquals(RECYCLED, data[i]);
        }
    }

    protected void assertDead(byte[] data) {
        assertNotNull(data);
        for (int i = 0; i < data.length; ++i) {
            assertEquals(DEAD, data[i]);
        }
    }

    protected abstract Recycler<byte[]> newRecycler(int limit);

    protected int limit = randomIntBetween(5, 10);

    public void testReuse() {
        Recycler<byte[]> r = newRecycler(limit);
        Recycler.V<byte[]> o = r.obtain();
        assertFalse(o.isRecycled());
        final byte[] b1 = o.v();
        assertFresh(b1);
        o.close();
        assertRecycled(b1);
        o = r.obtain();
        final byte[] b2 = o.v();
        if (o.isRecycled()) {
            assertRecycled(b2);
            assertSame(b1, b2);
        } else {
            assertFresh(b2);
            assertNotSame(b1, b2);
        }
        o.close();
        r.close();
    }

    public void testRecycle() {
        Recycler<byte[]> r = newRecycler(limit);
        Recycler.V<byte[]> o = r.obtain();
        assertFresh(o.v());
        random().nextBytes(o.v());
        o.close();
        o = r.obtain();
        assertRecycled(o.v());
        o.close();
        r.close();
    }

    public void testDoubleRelease() {
        final Recycler<byte[]> r = newRecycler(limit);
        final Recycler.V<byte[]> v1 = r.obtain();
        v1.close();
        try {
            v1.close();
        } catch (IllegalStateException e) {
            // impl has protection against double release: ok
            return;
        }
        // otherwise ensure that the impl may not be returned twice
        final Recycler.V<byte[]> v2 = r.obtain();
        final Recycler.V<byte[]> v3 = r.obtain();
        assertNotSame(v2.v(), v3.v());
        r.close();
    }

    public void testDestroyWhenOverCapacity() {
        Recycler<byte[]> r = newRecycler(limit);

        // get & keep reference to new/recycled data
        Recycler.V<byte[]> o = r.obtain();
        byte[] data = o.v();
        assertFresh(data);

        // now exhaust the recycler
        List<V<byte[]>> vals = new ArrayList<>(limit);
        for (int i = 0; i < limit ; ++i) {
            vals.add(r.obtain());
        }
        // Recycler size increases on release, not on obtain!
        for (V<byte[]> v: vals) {
            v.close();
        }

        // release first ref, verify for destruction
        o.close();
        assertDead(data);

        // close the rest
        r.close();
    }

    public void testClose() {
        Recycler<byte[]> r = newRecycler(limit);

        // get & keep reference to pooled data
        Recycler.V<byte[]> o = r.obtain();
        byte[] data = o.v();
        assertFresh(data);

        // randomize & return to pool
        random().nextBytes(data);
        o.close();

        // verify that recycle() ran
        assertRecycled(data);

        // closing the recycler should mark recycled instances via destroy()
        r.close();
        assertDead(data);
    }

}
